c# - 从像素数据的字节数组创建位图

标签 c# bitmap bitmapdata

本题是关于如何读写、分配和管理Bitmap的像素数据。

这是一个如何为像素数据分配字节数组(托管内存)并使用它创建位图的示例:

Size size = new Size(800, 600);
PixelFormat pxFormat = PixelFormat.Format8bppIndexed;
//Get the stride, in this case it will have the same length of the width.
//Because the image Pixel format is 1 Byte/pixel.
//Usually stride = "ByterPerPixel"*Width

//但并不总是如此。 More info at bobpowell .

int stride = GetStride(size.Width, pxFormat);
byte[] data = new byte[stride * size.Height];
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
Bitmap bmp = new Bitmap(size.Width, size.Height, stride,
             pxFormat, handle.AddrOfPinnedObject());

//After doing your stuff, free the Bitmap and unpin the array.
bmp.Dispose();
handle.Free();

public static int GetStride(int width, PixelFormat pxFormat)
{
    //float bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
    int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF;
    //Number of bits used to store the image data per line (only the valid data)
    int validBitsPerLine = width * bitsPerPixel;
    //4 bytes for every int32 (32 bits)
    int stride = ((validBitsPerLine + 31) / 32) * 4;
    return stride;
}

我以为 Bitmap 会复制数组数据,但它实际上指向相同的数据。你能看到吗:

Color c;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color before: " + c.ToString());
//Prints: Color before: Color [A=255, R=0, G=0, B=0]
data[0] = 255;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color after: " + c.ToString());
//Prints: Color after: Color [A=255, R=255, G=255, B=255]

问题:

  1. 从 byte[] 数组(托管内存)和 free() GCHandle 创建位图是否安全?如果它不安全,我将需要保留一个固定数组,这对 GC/性能有多糟糕?

  2. 更改数据是否安全(例如:data[0] = 255;)?

  3. Scan0 的地址可以被 GC 改变吗?我的意思是,我从锁定的位图中获取 Scan0,然后将其解锁,一段时间后再次锁定,Scan0 会有所不同吗?

  4. LockBits 方法中 ImageLockMode.UserInputBuffer 的用途是什么?很难找到相关信息! MSDN没有解释清楚!

编辑 1:一些跟进

  1. 您需要将其固定。它会减慢GC速度吗? I've asked it here .这取决于图像的数量及其大小。没有人给我一个量化的答案。它接缝很难确定。 您还可以使用 Marshal 分配内存或使用 Bitmap 分配的非托管内存。

  2. 我已经使用两个线程进行了大量测试。只要位图被锁定就可以了。如果位图被解锁,那就不安全了! My related post about read/write directly to Scan0 . Boing 的回答“我已经在上面解释了为什么你很幸运能够在锁外使用 scan0。因为你使用原始的 bmp PixelFormat 并且 GDI 在这种情况下经过优化以给你指针而不是副本。这个指针是有效的直到操作系统决定释放它。唯一的保证是在 LockBits 和 UnLockBits 之间。句号。”

  3. 是的,它可能会发生,但是 GC 对大内存区域的处理方式不同,它移动/释放这个大对象的频率较低。所以 GC 移动这个数组可能需要一段时间。 From MSDN :“任何大于或等于 85,000 字节 的分配都在 大对象堆 (LOH) 上进行”...“LOH 仅在第 2 代收集期间收集”。 .NET 4.5 对 LOH 进行了改进。

  4. @Boing 已经回答了这个问题。但我要承认。我没有完全理解它。因此,如果 Boing 或其他人可以请澄清一下,我将很高兴。顺便说一句,为什么我不能 directly read/write to Sca0 without locking ? => 您不应该直接写入 Scan0,因为 Scan0 指向由非托管内存(在 GDI 内部)制作的位图数据的副本。解锁后,这 block 内存可以重新分配给其他东西,Scan0 是否指向实际的位图数据不再确定。这可以重现,让 Scan0 处于锁定状态,解锁,并在解锁的位图中进行一些旋转移动。一段时间后,Scan0 将指向一个无效区域,当您尝试读取/写入其内存位置时将遇到异常。

最佳答案

  1. 如果您编码复制数据而不是设置 scan0(直接或通过 BitMap() 的重载),那么它是安全的。您不想固定托管对象,这会限制垃圾收集器。
  2. 如果你复制,绝对安全。
  3. 输入数组是托管的,可以由 GC 移动,scan0 是一个非托管指针,如果数组移动,它就会过时。 Bitmap 对象本身是受管理的,但通过句柄在 Windows 中设置 scan0 指针。
  4. ImageLockMode.UserInputBuffer 是什么?显然它可以传递给 LockBits,也许它告诉 Bitmap() 复制输入数组数据。

从数组创建灰度位图的示例代码:

    var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);

    ColorPalette ncp = b.Palette;
    for (int i = 0; i < 256; i++)
        ncp.Entries[i] = Color.FromArgb(255, i, i, i);
    b.Palette = ncp;

    var BoundsRect = new Rectangle(0, 0, Width, Height);
    BitmapData bmpData = b.LockBits(BoundsRect,
                                    ImageLockMode.WriteOnly,
                                    b.PixelFormat);

    IntPtr ptr = bmpData.Scan0;

    int bytes = bmpData.Stride*b.Height;
    var rgbValues = new byte[bytes];

    // fill in rgbValues, e.g. with a for loop over an input array

    Marshal.Copy(rgbValues, 0, ptr, bytes);
    b.UnlockBits(bmpData);
    return b;

关于c# - 从像素数据的字节数组创建位图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6782489/

相关文章:

c# - 使用 webclient DownloadFileAsync 多个文件

c# - SQL Server datetime 和 datetimeoffset 的等效 C# 数据类型是什么?

android - 在不损失图像质量的情况下缩放图像不起作用

actionscript-3 - AS3 将 ARGB 值的 ByteArray 转换为 RGB uint

actionscript-3 - ByteArray到BitmapData AS3

C# 使用 RSA DER 格式公钥解密文件

android - 为什么 BitmapFactory.decodeStream 返回 null?

c# - 将黑白位图转换为 boolean 数组

cocoa - 在 Cocoa 中操作 NSImage 的像素

c# - 如何使 TextBlock 在 GridViewColumn 中居中? (包括示例 xaml)