c# Bitmap.LockBits为什么可以改变PixelFormat?
首先看一下微软的官方文档对此方法的介绍
public System.Drawing.Imaging.BitmapData LockBits (System.Drawing.Rectangle rect,
System.Drawing.Imaging.ImageLockMode flags,
System.Drawing.Imaging.PixelFormat format);
Parameters
- rect:A Rectangle structure that specifies the portion of the Bitmap to lock.
- flags :An ImageLockMode enumeration that specifies the access level (read/write) for the Bitmap.
- format :A PixelFormat enumeration that specifies the data format of this Bitmap.
- 先看format参数:一个 PixelFormat 枚举,指定此 Bitmap 的数据格式。
- 乍一看觉得第三个参数应该是此Bitmap本身的PixelFormat,但仔细一想如果只能是这一种,那这个参数完全可以取消,因为若方法内部实现需要此参数的话,完全可以通过直接访问Bitmap的PixelFormat属性得到,所以第三个参数能改变也就可以理解了。但究竟能换成哪种PixelFormat 枚举呢?
- 再看一下此方法可能抛出的异常:
- ArgumentException:PixelFormat 不是特定的每像素位数值 - 或 - 为位图传入了错误的 PixelFormat。
- Exception:此操作失败。
- 什么叫每像素位数值(Bpp, bits per Pixel)?
- 请看PixelFormat枚举:
// // 摘要: // 指定图像中的每个像素的颜色数据格式。 public enum PixelFormat { // // 摘要: // 像素格式未定义。 Undefined = 0, // // 摘要: // 指定没有像素格式。 DontCare = 0, // // 摘要: // 此枚举的最大值。 Max = 15, // // 摘要: // 像素数据包含颜色索引值,这意味着这些值是在系统颜色表中,而不是各个颜色值的颜色的索引。 Indexed = 65536, // // 摘要: // 像素数据包含 GDI 颜色。 Gdi = 131072, // // 摘要: // 指定的格式是 16 位 / 像素;5 位用于红色、 绿色和蓝色组件。 不使用剩余的 1 位。 Format16bppRgb555 = 135173, // // 摘要: // 指定的格式是 16 位 / 像素;5 位用于红色组件、 6 位用于绿色组件和 5 位用于蓝色分量。 Format16bppRgb565 = 135174, // // 摘要: // 指定的格式是 24 位 / 像素;8 位用于红色、 绿色和蓝色组件。 Format24bppRgb = 137224, // // 摘要: // 指定的格式是 32 位 / 像素;8 位用于红色、 绿色和蓝色组件。 不使用剩余的 8 位。 Format32bppRgb = 139273, // // 摘要: // 指定的像素格式是每像素 1 位,它使用索引的颜色。 因此,颜色表中它有两种颜色。 Format1bppIndexed = 196865, // // 摘要: // 指定的格式是 4 位 / 像素,编制索引。 Format4bppIndexed = 197634, // // 摘要: // 指定的格式是 8 位 / 像素,编制索引。 因此,颜色表中它有 256 种颜色。 Format8bppIndexed = 198659, // // 摘要: // 像素数据包含不自左乘的 alpha 值。 Alpha = 262144, // // 摘要: // 像素格式是 16 位 / 像素。 颜色信息指定 32768 所属 5 位的红色、 5 位的颜色为绿色、 5 位为蓝色,和 1 的位是 alpha。 Format16bppArgb1555 = 397319, // // 摘要: // 像素格式包含预乘 alpha 值。 PAlpha = 524288, // // 摘要: // 指定的格式是 32 位 / 像素;8 位用于 alpha、 红色、 绿色和蓝色组件。 红色、 绿色和蓝色组件是根据 alpha 分量自左乘。 Format32bppPArgb = 925707, // // 摘要: // 保留。 Extended = 1048576, // // 摘要: // 像素格式是 16 位 / 像素。 颜色信息指定 65536 灰度梯度。 Format16bppGrayScale = 1052676, // // 摘要: // 指定的格式是 48 位 / 像素;为红色、 绿色和蓝色组件使用 16 位。 Format48bppRgb = 1060876, // // 摘要: // 指定的格式是每个像素; 64 位alpha、 红色、 绿色和蓝色组件使用 16 位。 红色、 绿色和蓝色组件进行自左乘的 alpha 分量根据。 Format64bppPArgb = 1851406, // // 摘要: // 32 位 / 像素默认像素格式。 此格式指定 24 位颜色深度和一个 8 位 alpha 通道。 Canonical = 2097152, // // 摘要: // 指定的格式是 32 位 / 像素;8 位用于 alpha、 红色、 绿色和蓝色组件。 Format32bppArgb = 2498570, // // 摘要: // 指定的格式是每个像素; 64 位alpha、 红色、 绿色和蓝色组件使用 16 位。 Format64bppArgb = 3424269 }
经过试验发现,只要PixelFormat中带有bpp的 (Format16bppGrayScale = 1052676 除外),都可以相互替换。
那么问题来了:替换PixelFormat到底是怎么实现的,有没有临时内存拷贝?
虽然心里已经有一丝答案,但作为菜鸟还是要找一下有没有相似的问题来固定自己的想法。
于是经过一番搜索,终于在stackoverflow(其实是第一时间到这里找的,stackoverflow太强大了)找到了想要的:LockBits() of Bitmap as different format in C#?
有兴趣的可以看一下大佬的解答...
This is simply a convenience provided by GDI+. It matters, it is often much more convenient and faster to access the pixels with a format that works well with the particular algorithm you want to apply.
The 32bppPArgb format is best to get the bitmap rendered to the screen as fast as possible, it is compatible with the video frame buffer format so no conversion is required. Typical render speed is x10 faster than any other pixel format. But PArgb is very awkward to manipulate in code. The R, G and B channel values are corrected by the alpha value, you'd have to divide it out again to recover the original RGB values. Asking for an Argb format solves that in one fell swoop.
Likewise, the 24bppRgb format is awkward, you have to use a byte* to access the pixel channels. That requires 3 memory accesses per pixel, slows down the code a great deal. Asking for 32bppArgb permits using an int*, much faster, and allows you to ignore stride.
Nothing particularly complicated about these conversions. But they are not for free, GDI+ has to do the work to allocate temporary storage and convert the pixel values back and forth. I profiled it on my pokey laptop, using a 1000 x 1000 bitmap and ImageLockMode.ReadWrite:
32bppArgb => 32bppArgb : 0.002 msec
32bppPArgb => 32bppArgb : 5.6 msec
32bppArgb => 24bppRgb : 5.6 msec
32bppPArgb => 24bppRgb : 5.6 msec
I measured perf on your RemoveAlphaChannel() method, using memcpy() to do the copy on the same 32bppArgb 1000 x 1000 bitmap. I got 2.8 msec for the required single ImageLockMode.ReadOnly pixel conversion and 2.8 msec for the copy. About twice as fast as doing it the GDI+ provided way with Graphics.DrawImage() which took 9.3 msec.
简单理解:如果不存在格式替换,那就不需要临时内存拷贝;否则就需要拷贝,GDI+有特定算法会快速方便地帮你完成;另外要把Bitmap渲染到屏幕, 32bppPArgb 是最快的,因为32位可以每次读取四字节(int *),一次读取一个像素;而24位每次读取一个字节(byte*),读取一个像素需要三次。
这样第一个和第二个参数就很好理解了:
由于可能需要进行临时内存拷贝,所以需要制定拷贝的大小,也就是选中的矩形区域;
如果是只读的,临时内存就不需要再拷贝回原来内存中;如果是读写方式的,临时内存就需要再拷贝回原内存中(如果对应像素相同也不需要拷贝);
所以选对参数对提升性能也是很有帮助。

浙公网安备 33010602011771号