6

在 .NET 中,您通常使用 Bitmap 类来存储图像。要快速访问它,您需要调用lock() 和 unlock()将位图的内容复制到内存中。那么,这是否意味着位图在锁定之前没有存储在打包的内存数组中?

究竟需要锁定什么?即为什么平台不能简单地返回指向位图第一个像素的指针,让您直接访问像素?(除了“不安全”的内存访问注意事项)

一些可能的原因:

  • 位图以原始压缩形式存储以节省内存(PNG、JPEG 等)
  • 位图以 24-bpp 格式存储,因此访问速度比 32-bpp 图像慢
  • 位图未存储在打包内存数组中,并且是碎片化的,因此无法快速读取/写入
  • 位图以未公开的方式存储,平台不希望您访问实际的位图数据内存 - 迫使您使用 lock() 在内存中创建它的副本
4

3 回答 3

12

位图以惰性方式读取。实际使用的算法在很大程度上取决于图像格式,.bmp 文件很容易,.jpeg 文件就不那么容易了。在底层,GDI+ 创建一个内存映射文件,在必要时利用操作系统的按需分页虚拟内存映射功能将文件数据映射到 RAM。正是这个 MMF 臭名昭著地在文件上创建了一个锁,并导致您在尝试将图像保存回具有相同名称的文件时遇到异常。

Bitmap.LockBits() 创建一个单独的缓冲区,将像素数据从 MMF 映射到内存中具有请求的像素格式的区域。现在,您拥有了定义明确的格式的数据,该格式独立于文件中的格式。调用 UnlockBits() 将修改后的数据写回(如果有)。文件中的数据如何与修改后的像素重新组合并没有具体说明,并且可能很大程度上取决于编解码器。

当您在其他方法中使用位图时,会发生这种完全相同的锁定,这是看不见的。就像使用 Graphics.DrawImage() 绘制图像一样。当你使用 Bitmap.GetPixel() 时臭名昭著。正如您现在可以猜到的那样,在锁定调用时转换像素数据涉及相当多的开销,原因是 GetPixel() 如此缓慢,因为您对位图中的每个像素而不是只执行一次,因为DrawImage 和 LockBits() 可以。

还应该清楚的是,开销在很大程度上取决于图像文件格式、即时解码像素数据的费用、解码器完成的缓存量、文件的像素格式与您在 LockBits() 中要求的像素格式。这里发生的事情太多了,无法做出可预测的性能猜测,您必须对其进行分析。

于 2012-11-21T15:36:30.710 回答
3

位图以未公开的方式存储,平台不希望您访问实际的位图数据内存 - 迫使您使用 lock() 在内存中创建它的副本

这个,我想。位图存储在 GDI 中,每次调用GetPixel()都会变成对GdipBitmapGetPixel()in的调用gdiplus.dll

Bitmap.LockBits(),在 GDI 中调用Bitmap::Lockbits,将立即返回 GDI 为给定图像保存的所有像素,作为托管BitmapData对象。

文档Bitmap::Lockbits说:

锁定该位图的一个矩形部分并提供一个临时缓冲区,您可以使用该缓冲区以指定格式读取或写入像素数据。当您调用 Bitmap::UnlockBits 时,您写入缓冲区的任何像素数据都会复制到 Bitmap 对象。

在 GDI 文档中,我找不到任何其他方式来访问“原始”位图,所以我认为这是因为 GDI 的设计方式(可能是因为 GDI 将位图存储在(视频?)内存中的设备依赖方式?) ,因此如果LockBits对“原始”位图数据进行操作,则必须调用。

于 2012-11-21T14:34:49.527 回答
1

为什么平台不能简单地返回指向位图第一个像素的指针并让您直接访问像素?

只是一个想法:所有图像数据(“像素”)都可以存储在视频卡的内存(而不是 CPU 的内存)上。因此,无法从 CPU/应用程序直接访问图像数据。因此,应该有一种特殊的方式来通过请求从 CPU 获取对图像数据的访问。像一个LockBits/UnockBits.

于 2012-11-21T14:48:12.043 回答