Как отрендерить SKNode в UIImage

Просто играю со SpriteKit и пытаюсь понять, как захватить «захват» SKNode в UIImage.

С UIView (или подклассом UIView) я использовал свойство layer представления для рендеринга в графическом контексте.

Eg.

#import <QuartzCore/QuartzCore.h>
+ (UIImage *)imageOfView:(UIView *)view {
    UIGraphicsBeginImageContextWithOptions(view.frame.size, YES, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [view.layer renderInContext:context];
    UIImage *viewShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return viewShot;
}

SKNode не является подклассом UIView и, следовательно, не поддерживается слоем.

Любые идеи о том, как я могу сделать рендеринг данного SKNode в UIImage?


person So Over It    schedule 17.12.2013    source источник


Ответы (2)


Это захватит всю сцену:

CGRect bounds = self.scene.view.bounds;
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, [UIScreen mainScreen].scale);
[self drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
UIImage* screenshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Если вам нужна только конкретная ветвь узла, вы можете скрыть все узлы, которые вы не хотите захватывать, прежде чем делать снимок экрана. Вы также можете использовать накопленный кадр, преобразованный в координаты UIKit, чтобы захватить только область рассматриваемого узла и его дочерних элементов.

В качестве альтернативы вы можете получить SKTexture из определенной части иерархии узлов:

SKTexture* tex = [self.scene.view textureFromNode:yourNode];

До iOS 9 не было способа преобразовать SKTexture обратно в UIImage. Теперь, однако, это тривиально:

UIImage *image = [UIImage imageWithCGImage:tex.CGImage];
person LearnCocos2D    schedule 17.12.2013
comment
Спасибо. Результаты нужно было снова использовать в наборе Sprite Kit, так что textureFromNode: отлично сработала. Ваше здоровье! - person So Over It; 19.12.2013
comment
@StevenRitchie К сожалению, да. - person ColdSteel; 29.12.2016
comment
Есть ли способ захватить спрайт с прозрачным фоном? - person GeneCode; 05.04.2020

Для тех, кто просматривает эту ветку 7 лет спустя, я пытался использовать описанные выше методы, но они дали относительно медленные результаты. Я хотел визуализировать всю SKScene за кадром, так как я визуализировал изображения как кадры для видео. Это означало, что я не мог использовать 1-й метод, предложенный LearnCocos2D, поскольку он требовал, чтобы вид отображался на экране, а второй метод требовал очень много времени для преобразования из SKTexture в UIImage. Вы можете использовать новый класс SKRenderer, представленный в iOS 11.0, для рендеринга всей сцены в UIImage, и он использует преимущества Metal, поэтому рендеринг происходит относительно быстро. Я смог отрендерить 1920x1080 SKScene примерно за 0,013 секунды!

Вы можете использовать это расширение:

  • Убедитесь, что вы import MetalKit
  • Параметр ignoreScreenScale указывает, должно ли выходное изображение быть точным до пикселя. Обычно, если вы будете отображать изображение обратно на экран, вы захотите, чтобы это значение было ложным. Если это значение равно false, размер выходного изображения масштабируется в соответствии с масштабом устройства таким образом, что каждая точка сцены занимает такое же количество пикселей изображения, как и на экране. Когда это верно, размер выходного изображения в пикселях равен размеру SKScene в точках.

Ваше здоровье!

extension SKScene {
    func toImage(ignoreScreenScale: Bool = false) -> UIImage? {
        guard let device = MTLCreateSystemDefaultDevice(),
              let commandQueue = device.makeCommandQueue(),
              let commandBuffer = commandQueue.makeCommandBuffer() else { return nil }

        let scale = ignoreScreenScale ? 1 : UIScreen.main.scale
        let size = self.size.applying(CGAffineTransform(scaleX: scale, y: scale))
        let renderer = SKRenderer(device: device)
        let renderPassDescriptor = MTLRenderPassDescriptor()

        var r = CGFloat.zero, g = CGFloat.zero, b = CGFloat.zero, a = CGFloat.zero
        backgroundColor.getRed(&r, green: &g, blue: &b, alpha: &a)

        let textureDescriptor = MTLTextureDescriptor()
        textureDescriptor.usage = [.renderTarget, .shaderRead]
        textureDescriptor.width = Int(size.width)
        textureDescriptor.height = Int(size.height)
        let texture = device.makeTexture(descriptor: textureDescriptor)

        renderPassDescriptor.colorAttachments[0].loadAction = .clear
        renderPassDescriptor.colorAttachments[0].texture = texture
        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(
            red: Double(r),
            green: Double(g),
            blue: Double(b),
            alpha:Double(a)
        )

        renderer.scene = self
        renderer.render(withViewport: CGRect(origin: .zero, size: size), commandBuffer: commandBuffer, renderPassDescriptor: renderPassDescriptor)
        commandBuffer.commit()

        let image = CIImage(mtlTexture: texture!, options: nil)!
        let transformed = image.transformed(by: CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -image.extent.size.height))
        return UIImage(ciImage: transformed)
    }
}
person CentrumGuy    schedule 29.12.2020