创建位图的方法
//
// 摘要:
// 用指定的大小、像素格式和像素数据初始化 System.Drawing.Bitmap 类的新实例。
//
// 参数:
// width:
// 新 System.Drawing.Bitmap 的宽度(以像素为单位)。
//
// height:
// 新 System.Drawing.Bitmap 的高度(以像素为单位)。
//
// stride:
// 指定相邻扫描行开始处之间字节偏移量的整数。 这通常(但不一定)是以像素格式表示的字节数(例如,2 表示每像素 16 位)乘以位图的宽度。 传递给此参数的值必须为
// 4 的倍数。
//
// format:
// 新 System.Drawing.Bitmap 的像素格式。 这必须指定以 Format 开头的值。
//
// scan0:
// 指向包含像素数据的字节数组的指针。
//
// 异常:
// T:System.ArgumentException:
// 指定了名称不以 Format 开头的 System.Drawing.Imaging.PixelFormat 值。 例如,指定 System.Drawing.Imaging.PixelFormat.Gdi
// 将导致一个 System.ArgumentException,但 System.Drawing.Imaging.PixelFormat.Format48bppRgb
// 不会出现这种情况。
public Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0);
PixelFormat如何影响stride步幅的计算
-
PixelFormat.Format8bppIndexed:
- 每像素使用 1 个字节(8 位)表示颜色,通常用于索引颜色表的灰度或调色板图像。
stride
= (width
* 1) + (对齐补足),对齐至 4 字节边界。
-
PixelFormat.Format16bppRgb555 或 PixelFormat.Format16bppRgb565:
- 每像素使用 2 个字节(16 位)表示 RGB 颜色。
stride
= (width
* 2) + (对齐补足),对齐至 4 字节边界。
-
PixelFormat.Format24bppRgb 或 PixelFormat.Format32bppRgb:
- 每像素分别使用 3 个字节(24 位)或 4 个字节(32 位,额外字节通常为 0 或填充)表示 RGB 颜色。
stride
= (width
* 3) 或 (width
* 4) + (对齐补足),分别对齐至 4 字节边界。
-
PixelFormat.Format32bppArgb 或 PixelFormat.Format32bppPArgb:
- 每像素使用 4 个字节(32 位)表示 ARGB 颜色,其中 A 代表 Alpha 透明通道。
stride
= (width
* 4) + (对齐补足),对齐至 4 字节边界。
-
PixelFormat.Format48bppRgb 或 PixelFormat.Format64bppArgb:
- 每像素分别使用 6 个字节(48 位)或 8 个字节(64 位)表示高精度 RGB 或 ARGB 颜色。
stride
= (width
* 6) 或 (width
* 8) + (对齐补足),分别对齐至 4 字节边界。
注意点
-
确保
stride
符合要求: 如前所述,stride
必须是 4 字节的倍数。在计算时,如果直接按像素字节数乘以宽度得到的值不是 4 的倍数,则需要添加额外的填充字节,使其对齐至 4 字节边界。 -
内存对齐:stride 可能不是简单地 width * bytes_per_pixel,因为某些硬件和操作系统可能要求内存按特定边界对齐。例如,它可能是 4 的倍数,以确保内存访问效率。
-
正确计算
scan0
指针:scan0
参数是指向包含像素数据的字节数组的指针。当创建或操作位图时,需要确保该指针指向正确的位置,即首行像素数据的起始地址。这通常需要根据stride
和height
计算得出。 -
内存管理: 使用指针传递像素数据时,需要确保内存的有效性和生命周期。确保分配了足够的内存来存储整个位图数据,并在不再需要时正确释放内存,以避免内存泄漏。
-
兼容性与性能: 不同的
PixelFormat
可能会影响图像的兼容性和处理性能。例如,带有 Alpha 通道的格式(如Format32bppArgb
)提供了透明度信息,但可能不被所有软件或硬件完全支持;而某些特定格式(如 16 位 RGB)可能更适合某些图形硬件加速。 -
跨平台注意事项: 如果涉及跨平台操作,不同的操作系统或图形库可能对位图格式的支持有所差异。在设计和实现时,应考虑目标平台的兼容性需求。
-
数据访问一致性: 当直接操作底层像素数据时,要注意多线程环境下的数据同步问题,以防止数据竞争导致的图像损坏。
-
性能考虑:对于大型位图或频繁操作的位图,不恰当的
stride
设置可能会导致性能问题。尽量使stride
与硬件和操作系统的内存访问模式相匹配。
总结
在使用 Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0)
构造函数时,应仔细考虑 stride
与 PixelFormat
的关系,确保正确的内存布局和数据访问,同时注意相关的内存管理和性能因素。
举例
灰度图的Stride计算:
对于灰度图像,每个像素通常占用1字节(8位),表示从0到255的灰度值。假设图像的宽度为width
像素,如果没有特别的内存对齐要求,理论上 stride 就是 width
。但是,由于内存对齐的考虑,实际的 stride 可能会增加到最接近 width
的、大于等于 width
且能被4整除的数值。
彩色图像的Stride计算:
对于24位RGB图像,每个像素由3个字节组成(R、G、B各占1字节)。如果图像宽度不是4的倍数,内存对齐同样会导致stride比简单宽度乘以3要大。具体计算方法与灰度图类似,确保最终的stride是4的倍数。
实例:
-
灰度图示例:如果一个灰度图宽度为17像素,则实际 stride 不是17,而是20,因为20是比17大的、最接近17且能被4整除的数。
-
24位RGB图像示例:如果一个RGB图像宽度为17像素,每个像素3字节,则无填充情况下需要
17 * 3 = 51
字节。但为了内存对齐,stride将会是52,因为52是满足条件的最小4的倍数。
在实际编程中,你可以使用Bitmap
类的Stride
属性来获取或设置这个值,尤其是在使用LockBits
和UnlockBits
方法进行像素级操作时,正确计算和使用stride是非常重要的,以避免数据错位或访问越界的问题。
如果是PixelFormat.Format24bppRgb,如何计算stride步幅
代码如下:
//如果是PixelFormat.Format24bppRgb
int bytesPerPixel = 3; // 对于 Format24bppRgb
int width = ...; // 图像的实际宽度
width = (int)(width / 8) * 8;//((width + 3) / 4) * 4;
//方法1
int padding = (4 - (width * bytesPerPixel) % 4) % 4;
int stride = width * bytesPerPixel + padding;
stride = ((stride + 3) / 4) * 4;
//方法2
int stride = ((width * 3 + 3) / 4) * 4;
如果步幅不对,可能导致图像显示异常