28

I am trying to get a screen grab of a view that has a SKScene in it. The technique I am using is:

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, scale);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

This works great with normal UIViews, but for whatever reason it is ignoring all the sprites in the SKScene.

I'm not sure if this is a bug, or if Sprite Kit's rendering is separate from UIGraphics.

Question: How do I get a screen grab of an SKScene when the way that worked for UIViews seems to not work with Sprite Kit, or has anyone had success using UIGraphics context with Sprite Kit?

Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
Siegfoult
  • 1,844
  • 17
  • 19
  • 1
    Good question. Since SKView is using OpenGL you will need to use the code to grab an OpenGL framebuffer. Example: http://stackoverflow.com/questions/11769006/cocos2d-2-0-bizarre-behavior-during-a-screenshot-capture Question is whether glReadPixels will return anything meaningful, since SKView's open gl is not exposed though you can get the GL context via [EAGLContext currentContext]. – CodeSmile Oct 24 '13 at 16:53
  • See http://stackoverflow.com/a/37625473/763355 – MoDJ Jun 06 '16 at 00:49

4 Answers4

31

You almost have it, but the problem is as explained in the comment above. If you want to capture SKScene contents, try something like this instead:

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

The solution is to basically use the new method drawViewHierarchyInRect:afterScreenUpdates instead, which is the best we have for now; note, it's not exactly speedy, so doing this in realtime will not be pretty.

idmean
  • 14,540
  • 9
  • 54
  • 83
Matt
  • 4,849
  • 3
  • 26
  • 23
  • @Matt How quickly does say a single screen take to grab via this method take ? What does 'not exactly speedy' equate to roughly ? – prototypical Oct 24 '13 at 19:48
  • It's sub-500ms in my simple test scenes, but hard to say without benchmarking your specific scene. Less sprites/movements seems to make it a bit faster. If this is an occasional feature, it's not a major issue. But if you want to do realtime processing (i.e. emulate blur effect with moving sprites), I doubt this will work on current hardware. – Matt Oct 24 '13 at 19:57
  • Both methods of screen grabbing are too slow for moving sprites. That was something I tried before. Now, I'm just doing it once in a while for a view that appears on the screen (sort of like a UIAlertView). When not in a tight loop, both methods of screen grab run fast enough to not be noticed. – Siegfoult Oct 24 '13 at 20:30
  • I've found that pending changes aren't applied (e.g. hiding nodes) even if you say YES to afterScreenUpdates. (iOS8.3, iPhone 4s). the screen capture is still the previous state. However, calling the screen grab like this did solve it: `[self runAction:[SKAction waitForDuration:0.0001] completion:^{ dothescreengrab }];` – GilesDMiddleton Jun 29 '15 at 16:44
10

As a faster alternative, you can use the textureFromNode method on SKView, which returns an image of the view as an SKTexture. Simply pass the SKScene as the argument:

let texture = skView.textureFromNode(scene)

Or to capture part of the scene:

let texture = skView.textureFromNode(scene, crop: cropRect)
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78
4

//1: get texture from "someNode"

let texture = skView.textureFromNode(someNode)

//2: get UIImage from the node texture

let image = UIImage(cgImage: texture!.cgImage())
letanthang
  • 390
  • 5
  • 7
0

A solution for Swift:

func getScreenshot(scene: SKScene, duration:TimeInterval = 0.0001, completion:((_ txt:SKTexture) -> Void)?) {
    let action = SKAction.run {
        let bounds = scene.view?.bounds
        var image = UIImage()
        UIGraphicsBeginImageContextWithOptions(bounds!.size, true, UIScreen.main.scale)
        scene.view?.drawHierarchy(in: bounds!, afterScreenUpdates: true)
        if let screenshot = UIGraphicsGetImageFromCurrentImageContext() {
            UIGraphicsEndImageContext()
            image = screenshot
        } else {
            assertionFailure("Unable to make a screenshot for the scene \(type(of:scene))")
        }
        completion!(SKTexture(image: image))
    }
    let wait = SKAction.wait(forDuration: duration)
    let seq = SKAction.sequence([wait,action])
    scene.run(seq,withKey:"getScreenshot")
}

Usage:

getScreenshot(scene: self, completion:{ (txt) -> Void in
            let thumbNode = SKSpriteNode(texture: txt, size:CGSize(width:300,height:224))
            // do whatever you want with your screenshot..
}
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133