简体   繁体   English

从像素数据的字节数组创建 Bitmap

[英]Create Bitmap from a byte array of pixel data

This question is about how to read/write, allocate and manage the pixel data of a Bitmap.这个问题是关于如何读/写,分配和管理Bitmap的像素数据。

Here is an example of how to allocate a byte array (managed memory) for pixel data and creating a Bitmap using it:以下是如何为像素数据分配字节数组(托管内存)并使用它创建 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

//But it is not always true. //但这并不总是正确的。 More info at bobpowell . 更多信息在 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;
}

I thought that the Bitmap would make a copy of the array data, but it actually points to the same data.我以为 Bitmap 会复制数组数据,但它实际上指向的是相同的数据。 Was you can see:你可以看到:

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]

Questions:问题:

  1. Is it safe to do create a bitmap from a byte[] array (managed memory) and free() the GCHandle?从 byte[] 数组(托管内存)和 free() GCHandle 创建 bitmap 是否安全? If it is not safe, Ill need to keep a pinned array, how bad is that to GC/Performance?如果不安全,我需要保留一个固定数组,这对 GC/Performance 有多糟糕?

  2. Is it safe to change the data (ex: data[0] = 255;)?更改数据是否安全(例如:data[0] = 255;)?

  3. The address of a Scan0 can be changed by the GC? GC可以改变Scan0的地址吗? I mean, I get the Scan0 from a locked bitmap, then unlock it and after some time lock it again, the Scan0 can be different?我的意思是,我从锁定的 bitmap 获得 Scan0,然后将其解锁并在一段时间后再次锁定它,Scan0 可以不同吗?

  4. What is the purpose of ImageLockMode.UserInputBuffer in the LockBits method? LockBits 方法中 ImageLockMode.UserInputBuffer 的用途是什么? It is very hard to find info about that!很难找到这方面的信息! MSDN do not explain it clearly! MSDN不解释清楚!

EDIT 1: Some followup编辑1:一些后续行动

  1. You need to keep it pinned.你需要把它固定住。 Will it slow down the GC?它会减慢GC吗? I've asked it here . 我已经在这里问过了 It depends on the number of images and its sizes.这取决于图像的数量及其大小。 Nobody have gave me a quantitative answer.没有人给我一个定量的答案。 It seams that it is hard to determine.它似乎很难确定。 You can also alloc the memory using Marshal or use the unmanaged memory allocated by the Bitmap.您还可以使用 Marshal 分配 memory 或使用由 Bitmap 分配的非托管 memory。

  2. I've done a lot of test using two threads.我用两个线程做了很多测试。 As long as the Bitmap is locked it is ok.只要 Bitmap 被锁定就可以了。 If the Bitmap is unlock, than it is not safe!如果 Bitmap 被解锁,那就不安全了! My related post about read/write directly to Scan0 . 我有关直接读/写到 Scan0 的相关帖子 Boing's answer "I already explained above why you are lucky to be able to use scan0 outside the lock. Because you use the original bmp PixelFormat and that GDI is optimized in that case to give you the pointer and not a copy. This pointer is valid until the OS will decide to free it. The only time there is a guarantee is between LockBits and UnLockBits. Period." Boing 的回答“我已经在上面解释了为什么你很幸运能够在锁外使用 scan0。因为你使用原始的 bmp PixelFormat 并且在这种情况下 GDI 被优化为给你指针而不是副本。这个指针是有效的直到操作系统决定释放它。唯一有保证的时间是在 LockBits 和 UnLockBits 之间。期间。

  3. Yeah, it can happen, but large memory regions are treated different by the GC, it moves/frees this large object less frequently.是的,它可能会发生,但是大的 memory 区域被 GC 处理不同,它移动/释放这个大的 object 的频率较低。 So it can take a while to GC move this array.所以 GC 移动这个数组可能需要一段时间。 From MSDN : "Any allocation greater than or equal to 85,000 bytes goes on the large object heap (LOH) "... "LOH is only collected during a generation 2 collection". 来自 MSDN :“任何大于或等于85,000 bytes的分配都在large object heap (LOH)上进行”......“LOH 仅在第 2 代收集期间收集”。 .NET 4.5 have Improvements in LOH. .NET 4.5 在 LOH 方面有所改进。

  4. This question have been answered by @Boing. @Boing 已经回答了这个问题。 But I'm going to admit.但我要承认。 I did not fully understand it.我没有完全理解。 So if Boing or someone else could please clarify it , I would be glad.因此,如果Boing或其他人可以please clarify it ,我会很高兴。 By the way, Why I can't just directly read/write to Sca0 without locking ?顺便说一句,为什么我不能直接读/写 Sca0 而不加锁 => You should not write directly to Scan0 because Scan0 points to a copy of the Bitmap data made by the unmanaged memory (inside GDI). => 您不应直接写入 Scan0,因为 Scan0 指向由非托管 memory(在 GDI 内部)生成的 Bitmap 数据的副本。 After unlock, this memory can be reallocate to other stuff, its not certain anymore that Scan0 will point to the actual Bitmap data.解锁后,这个 memory 可以重新分配给其他东西,它不再确定 Scan0 将指向实际的 Bitmap 数据。 This can be reproduced getting the Scan0 in a lock, unlock, and do some rotate-flit in the unlocked bitmap.这可以通过将 Scan0 锁定、解锁并在解锁的 bitmap 中进行一些旋转飞行来重现。 After some time, Scan0 will point to an invalid region and you will get an exception when trying to read/write to its memory location.一段时间后,Scan0 将指向一个无效区域,当您尝试读取/写入其 memory 位置时会出现异常。

  1. Its safe if you marshal.copy data rather than setting scan0 (directly or via that overload of BitMap()).如果你 marshal.copy 数据而不是设置 scan0(直接或通过 BitMap() 的重载),它是安全的。 You don't want to keep managed objects pinned, this will constrain the garbage collector.您不想将托管对象保持固定,这将限制垃圾收集器。
  2. If you copy, perfectly safe.如果你复制,绝对安全。
  3. The input array is managed and can be moved by the GC, scan0 is an unmanaged pointer that would get out of date if the array moved.输入数组是托管的,可以由 GC 移动,scan0 是一个非托管指针,如果数组移动,它将过期。 The Bitmap object itself is managed but sets the scan0 pointer in Windows via a handle. Bitmap object 本身是受管理的,但通过句柄在 Windows 中设置 scan0 指针。
  4. ImageLockMode.UserInputBuffer is? ImageLockMode.UserInputBuffer 是什么? Apparently it can be passed to LockBits, maybe it tells Bitmap() to copy the input array data.显然它可以传递给 LockBits,也许它告诉 Bitmap() 复制输入数组数据。

