Search code examples
cocoadrawingcore-animationcalayernsview

Capturing an offline NSView to an NSImage


I'm trying to make a custom animation for replacing an NSView with another. For that reason I need to get an image of the NSView before it appears on the screen.

The view may contain layers and NSOpenGLView subviews, and therefore standard options like initWithFocusedViewRect and bitmapImageRepForCachingDisplayInRect do not work well in this case (they layers or OpenGL content well in my experiments).

I am looking for something like CGWindowListCreateImage, that is able to "capture" an offline NSWindow including layers and OpenGL content.

Any suggestions?


Solution

  • I created a category for this:

    @implementation NSView (PecuniaAdditions)
    
    /**
     * Returns an offscreen view containing all visual elements of this view for printing,
     * including CALayer content. Useful only for views that are layer-backed.
     */
    - (NSView*)printViewForLayerBackedView;
    {
        NSRect bounds = self.bounds;
        int bitmapBytesPerRow = 4 * bounds.size.width;
    
        CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
        CGContextRef context = CGBitmapContextCreate (NULL,
                                                      bounds.size.width,
                                                      bounds.size.height,
                                                      8,
                                                      bitmapBytesPerRow,
                                                      colorSpace,
                                                      kCGImageAlphaPremultipliedLast);
        CGColorSpaceRelease(colorSpace);
    
        if (context == NULL)
        {
            NSLog(@"getPrintViewForLayerBackedView: Failed to create context.");
            return nil;
        }
    
        [[self layer] renderInContext: context];
        CGImageRef img = CGBitmapContextCreateImage(context);
        NSImage* image = [[NSImage alloc] initWithCGImage: img size: bounds.size];
    
        NSImageView* canvas = [[NSImageView alloc] initWithFrame: bounds];
        [canvas setImage: image];
    
        CFRelease(img);
        CFRelease(context);
        return canvas;
    }
    
    @end
    

    This code is primarily for printing NSViews which contain layered child views. Might help you too.