如何将包含核心动画层的视图渲染为位图?

13

我正在使用一个 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

然而,当我使用这段代码时,核心动画层没有被渲染。

我研究了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相关的示例代码。

2个回答

16

关于记录Core Animations的方法,“Cocoa is my girlfriend”有一篇很好的文章。作者将整个动画捕捉到电影中,但您可以使用他抓取单个帧的部分。
跳转到本文的“获取当前帧”部分:
http://www.cimgf.com/2009/02/03/record-your-core-animation-animation/

基本思路是:

  • 创建一个CGContext
  • 使用CALayer的renderInContext:
  • 从上下文中创建NSBitmapImageRep(使用CGBitmapContextCreateImage和NSBitmapImageRep的initWithCGImage)

更新:
我刚刚阅读到,在Mac OS X 10.5中,renderInContext:方法不支持所有类型的层。 它不适用于以下图层类:

  • QCCompositionLayer
  • CAOpenGLLayer
  • QTMovieLayer

非常感谢,这正是我所需要的,并且完美地运行。我希望早些时候注意到了-renderInContext:方法,而不是被CARenderer误导。 - Rob Keniger
这对于屏幕外的元素有效吗?例如,我的CALayer包含一些尚未在视图框架中的元素,但我需要滚动才能看到它们。 - Tudor
有人尝试过在使用 [UIView animateWithDuration:...] 启动的动画中实现这个吗?我尝试了但是没有成功。 - Isak

4
如果您需要渲染CALayer层次结构为NSImage(或iPhone的UIImage)的示例代码,可以查看Core Plot框架的CPLayer及其-imageOfLayer方法。我们实际上创建了一条独立于CALayer使用的正常-renderInContext:过程的渲染路径,因为正常方式在生成图层的PDF表示时不保留矢量元素。这就是为什么您会在此代码中看到-recursivelyRenderInContext:方法。

但是,如果您尝试从weichsel提到的任何图层类型(QCCompositionLayer、CAOpenGLLayer或QTMovieLayer)中捕获,则无法帮助您。


谢谢,这比weichsel的回答全面得多,但在这种情况下,这已经超出了我实际需要的范围。-recursivelyRenderInContext:的代码确实非常好。 - Rob Keniger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接