How do I render a view which contains Core Animation layers to a bitmap?
Asked Answered
N

2

13

I am using an NSView to host several Core Animation CALayer objects. What I want to be able to do is grab a snapshot of the view's current state as a bitmap image.

This is relatively simple with a normal NSView using something like this:

void ClearBitmapImageRep(NSBitmapImageRep* bitmap) {
    unsigned char* bitmapData = [bitmap bitmapData];
    if (bitmapData != NULL)
        bzero(bitmapData, [bitmap bytesPerRow] * [bitmap pixelsHigh]);
}

@implementation NSView (Additions)
- (NSBitmapImageRep*)bitmapImageRepInRect:(NSRect)rect
{
    NSBitmapImageRep* imageRep = [self bitmapImageRepForCachingDisplayInRect:rect];
    ClearBitmapImageRep(imageRep);
    [self cacheDisplayInRect:rect toBitmapImageRep:imageRep];
    return imageRep;
}
@end

However, when I use this code, the Core Animation layers are not rendered.

I have investigated CARenderer, as it appears to do what I need, however I cannot get it to render my existing layer tree. I tried the following:

NSOpenGLPixelFormatAttribute att[] = 
{
    NSOpenGLPFAWindow,
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFAColorSize, 24,
    NSOpenGLPFAAlphaSize, 8,
    NSOpenGLPFADepthSize, 24,
    NSOpenGLPFANoRecovery,
    NSOpenGLPFAAccelerated,
    0
};

NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:att];
NSOpenGLView* openGLView = [[NSOpenGLView alloc] initWithFrame:[self frame] pixelFormat:pixelFormat];
NSOpenGLContext* oglctx = [openGLView openGLContext];

CARenderer* renderer = [CARenderer rendererWithCGLContext:[oglctx CGLContextObj] options:nil];
renderer.layer = myContentLayer;
[renderer render];
NSBitmapImageRep* bitmap = [oglView bitmapImageRepInRect:[oglView bounds]];

However, when I do this I get an exception:

CAContextInvalidLayer -- layer <CALayer: 0x1092ea0> is already attached to a context

I'm guessing that this must be because the layer tree is hosted in my NSView and therefore attached to its context. I don't understand how I can detach the layer tree from the NSView in order to render it to a bitmap, and it's non-trivial in this case to create a duplicate layer tree.

Is there some other way to get the CALayers to render to a bitmap? I can't find any sample code anywhere for doing this, in fact I can't find any sample code for CARenderer at all.

Necessaries answered 30/12, 2009 at 3:50 Comment(0)
B
16

There is a great post on "Cocoa is my girlfriend" about recording Core Animations. The author captures the whole animation into a movie, but you could use the part where he grabs a single frame.
Jump to the "Obtaining the Current Frame" section in this article:
http://www.cimgf.com/2009/02/03/record-your-core-animation-animation/

The basic idea is:

  • Create a CGContext
  • Use CALayer's renderInContext:
  • Create a NSBitmapImageRep from the context (using CGBitmapContextCreateImage and NSBitmapImageRep's initWithCGImage)

Update:
I just read, that the renderInContext: method does not support all kind of layers in Mac OS X 10.5. It does not work for the following layers classes:

  • QCCompositionLayer
  • CAOpenGLLayer
  • QTMovieLayer
Burford answered 30/12, 2009 at 8:59 Comment(3)
Many thanks, this is exactly what I needed and works perfectly. I wish I'd noticed the -renderInContext: method earlier, CARenderer led me up the garden path.Necessaries
Does this work for the elements are offscreen? E.g. my CALayer contains some elements that are not in the view frame yet, but I need to scroll to see them.Dick
Has anyone gotten this to work with an animation started with [UIView animateWithDuration:...]? It did not work for me.Blumenthal
K
4

If you want sample code for how to render a CALayer hierarchy to an NSImage (or UIImage for the iPhone), you can look at the Core Plot framework's CPLayer and its -imageOfLayer method. We actually created a rendering pathway that is independent of the normal -renderInContext: process used by CALayer, because the normal way does not preserve vector elements when generating PDF representations of layers. That's why you'll see the -recursivelyRenderInContext: method in this code.

However, this won't help you if you are trying to capture from any of the layer types mentioned by weichsel (QCCompositionLayer, CAOpenGLLayer, or QTMovieLayer).

Kellar answered 30/12, 2009 at 22:4 Comment(1)
Thanks for this, it is much more comprehensive than the answer from weichsel but it's more than I actually need in this case. The -recursivelyRenderInContext: code is very nice indeed.Necessaries

© 2022 - 2024 — McMap. All rights reserved.