Render/Snapshot SpriteKit Scene to NSImage
Asked Answered
S

4

16

Does anyone know how to "snapshot" a complete SKView or SKScene into an NSImage?

We have been able to use the textureFromNode API to create an SKTexture from a node and all its children. But so far we can't figure out a way to extract the image data into say an NSImage. We are building a mixed Cocoa / SpriteKit app where we need to extract small thumbnails of scenes.

Again, this seems possible on iOS (to get a UIImage) using drawViewHierarchyInRect:

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, scale);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

But how to do this on Cocoa with NSImage? Help.

Supreme answered 18/12, 2013 at 17:38 Comment(3)
This is what you are looking for: #3251761Cinereous
No, the link you mention doesn't work for OpenGL-based views (see the comments in it), which is the case for SKViews.Overdo
How about the answer at: https://mcmap.net/q/491462/-ios-sprite-kit-screengrabMadison
T
5

As of OS X 10.11 (and iOS 9 and tvOS, but we'll ignore those since you're asking about NSImage), SKTexture can export its contents to a CGImageRef. So the textureFromNode approach you mention is now the first step in a working solution:

SKTexture *texture = [view textureFromNode: view.scene];
NSImage *image = [[NSImage alloc] initWithCGImage: texture.CGImage
                                             size: view.bounds];

Note the size parameter to initWithCGImage: you can pass CGSizeZero there to make the NSImage inherit the pixel size of the CGImage, but since you might be dealing with a Retina Display you probably want the point size, which you can get from the view dimensions.

Thrasher answered 11/2, 2016 at 19:57 Comment(0)
C
0

Here was my attempt (on OSX) to scoop the underlying rendered image...

First, I subclassed SKView and in its drawRect method lazily grabbed an NSBitmapImageRep

if (![self cache])
    [self setCache:[self bitmapImageRepForCachingDisplayInRect:[self visibleRect]]];

I then set up an NSTimer which would periodically attempt to copy the contents of that cache to a custom thumbnail view:

MySKView *sv = .....
NSBitmapImageRep *cache = [sv cache];
if (!cache)
    return;

NSRect srcFrame = NSMakeRect(0,0,[sv frame].size.width,[sv frame].size.height);
NSRect thumbFrame = [[self thumbView] frame];
NSRect thumbRect = NSMakeRect(0,0,thumbFrame.size.width,thumbFrame.size.height);
[sv cacheDisplayInRect:srcFrame toBitmapImageRep:cache];
[[self thumbView] lockFocus];
[cache drawInRect:thumbRect
         fromRect:srcFrame
         operation:NSCompositeCopy
         fraction:1.0
   respectFlipped:YES
            hints:nil];
[[self thumbView] unlockFocus];

No dice. All I get in my thumbNail view is black.

Likely for the same reason that this is all OpenGL/GPU-based drawing.

That would lead me in the direction of somehow finding the underlying OpenGL drawing context used by SKView and doing a glReadPixels on that context.

Canella answered 3/4, 2014 at 14:14 Comment(1)
The headers say you can't subclass SKView… I'm thinking maybe in the update: method?Acidosis
N
0

There's no direct access to the image data from a SKTexture yet. You can present the node in a offscreen scene and capture the view hierarchy. I use this code in iOS for reference:

class func imageFromNode(node : SKNode, inScene scene: SKScene) -> UIImage? {
    if let texture = scene.view?.textureFromNode(node) {
        let view = SKView(frame: CGRect(origin: CGPointZero, size: texture.size()))
        view.allowsTransparency = true
        view.backgroundColor = SKColor.clearColor()

        let scene = SKScene(size: view.bounds.size)
        scene.view?.allowsTransparency = true
        scene.backgroundColor = SKColor.clearColor()

        let sprite  = SKSpriteNode(texture: texture)
        sprite.position = CGPoint(x: CGRectGetMidX(view.frame), y: CGRectGetMidY(view.frame))
        scene.addChild(sprite)
        view.presentScene(scene)
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0.0)
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
    return nil
}
Napkin answered 2/12, 2014 at 20:10 Comment(0)
C
-1

SKView is a UIView subclass, so you should be able to draw the view's layer in a CoreGraphics context, and then get the image from the context.

Look at using view.layer.drawInContext.

Cameroncameroon answered 5/3, 2015 at 16:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.