如何避免NSBitmapImageRep创建大量中间CGImage?

时间:2018-10-06 04:18:05

标签: multithreading macos cgimage nsbitmapimagerep

我有一个生成艺术应用程序,该应用程序从一小部分要点开始,向外扩展它们,然后检查增长以确保它不与任何东西相交。我的第一个天真的实现是在主UI线程上完成所有操作,并产生预期的结果。随着大小的增加,有更多要检查的点,它会放慢速度并最终阻塞UI。

我做了明显的事情,并将计算移到另一个线程,以便UI可以保持响应。这有所帮助,但只有一点点。我通过NSBitmapImageRep来包裹NSGraphicsContext来完成此工作,以便可以使用它。但是我需要确保在尝试在后台线程上绘制时,不要将其绘制到主UI线程的屏幕上。所以我介绍了一把锁。随着数据变大,绘图可能会花费很长时间,因此即使这样做也是有问题的。

我的最新修订版有2个NSBitmapImageRep。一个拥有最新绘制的版本,并在需要更新视图时将其绘制到屏幕上。另一个被吸引到后台线程。在后台线程上绘制完成后,将其复制到另一个线程上。我通过获取每个的基地址并简单地调用memcpy()来将像素从一个像素移动到另一个像素来进行复制。 (我尝试交换它们而不是复制它们,但是即使图形以对[-NSGraphicsContext flushContext]的调用结束,我仍将部分绘制的结果绘制到窗口中。)

计算线程如下:

    BOOL    done    = NO;
    while (!done)
    {
        self->model->lockBranches();
        self->model->iterate();
        done = (!self->model->moreToDivide()) || (!self->keepIterating);
        self->model->unlockBranches();

        [self drawIntoOffscreen];

        dispatch_async(dispatch_get_main_queue(), ^{
            self.needsDisplay = YES;
        });
    }

这足以使UI保持响应状态。但是,每次将绘制的图像复制到斑点图像时,我都会调用[-NSBitmapImageRep baseAddress]。查看乐器中的内存配置文件,对该函数的每次调用都会导致创建一个CGImage。此外,CGImage直到计算完成才发布,可能要花几分钟。这将导致内存增大。我在过程中看到了大约3-4个CGImage,尽管我从来不需要超过两个。计算完成并清空缓存后,我的应用程序的内存减少到只有350-500 MB。我本来没想到在计算循环中使用自动释放池,但是可以尝试一下。

似乎操作系统正在缓存其创建的图像。但是,在计算完成之前,它不会清除缓存,因此直到那时它才能不受限制地增长。有什么办法可以防止这种情况发生?

1 个答案:

答案 0 :(得分:1)

请勿使用-bitmapDatamemcpy()复制图像。将一个图像绘制到另一个图像中。

我通常建议开发人员阅读10.6 AppKit release notes中的“ NSBitmapImageRep:CoreGraphics阻抗匹配和性能说明”部分:

  

NSBitmapImageRep:CoreGraphics阻抗匹配和性能说明

     

以上发布说明详细介绍了NSImage级别的核心更改,   雪豹。在   NSBitmapImageRep级别,也用于提高性能和改善阻抗   与CoreGraphics匹配。

     

NSImage是图像的相当抽象的表示形式。好漂亮啊   只是一个可以绘制的东西,尽管它不如NSView抽象   因为它不应表现出上下文的不同方面   除了质量决策外,它都被纳入其中。有点不透明   语句,但可以用一个示例说明:如果您绘制一个   按钮进入100x22区域与22x22区域,   按钮以拉伸其中间但不拉伸其端盖。图片不应   这样行事(如果尝试,可能会中断!)。一个图像   应始终线性且均匀缩放以填充其中的矩形   绘制,尽管它可能会选择表示形式并进行优化   该地区的质量。同样,所有图片表示形式   NSImage应该表示相同的图形。不要完全打包   代表不同的图片。

     

离题,NSBitmapImageRep更具体了   宾语。 NSImage没有像素,NSBitmapImageRep没有像素。一个   NSBitmapImageRep是一块数据以及像素格式   信息和色彩空间信息,使我们能够解释   数据作为颜色值的矩形数组。

     

与CGImage几乎相同。在《雪豹》中   NSBitmapImageRep由CGImageRef本地支持,而不是   直接的数据块。 CGImageRef实际上具有数据块。   在Leopard中,从CGImage实例化的NSBitmapImageRep将   解压缩并可能处理数据(从   位图文件格式),在SnowLeopard中,我们尝试将其挂在   原始的CGImage。

     

