不要使用-bitmapData
andmemcpy()
来复制图像。将一个图像绘制到另一个图像中。
我经常建议开发人员阅读10.6 AppKit 发行说明中的“NSBitmapImageRep:CoreGraphics 阻抗匹配和性能说明”部分:
NSBitmapImageRep:CoreGraphics 阻抗匹配和性能说明
上面的发行说明详细介绍了 SnowLeopard 的 NSImage 级别的核心更改。在 NSBitmapImageRep 级别也有很大的变化,同样是为了提高性能和改善与 CoreGraphics 的阻抗匹配。
NSImage 是一个相当抽象的图像表示。它几乎只是一个可以绘制的东西,尽管它不如 NSView 抽象,因为它不应该表现出不同的基于上下文的不同方面,除了质量决策。这是一种不透明的陈述,但可以用一个例子来说明:如果你将一个按钮绘制到一个 100x22 区域而不是 22x22 区域,你可以期望按钮拉伸它的中间而不是它的端盖。图像不应该那样表现(如果你尝试它,你可能会破坏!)。图像应始终线性且均匀地缩放以填充其绘制的矩形,尽管它可以选择表示等来优化该区域的质量。类似地,一个 NSImage 中的所有图像表示应该表示相同的图形。大学教师'
离题了,一个 NSBitmapImageRep 是一个更具体的对象。一个 NSImage 没有像素,一个 NSBitmapImageRep 有。NSBitmapImageRep 是一大块数据以及像素格式信息和颜色空间信息,它允许我们将数据解释为颜色值的矩形数组。
这与 CGImage 几乎相同。在 SnowLeopard 中,NSBitmapImageRep 本身由 CGImageRef 支持,而不是直接支持一大块数据。CGImageRef 确实有大量数据。在 Leopard 中,从 CGImage 实例化的 NSBitmapImageRep 将解包并可能处理数据(在从位图文件格式读取时发生),在 SnowLeopard 中,我们努力只挂在原始 CGImage 上。
这会产生一些性能后果。大多数都很好!您应该看到较少的位图数据编码和解码为 CGImage。如果您从 JPEG 文件初始化 NSImage,然后在 PDF 中绘制它,您应该得到与原始 JPEG 文件大小相同的 PDF。在 Leopard 中,您会看到解压缩图像大小的 PDF。再举一个例子,CoreGraphics 缓存,包括上传到显卡的缓存,是与 CGImage 实例绑定的,所以同一个实例越多越好。
但是:在某种程度上,使用 NSBitmapImageRep 快速的操作已经改变。CGImages 是不可变的,NSBitmapImageRep 是。如果你修改一个 NSBitmapImageRep,在内部它可能不得不从 CGImage 中复制数据,合并你的更改,并将其重新打包为一个新的 CGImage。因此,基本上,绘制 NSBitmapImageRep 很快,查看或修改其像素数据则不然。这在 Leopard 中是正确的,但现在更是如此。
上面的步骤确实是懒惰地发生的:如果你做了一些导致 NSBitmapImageRep 从它的支持 CGImageRef 复制数据的事情(比如调用 bitmapData),位图将不会将数据重新打包为 CGImageRef,直到它被绘制或者直到它需要一个 CGImage其他原因。因此,访问数据当然不是世界末日,在某些情况下是正确的做法,但总的来说,您应该考虑绘图。如果您认为您想使用像素,请查看 CoreImage - 这是我们系统中真正用于像素处理的 API。
这与安全不谋而合。我们在 SnowLeopard 更改中看到的一个问题是应用程序非常喜欢硬编码位图格式。一个 NSBitmapImageRep 可以是每像素 8、32 或 128 位,它可以是浮点数也可以不是,它可以是预乘或不预乘,它可能有也可能没有 alpha 通道等。这些方面是用位图属性指定的,比如-位图格式。不幸的是,如果有人想从 NSBitmapImageRep 实例中提取 bitmapData,他们通常只调用 bitmapData,将数据视为(例如)预乘 32 位每像素 RGBA,如果它似乎有效,就结束吧。
既然 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 阻抗匹配”——然后通读到后面的部分。
顺便说一句,交换图像代表很有可能会起作用,但您只是没有正确同步它们。您必须显示使用两个代表的代码,以便我们确定。