Example code to create a greyscale bitmap from array:从数组创建灰度 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;

Concerning your question 4: The ImageLockMode.UserInputBuffer can give you the control of the allocating process of those huge amount of memory that could be referenced into a BitmapData object.关于您的问题 4: ImageLockMode.UserInputBuffer可以让您控制可以引用到BitmapData object 的大量 memory 的分配过程。

If you choose to create yourself the BitmapData object you can avoid a Marshall.Copy .如果您选择自己创建BitmapData object 您可以避免Marshall.Copy You will then have to use this flag in combinaison with another ImageLockMode .然后,您必须将此标志与另一个ImageLockMode使用。

Beware that it is a complicated business, specially concerning Stride and PixelFormat.请注意,这是一项复杂的业务,特别是关于 Stride 和 PixelFormat。

Here is an example that would get in one shot the content of 24bbp buffer onto a BitMap and then in one another shot read it back into another buffer into 48bbp.这是一个示例,它可以一次性将 24bbp 缓冲区的内容写入 BitMap,然后再将其读回另一个缓冲区,以 48bbp。

Size size = Image.Size;
Bitmap bitmap = Image;
// myPrewrittenBuff is allocated just like myReadingBuffer below (skipped for space sake)
// But with two differences: the buff would be byte [] (not ushort[]) and the Stride == 3 * size.Width (not 6 * ...) because we build a 24bpp not 48bpp
BitmapData writerBuff= bm.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb, myPrewrittenBuff);
// note here writerBuff and myPrewrittenBuff are the same reference
bitmap.UnlockBits(writerBuff);
// done. bitmap updated , no marshal needed to copy myPrewrittenBuff 

// Now lets read back the bitmap into another format...
BitmapData myReadingBuffer = new BitmapData();
ushort[] buff = new ushort[(3 * size.Width) * size.Height]; // ;Marshal.AllocHGlobal() if you want
GCHandle handle= GCHandle.Alloc(buff, GCHandleType.Pinned);
myReadingBuffer.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
myReadingBuffer.Height = size.Height;
myReadingBuffer.Width = size.Width;
myReadingBuffer.PixelFormat = PixelFormat.Format48bppRgb;
myReadingBuffer.Stride = 6 * size.Width;
// now read into that buff
BitmapData result = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb, myReadingBuffer);
if (object.ReferenceEquals(result, myReadingBuffer)) {
    // Note: we pass here
    // and buff is filled
}
bitmap.UnlockBits(result);
handle.Free();
// use buff at will...

If you use ILSpy you'll see that this method link to GDI+ and those methods helps are more complete.如果您使用 ILSpy,您会看到此方法链接到GDI+ ,并且这些方法的帮助更完整。

You may increase performance by using your own memory scheme, but beware that Stride may need to have some alignment to get the best performance.您可以通过使用自己的 memory 方案来提高性能,但请注意,Stride 可能需要一些 alignment 才能获得最佳性能。

You then will be able to go wild for example allocating huge virtual memory mapped scan0 and blit them quite efficiently.然后,您将能够对 go 进行野生分配,例如分配巨大的虚拟 memory 映射的 scan0 并非常有效地对它们进行 blit。 Note that pinning huge array (and especially a few) won't be a burden to the GC and will allow you to manipulate the byte/short in a totally safe way (or unsafe if you seek speed)请注意,固定巨大的数组(尤其是少数)不会成为 GC 的负担,并且允许您以完全安全的方式操作字节/短(如果您寻求速度,则不安全)

I'm not sure if there is a reason you're doing it the way you are.我不确定你这样做是否有原因。 Maybe there is.也许有。 It seems like you're off the beaten path enough so that you might be trying to do something more advanced than what the title of your question implies...似乎您已经走出了人迹罕至的道路,因此您可能正在尝试做一些比您的问题标题所暗示的更高级的事情...

However, the traditional way of creating a Bitmap from a Byte array is:但是,从 Byte 数组创建 Bitmap 的传统方法是:

using (MemoryStream stream = new MemoryStream(byteArray))
{
     Bitmap bmp = new Bitmap(stream);
     // use bmp here....
}

Here is a sample code i wrote to convert byte array of pixels to an 8 bits grey scale image(bmp) this method accepts the pixel array, image width, and height as arguments //这是我编写的将像素字节数组转换为 8 位灰度图像 (bmp) 的示例代码此方法接受像素数组、图像宽度和高度为 arguments //

public Bitmap Convert2Bitmap(byte[] DATA, int width, int height)
{
    Bitmap Bm = new Bitmap(width,height,PixelFormat.Format24bppRgb);
    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;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            int Value = DATA[x + (y * width)];
            Color C = ncp.Entries[Value];
            Bm.SetPixel(x,y,C);
        }
    }
   return Bm;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM