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

bitmap, cocoa, core-animation, objective-c, rendering

Solution

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

Problem

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 `CALayer`s 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.

Original source