Как визуализировать представление, содержащее слои основной анимации, в растровое изображение?

Я использую NSView для размещения нескольких объектов Core Animation CALayer. Я хочу, чтобы иметь возможность сделать снимок текущего состояния представления в виде растрового изображения.

Это относительно просто с обычным NSView, используя что-то вроде этого:

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

Однако, когда я использую этот код, уровни Core Animation не отображаются.

Я исследовал CARenderer, поскольку он, похоже, делает то, что мне нужно, однако я не могу заставить его визуализировать существующее дерево слоев. Я попробовал следующее:

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]];

Однако, когда я делаю это, я получаю исключение:

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

Я предполагаю, что это должно быть потому, что дерево слоев размещено в моем NSView и поэтому привязано к его контексту. Я не понимаю, как я могу отделить дерево слоев от NSView, чтобы отобразить его в растровое изображение, и в этом случае нетривиально создать дублирующее дерево слоев.

Есть ли другой способ получить CALayer для рендеринга в растровое изображение? Я не могу найти какой-либо пример кода для этого, на самом деле я вообще не могу найти код для CARenderer.

Ответ 1

Есть отличная статья на тему "Cocoa - моя девушка" о записи Core Animations. Автор фиксирует всю анимацию в фильме, но вы можете использовать ту часть, где он захватывает один кадр.
Перейдите к разделу "Получение текущего кадра" в этой статье:
http://www.cimgf.com/2009/02/03/record-your-core-animation-animation/

Основная идея:

  • Создать CGContext
  • Используйте CALayer renderInContext:
  • Создайте NSBitmapImageRep из контекста (используя CGBitmapContextCreateImage и NSBitmapImageRep initWithCGImage)

Update:
Я просто прочитал, что метод renderInContext: не поддерживает все типы слоев в Mac OS X 10.5. Он не работает для следующих классов слоев:

  • QCCompositionLayer
  • CAOpenGLLayer
  • QTMovieLayer

Ответ 2

Если вам нужен пример кода для визуализации иерархии CALayer для NSImage (или UIImage для iPhone), вы можете посмотреть Core Plot framework CPLayer и - метод imageOfLayer. Мы фактически создали путь рендеринга, который не зависит от процесса normal -renderInContext:, который используется CALayer, поскольку обычный способ не сохраняет векторные элементы при создании PDF-представлений слоев. Поэтому в этом коде вы увидите метод -recursivelyRenderInContext:

Однако это не поможет вам, если вы пытаетесь захватить любой из типов слоев, упомянутых weichsel (QCCompositionLayer, CAOpenGLLayer или QTMovieLayer).