在C#中,Bitmap的LockBits()方法是否支持不同格式?

5
我最近发现了一个与在C#中将位图格式转换为24位RGB有关的问题的答案:更快地复制图像以更改其PixelFormat,其中所选答案说明如下:

另一种方法是锁定没有Alpha通道的比特,并将内存复制到新的位图中

以下代码作为示例提供(简化版本):

public static Bitmap RemoveAlphaChannel(Bitmap bitmap) {
    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    Bitmap bitmapDest = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb);
    BitmapData data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    BitmapData dataDest = bitmapDest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
    int bitmapSize = data.Stride * data.Height;
    Buffer.MemoryCopy(dataDest.Scan0, data.Scan0, bitmapSize, bitmapSize);
    bitmap.UnlockBits(data);
    bitmapDest.UnlockBits(dataDest);
    return bitmapDest;
}

让我们使用这个函数将一个32位ARGB图像转换为24位RGB。我有以下几个问题:
  1. 锁定32位位图作为24位的工作原理是什么?为什么允许这样做?
  2. 现在,原始位图中每个像素的RGB分量是连续的吗?Alpha分量去哪了?步幅长度发生了什么变化?
  3. 将位图的位锁定为不同格式是否会在底层创建位图的副本?
1个回答

9

这只是GDI+提供的一种方便。虽然它很重要,但使用特定算法更方便和更快速的访问像素格式通常更加方便。

32bppPArgb格式最适合快速将位图渲染到屏幕上,它与视频帧缓冲区格式兼容,因此无需转换。典型的渲染速度比任何其他像素格式快x10。但在代码中操纵PArgb非常麻烦。 R、G和B通道值由alpha值校正,您必须再次除以它才能恢复原始RGB值。请求使用Argb格式可以一举解决这个问题。

同样,24bppRgb格式很麻烦,必须使用byte*来访问像素通道。这需要每个像素进行3次内存访问,大大降低了代码的速度。请求32bppArgb允许使用int*,速度更快,并允许您忽略步幅。

这些转换并不特别复杂。但它们不是免费的,GDI+必须做出工作来分配临时存储并转换像素值。我在我的笨重的笔记本电脑上对其进行了分析,使用1000 x 1000位图和ImageLockMode.ReadWrite:

32bppArgb => 32bppArgb  : 0.002 msec
32bppPArgb => 32bppArgb : 5.6 msec
32bppArgb => 24bppRgb   : 5.6 msec
32bppPArgb => 24bppRgb  : 5.6 msec

我使用memcpy()函数测试了你的RemoveAlphaChannel()方法,在同一个32bppArgb 1000 x 1000位图上进行像素复制。我得到了2.8毫秒的所需单个ImageLockMode.ReadOnly像素转换时间和2.8毫秒的复制时间。这比使用Graphics.DrawImage()以GDI+提供的方式执行要快大约两倍,后者需要9.3毫秒。


1
有趣的是,当在不同格式上锁定时,GDI执行了一次复制。如果ImageLockMode为ReadWrite,那么解锁时新复制的位图是否必须(秘密地)复制回原始格式以保留在锁定期间所做的任何更改? - MathuSum Mut
1
是的。请注意,在您的特定情况下,由于目标位图已经具有正确的像素格式,因此不会发生这种情况。 - Hans Passant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接