views:

1143

answers:

5

For part of my application I have a need to create an image of a certain view and all of its subviews.

To do this I'm creating a context that wraps a bitmap with the same-size as the view, but I'm unsure how to draw the view hierarchy into it. I can draw a single view just be setting the context and explicitly calling drawRect, but this does not deal with all of the subviews.

I can't see anything in the NSView interface that could help with this so I suspect the solution may lie at a higher level.

+2  A: 

You can use -[NSView dataWithPDFInsideRect:] to render the entire hierarchy of the view you send it to into a PDF, returned as an NSData object. You can then do whatever you wish with that, including render it into a bitmap.

Are you sure you want a bitmap representation though? After all, that PDF could be (at least in theory) resolution-independent.

Chris Hanson
A: 

Yeah, for this specific usage the un-scaled image data is exactly what I need.

Andrew Grant
+1  A: 

You can use -[NSBitmapImageRep initWithFocusedViewRect:] after locking focus on a view to have the view render itself (and its subviews) into the given rectangle.

Chris Hanson
+2  A: 

I found that writing the drawing code myself was the best way to:

  • deal with potential transparency issues (some of the other options do add a white background to the whole image)
  • performance was much better

The code below is not perfect, because it does not deal with scaling issues when going from bounds to frames, but it does take into account the isFlipped state, and works very well for what I used it for. Note that it only draws the subviews (and the subsubviews,... recursively), but getting it to also draw itself is very easy, just add a [self drawRect:[self bounds]] in the implementation of imageWithSubviews.

- (void)drawSubviews
{
    BOOL flipped = [self isFlipped];

    for ( NSView *subview in [self subviews] ) {

     // changes the coordinate system so that the local coordinates of the subview (bounds) become the coordinates of the superview (frame)
     // the transform assumes bounds and frame have the same size, and bounds origin is (0,0)
     // handling of 'isFlipped' also probably unreliable
     NSAffineTransform *transform = [NSAffineTransform transform];
     if ( flipped ) {
      [transform translateXBy:subview.frame.origin.x yBy:NSMaxY(subview.frame)];
      [transform scaleXBy:+1.0 yBy:-1.0];
     } else
      [transform translateXBy:subview.frame.origin.x yBy:subview.frame.origin.y];
     [transform concat];

     // recursively draw the subview and sub-subviews
     [subview drawRect:[subview bounds]];
     [subview drawSubviews];

     // reset the transform to get back a clean graphic contexts for the rest of the drawing
     [transform invert];
     [transform concat];
    }
}

- (NSImage *)imageWithSubviews
{
    NSImage *image = [[[NSImage alloc] initWithSize:[self bounds].size] autorelease];
    [image lockFocus];
    // it seems NSImage cannot use flipped coordinates the way NSView does (the method 'setFlipped:' does not seem to help)
    // Use instead an NSAffineTransform
    if ( [self isFlipped] ) {
     NSAffineTransform *transform = [NSAffineTransform transform];
     [transform translateXBy:0 yBy:NSMaxY(self.bounds)];
     [transform scaleXBy:+1.0 yBy:-1.0];
     [transform concat];
    }
    [self drawSubviews];
    [image unlockFocus];
    return image;
}
charles
+1  A: 

What you want to do is available explicitly already. See the section "NSView Drawing Redirection API" in the 10.4 AppKit release notes.

Make an NSBitmapImageRep for caching and clear it: NSGraphicsContext *bitmapGraphicsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:cacheBitmapImageRep]; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:bitmapGraphicsContext]; [[NSColor clearColor] set]; NSRectFill(NSMakeRect(0, 0, [cacheBitmapImageRep size].width, [cacheBitmapImageRep size].height)); [NSGraphicsContext restoreGraphicsState];

Cache to it: -[NSView cacheDisplayInRect:toBitmapImageRep:]

If you want to more generally draw into a specified context handling view recursion and transparency correctly, -[NSView displayRectIgnoringOpacity:inContext:]

Link for "NSView Drawing Redirection API" in 10.4 http://developer.apple.com/mac/library/releasenotes/Cocoa/AppKitOlderNotes.html
Matthieu Cormier