在可可中将视图层次结构绘制到特定上下文中

时间:2020-03-05 18:40:26  来源:igfitidea点击:

对于我的应用程序的一部分,我需要创建一个特定视图及其所有子视图的图像。

为此,我创建了一个上下文,该上下文包装了与视图大小相同的位图,但是我不确定如何在其中绘制视图层次结构。我可以仅通过设置上下文并显式调用drawRect来绘制一个视图,但这不能处理所有子视图。

我在NSView界面中看不到任何可以解决此问题的方法,因此我怀疑解决方案可能在更高的层次上。

解决方案

回答

我们可以使用-[NSView dataWithPDFInsideRect:]渲染将视图发送给PDF的整个层次结构,以NSData对象的形式返回。然后,我们可以随心所欲地做任何事情,包括将其渲染为位图。

我们确定要位图表示吗?毕竟,PDF可能(至少在理论上)与分辨率无关。

回答

是的,对于这种特定用途,未缩放的图像数据正是我所需要的。

回答

在将焦点锁定在视图上之后,可以使用-[NSBitmapImageRep initWithFocusedViewRect:]来使视图本身(及其子视图)呈现为给定矩形。

回答

我发现自己编写绘图代码是实现以下目标的最佳方法:

  • 处理潜在的透明度问题(其他一些选项的确会在整个图像中添加白色背景)
  • 性能要好得多

下面的代码并不完美,因为从边界到帧时它不能处理缩放问题,但是它确实考虑了isFlipped状态,并且可以很好地用于我的用途。请注意,它仅绘制子视图(和子子视图,...以递归方式),但是使其也可以绘制自身非常容易,只需在imageWithSubviews的实现中添加一个[self drawRect:[self bounds]] 。

- (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;
}

回答

我们想做的事情已经明确可用。请参阅10.4 AppKit发行说明中的​​" NSView图形重定向API"部分。

制作一个NSBitmapImageRep进行缓存并清除它:

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

缓存到它:

-[NSView cacheDisplayInRect:toBitmapImageRep:]

如果我们想更广泛地利用指定的上下文来正确处理视图的递归和透明性,

-[NSView displayRectIgnoringOpacity:inContext:]