C#中从位图提取RGB缓冲区与C++不同

3
我是一个有用的助手,可以为您翻译文本。
我正在使用一个第三方DLL,它的参数是RGB缓冲区。 我已经使用以下代码从Bitmap中读取RGB缓冲区:
private byte[] GetBGRValues(Bitmap bmp)
    {

        // Lock the bitmap's bits. 
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

        // Get the address of the first line.
        IntPtr ptr = bmpData.Scan0;
        // Declare an array to hold the bytes of the bitmap.
        int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
        byte[] rgbValues = new byte[bytes];


        // Copy the RGB values into the array.
        System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); 
        bmp.UnlockBits(bmpData);

        return rgbValues;
    }

问题在于生成的RGB缓冲区不正确。如果我使用正确的参数在IrfanView中打开此缓冲区,则生成的图像不正确(看起来像是被移位了)。
如果我使用C ++代码读取的缓冲区,则可以正常工作。
我注意到比我预期的* 多1个单位。(我知道.NET使用4字节对齐)。
问题是:为什么RGB缓冲区不正确?

为什么 RGB 缓冲区不正确?RGB 缓冲区是正确的。(我知道 .NET 使用 4 字节对齐)。并不是 .NET 使用 4 字节对齐,而是图像本身使用它。C++ 或 .NET 都提供相同的缓冲区。不要那么快就下结论。 - γηράσκω δ' αεί πολλά διδασκόμε
我提供给这个方法的位图是24bpp,宽度为897,高度为1281。为什么步幅值是2692而不是2691?如果我将生成的缓冲区保存到文件并在IrfanView中打开,图像是不正确的。为什么? - user1862876
你所访问的不仅仅是像素缓冲区,而是针对快速访问进行了优化的图像内存表示。这两者是不同的。正如你所观察到的,内存中的表示将具有对齐和类似优化,这意味着步幅可以大于图像宽度。换句话说,如果你之后尝试将该字节数组解释为一个897*1281个3字节像素集,你会遇到问题。 - Lasse V. Karlsen
如果您只想获取像素字节,请循环遍历每一行,从每一行的开头复制width3个字节到MemoryStream(或类似对象)中,并在下一次复制之前跳过“Stride”个字节。如果Stride > Width3,则忽略末尾的最后几个字节。这样可以得到与其他程序更相似的结果。 - Lasse V. Karlsen
3个回答

5
你应该注意 - 在一般情况下,你不能只用一个 Copy调用来简单复制图像。因为Stride包含了行的长度和填充,可能比行的长度要大。所以你需要仅拷贝每一行所需的字节,忽略填充字节,并且通过增加Stride来前进到下一行。
我猜这就是你代码中出现的问题: original and valid result - 原始图像和期望结果 invalid result - 没有考虑Stride导致的错误结果
这里是可运行的代码:
public static byte[] GetBGRValues(Bitmap bmp)
{
    var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);

    var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
    var imgBytes = bmp.Height * rowBytes;
    byte[] rgbValues = new byte[imgBytes];

    var ptr = bmpData.Scan0;
    for (var i = 0; i < bmp.Height; i++)
    {
        Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes);
        ptr += bmpData.Stride; // next row
    }

    bmp.UnlockBits(bmpData);

    return rgbValues;
}

更多细节您可以在这个答案中阅读:字节数组转图片

此外,这张图片也许可以帮助您理解Stride的作用:

行长度与步幅

当您从Bitmap中获取字节时,需要跳过右侧的白色区域。


有时候,一张图片胜过千言万语 :-) 最终,我终于明白了整个“步幅”概念! 附注:我现在使用AForge,它可以处理大部分这些事情。 - Xan-Kun Clark-Davis

0

我记得很多年前我曾经在做的一个库——颜色变化得非常奇怪。由于底层的 Microsoft 库在库内部反转了 RGB,我们不知情地尝试了一次转换,并发现了这个小宝石。

而且,在你的 C# 缓冲区中也别忘了 Alpha 通道。

虽然人们通常用 ARGB 的缩写来表示位图中的颜色通道,但是内存中的位图颜色通道的顺序是:蓝色、绿色、红色和 Alpha!http://softwarebydefault.com/2013/03/22/bitmap-swap-argb/


0

请确保注意顺序为B-G-R而不是R-G-B。 您可以尝试使用以下不安全的代码将值转换为uint。这样,您就可以将RGB转换为uint。

/// <summary>
///     Locks a Bitmap into system memory.
/// </summary>
public unsafe void LockBits()
{
    var width = this.Bitmap.Width;
    var height = this.Bitmap.Height;

    var imageLockMode = ImageLockMode.UserInputBuffer;

    // Setting imageLockMode
    imageLockMode = imageLockMode | ImageLockMode.ReadOnly;
    imageLockMode = imageLockMode | ImageLockMode.WriteOnly;

    // Save the bouunds
    this._bounds = new Rectangle(0, 0, width, height);

    // Create Pointer
    var someBuffer = new uint[width*height];
    // Pin someBuffer
    fixed (uint* buffer = someBuffer) //pin
    {
        // Create new bitmap data.
        var temporaryData = new BitmapData
        {
            Width = width,
            Height = height,
            PixelFormat = PixelFormat.Format32bppArgb,
            Stride = width*4,
            Scan0 = (IntPtr) buffer
        };

        // Get the data
        this.BitmapData = this.Bitmap.LockBits(this._bounds, imageLockMode,     PixelFormat.Format32bppArgb,
        temporaryData);
        // Set values
        this.Buffer = someBuffer;
   }
}

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