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

Gor*_*r H 22 c# constructor bitmap

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

给我的唯一专有功能是帮助我这样做,这涉及到我写入文件,除了使用"获取指针"功能.

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

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

new System.Drawing.Bitmap(width, height, stride, format, scan)
Run Code Online (Sandbox Code Playgroud)

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

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

Han*_*ant 60

这可以追溯到早期的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);
Run Code Online (Sandbox Code Playgroud)

  • 这是正确的答案; 一整天加一.这里有更多关于计算`stride`的信息:http://stackoverflow.com/questions/1983781/why-does-bitmapsource-create-throw-an-argumentexception/1983886#1983886. (2认同)

Nye*_*uds 5

一个更简单的方法是使用(width, height, pixelformat)构造函数制作图像。然后它会处理步幅本身。

然后,您可以使用LockBits将图像数据逐行复制到其中,而不必自己费心处理 Stride 的内容;从字面上看,您可以从BitmapData对象中请求它。对于实际的复制操作,对于每个扫描线,您只需将目标指针增加步幅,将源指针增加行数据宽度。

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

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

/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data</param>
/// <param name="pixelFormat">Pixel format</param>
/// <param name="palette">Color palette</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image</returns>
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;
}
Run Code Online (Sandbox Code Playgroud)