这会对性能产生一些影响。多数都很好!你应该看到   更少的位图数据作为CGImages的编码和解码。如果你   从JPEG文件初始化NSImage,然后将其绘制为PDF,   应获得与原始JPEG相同文件大小的PDF。在   豹,您会看到一个PDF格式,大小与解压缩后的图像相同。服用   另一个例子是CoreGraphics缓存,包括上传到   图形卡,绑定到CGImage实例,所以更多相同   实例可以更好地使用。

     

但是:在某种程度上,快速的操作   NSBitmapImageRep已更改。 CGImage是不可变的,   NSBitmapImageRep是。如果您在内部修改NSBitmapImageRep   可能必须将数据从CGImage中复制出来,   更改,并将其重新打包为新的CGImage。所以基本上   NSBitmapImageRep速度很快,查看或修改其像素数据是   不。在Leopard中确实如此,但现在更是如此。

     

上面的步骤确实很懒惰:如果您做的事情导致   NSBitmapImageRep从其支持CGImageRef复制数据(如调用   bitmapData),则该位图将不会将数据重新打包为CGImageRef,直到   它被绘制,或者直到由于其他原因需要CGImage为止。所以,   当然访问数据不是世界末日,而是   在某些情况下应该做的正确的事,但总的来说您应该   考虑绘图。如果您想与他人合作   像素,请改用CoreImage-这就是我们的API   真正用于像素处理的系统。

     

这与安全性相吻合。我们的SnowLeopard遇到的问题   变化是应用程序更喜欢对位图格式进行硬编码。一个   NSBitmapImageRep可以是每个像素8、32或128位,它可以是   浮点数与否,可以预乘或不乘,或者   可能没有alpha通道等。   位图属性,例如-bitmapFormat。不幸的是,如果有人要   从NSBitmapImageRep实例中提取bitmapData,他们   通常只调用bitmapData,将数据视为(例如)预乘   每像素RGBA为32位,如果它可以正常工作,则称之为一天。

     

现在,NSBitmapImageRep处理的数据不如使用的多   到,您可能会发现的随机位图图像代表可能有所不同   格式比以前。某些硬编码格式可能是   错误的。

     

不是解决方案是尝试处理完整范围的格式   NSBitmapImageRep的数据可能在其中,太难了。   而是将位图绘制成您知道的格式,然后   看看那个。

     

看起来像这样:

NSBItmapImageRep *bitmapIGotFromAPIThatDidNotSpecifyFormat;
NSBitmapImageRep *bitmapWhoseFormatIKnow = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:width pixelsHigh:height
                                                  bitsPerSample:bps samplesPerPixel:spp hasAlpha:alpha isPlanar:isPlanar
                                                  colorSpaceName:colorSpaceName bitmapFormat:bitmapFormat bytesPerRow:rowBytes
                                                  bitsPerPixel:pixelBits];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmapWhoseFormatIKnow]];
[bitmapIGotFromAPIThatDidNotSpecifyFormat draw];
[NSGraphicsContext restoreGraphicsState];
unsigned char *bitmapDataIUnderstand = [bitmapWhoseFormatIKnow bitmapData];
     

这不会产生比仅访问更多的数据副本   bitmapIGotFromAPIThatDidNotSpecifyFormat的bitmapData,因为   无论如何,都需要从支持CGImage中复制数据。也   请注意,这并不取决于源图形是位图。   这是一种以已知格式获取任何图形或仅   获得位图。与获取位图相比,这是一种更好的方法   例如,调用-TIFFRepresentation。也比   将焦点锁定在NSImage上并使用-[NSBitmapImageRep   initWithFocusedViewRect:]。

     

因此,总而言之:(1)绘制速度很快。不玩像素。 (2)如果   您认为需要使用像素,(a)考虑是否有办法   用绘图或(b)查看CoreImage。 (3)如果你仍然   要获取像素,绘制成您知道格式的位图   然后看看那些像素。

事实上,从上一节开始以类似的标题“ NSImage,CGImage和CoreGraphics阻抗匹配”开始阅读下一节是一个好主意。

顺便说一句,交换图像代表很有可能会起作用,但是您只是没有正确地同步它们。您必须显示代码以确保我们知道两次使用的位置。