为什么System.Drawing.Bitmap构造函数中的“stride”必须是4的倍数?

我正在编写一个应用程序,要求我采用专有的位图格式(MVTec Halcon HImage)并将其转换为C#中的System.Drawing.Bitmap。

除了使用“获取指针”function外,我给你的唯一专有function是帮我写文件。

这个function很棒,它给了我一个指向像素数据,宽度,高度和图像类型的指针。

我的问题是当我使用构造函数创建System.Drawing.Bitmap时:

new System.Drawing.Bitmap(width, height, stride, format, scan) 

我需要指定一个4的倍数的“步幅”。这可能是一个问题,因为我不确定我的函数将被命中的大小位图。 假设我最终得到一个111×111像素的位图,除了在我的图像中添加一个伪列或减去3列之外,我无法运行此function。

有没有办法可以绕过这个限制?

这可以追溯到早期的CPU设计。 压缩位图位的最快方法是从扫描线的开始一次读取32位。 当扫描线的第一个字节在32位地址边界上对齐时,这种方法效果最佳。 换句话说,地址是4的倍数。在早期的CPU上,如果第一个字节未对齐,则需要额外的CPU周期才能从RAM中读取两个32位字,并将字节混洗以创建32位值。 确保每条扫描线从对齐的地址开始(如果步幅是4的倍数,则自动)避免这种情况。

这在现代CPU上不再是真正的问题,现在与高速缓存行边界的对齐更为重要。 尽管如此,由于appcompat的原因,对于步幅的4个要求的倍数仍然存在。

顺便说一下,您可以通过以下方式轻松计算格式和宽度的步幅:

  int bitsPerPixel = ((int)format & 0xff00) >> 8; int bytesPerPixel = (bitsPerPixel + 7) / 8; int stride = 4 * ((width * bytesPerPixel + 3) / 4); 

正如Jake之前所说,你通过找到每个像素的字节数来计算步幅(16位为2位,32位为4位),然后将其乘以宽度。 因此,如果您的宽度为111,而32位图像的宽度为444,则为4的倍数。

但是,让我们说一分钟你有一个24位图像。 24位等于3个字节,因此对于111像素宽度,您将获得333作为步幅。 显然,这不是4的倍数。所以你想要舍入到336(4的下一个最高倍数)。 即使你有一些额外的东西,这个未使用的空间也不足以在大多数应用程序中产生很大的不同。

不幸的是,没有办法绕过这个限制(除非你总是使用32位或64位的想象,它们总是4的倍数。

记住stridewidth不同。 您可以拥有每行具有111(8位)像素的图像,但每行存储在112字节的存储器中。

这样做是为了有效利用内存,正如@Ian所说,它将数据存储在int32

因为它使用int32来存储每个像素。

 Sizeof(int32) = 4 

但不要担心,当图像从内存保存到文件时,它将使用最有效的内存使用。 在内部,它使用每像素24位(8位红色,8位绿色和8位蓝色),并使最后8位冗余。

更简单的方法是使用(width, height, pixelformat)构造函数生成图像。 然后它会照顾步幅本身。

然后,您可以使用LockBits将图像数据复制到其中,而无需自己打扰Stride的内容; 你可以直接从BitmapData对象请求它。 对于实际的复制操作,对于每个扫描线,只需通过步幅增加目标指针,将源指针增加到行数据宽度。

这是一个以字节数组forms获取图像数据的示例。 如果这是完全紧凑的数据,您的输入步幅通常只是图像宽度乘以每个像素的字节数。 如果是8位调色板数据,则只是宽度。

如果从图像对象中提取图像数据,则应该以完全相同的方式从该提取过程中存储原始步幅,方法是将其从BitmapData对象中取出。

 ///  /// Creates a bitmap based on data, width, height, stride and pixel format. ///  /// Byte array of raw source data /// Width of the image /// Height of the image /// Scanline length inside the data /// Pixel format /// Color palette /// Default color to fill in on the palette if the given colors don't fully fill it. /// The new image public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor) { Bitmap newImage = new Bitmap(width, height, pixelFormat); BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat); Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8; // Compensate for possible negative stride on BMP format. Boolean isFlipped = stride < 0; stride = Math.Abs(stride); // Cache these to avoid unnecessary getter calls. Int32 targetStride = targetData.Stride; Int64 scan0 = targetData.Scan0.ToInt64(); for (Int32 y = 0; y < height; y++) Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth); newImage.UnlockBits(targetData); // Fix negative stride on BMP format. if (isFlipped) newImage.RotateFlip(RotateFlipType.Rotate180FlipX); // For indexed images, set the palette. if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null) { ColorPalette pal = newImage.Palette; for (Int32 i = 0; i < pal.Entries.Length; i++) { if (i < palette.Length) pal.Entries[i] = palette[i]; else if (defaultColor.HasValue) pal.Entries[i] = defaultColor.Value; else break; } newImage.Palette = pal; } return newImage; } 

正确的代码:

 public static void GetStride(int width, PixelFormat format, ref int stride, ref int bytesPerPixel) { //int bitsPerPixel = ((int)format & 0xff00) >> 8; int bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format); bytesPerPixel = (bitsPerPixel + 7) / 8; stride = 4 * ((width * bytesPerPixel + 3) / 4); }