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

发布于 2024-11-25 15:50:54 字数 3522 浏览 1 评论 0原文

本题是关于如何读/写、分配和管理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

//但这并不总是正确的。 更多信息请参见 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. GC 可以更改 Scan0 的地址吗?我的意思是,我从锁定的位图中获取 Scan0,然后解锁它,一段时间后再次锁定它,Scan0 可以不同吗?

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

编辑1:一些后续

  1. 您需要将其固定。它会减慢 GC 的速度吗? 我在这里问过。这取决于图像的数量及其大小。没有人给我一个定量的答案。看来很难确定。 您还可以使用 Marshal 分配内存或使用位图分配的非托管内存。

  2. 我使用两个线程做了很多测试。只要位图被锁定就可以了。如果位图被解锁,那么它就不安全! 我有关直接读/写的相关帖子到扫描0。 Boing 的回答“我已经在上面解释了为什么你很幸运能够在锁之外使用 scan0。因为你使用原始的 bmp PixelFormat 并且 GDI 在这种情况下进行了优化,为你提供指针而不是副本。这个指针是有效的直到操作系统决定释放它。唯一有保证的时间是在 LockBits 和 UnLockBits 之间。”

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

  4. 这个问题已由@Boing 回答。但我要承认。我没有完全理解它。因此,如果 Boing 或其他人可以请澄清,我会很高兴。顺便说一句,为什么我不能不加锁直接读/写Sca0? =>您不应直接写入 Scan0,因为 Scan0 指向由非托管内存(GDI 内部)创建的位图数据的副本。解锁后,该内存可以重新分配给其他东西,不再确定Scan0是否会指向实际的Bitmap数据。这可以通过将 Scan0 锁定、解锁并在解锁的位图中进行一些旋转飞行来重现。一段时间后,Scan0 将指向无效区域,并且在尝试读/写其内存位置时会出现异常。

This question is about how to read/write, allocate and manage the pixel data of a Bitmap.

Here is an example of how to allocate a byte array (managed memory) for pixel data and creating a Bitmap using it:

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.

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. 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? If it is not safe, Ill need to keep a pinned array, how bad is that to GC/Performance?

  2. Is it safe to change the data (ex: data[0] = 255;)?

  3. The address of a Scan0 can be changed by the GC? 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?

  4. What is the purpose of ImageLockMode.UserInputBuffer in the LockBits method? It is very hard to find info about that! MSDN do not explain it clearly!

EDIT 1: Some followup

  1. You need to keep it pinned. Will it slow down the 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.

  2. I've done a lot of test using two threads. As long as the Bitmap is locked it is ok. If the Bitmap is unlock, than it is not safe! My related post about read/write directly to 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."

  3. Yeah, it can happen, but large memory regions are treated different by the GC, it moves/frees this large object less frequently. So it can take a while to GC move this array. 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". .NET 4.5 have Improvements in LOH.

  4. This question have been answered by @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. By the way, Why I can't just directly read/write to Sca0 without locking? => You should not write directly to Scan0 because Scan0 points to a copy of the Bitmap data made by the unmanaged memory (inside GDI). After unlock, this memory can be reallocate to other stuff, its not certain anymore that Scan0 will point to the actual Bitmap data. This can be reproduced getting the Scan0 in a lock, unlock, and do some rotate-flit in the unlocked 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

客…行舟 2024-12-02 15:50:54
  1. 如果您 marshal.copy 数据而不是设置 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;
  1. Its safe if you marshal.copy data rather than setting scan0 (directly or via that overload of 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. The Bitmap object itself is managed but sets the scan0 pointer in Windows via a handle.
  4. ImageLockMode.UserInputBuffer is? Apparently it can be passed to LockBits, maybe it tells Bitmap() to copy the input array data.

Example code to create a greyscale bitmap from array:

    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;
梦情居士 2024-12-02 15:50:54

关于您的问题 4:ImageLockMode.UserInputBuffer 可以让您控制可引用到 BitmapData 的大量内存的分配过程。代码>对象。

如果您选择自己创建 BitmapData 对象,则可以避免使用 Marshall.Copy。然后,您必须将此标志与另一个 ImageLockMode 结合使用。

请注意,这是一件复杂的事情,特别是关于 Stride
和像素格式。

下面是一个示例,它将在一个镜头中将 24bbp 缓冲区的内容读取到位图上,然后在另一个镜头中将其读回到另一个 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...

如果您使用 ILSpy,您会看到此方法链接到 GDI+ 这些方法帮助更完整。

您可以通过使用自己的内存方案来提高性能,但是
请注意,Stride 可能需要进行一些调整才能获得最佳效果
性能。

然后,您将能够疯狂地分配巨大虚拟内存映射scan0并非常有效地进行blit它们。
请注意,固定大型数组(尤其是少数数组)不会成为 GC 的负担,并且允许您以完全安全的方式操作字节/短数组(如果您寻求速度,则不安全)

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.

If you choose to create yourself the BitmapData object you can avoid a Marshall.Copy. You will then have to use this flag in combinaison with another ImageLockMode.

Beware that it is a complicated business, specially concerning Stride
and 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.

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.

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.

You then will be able to go wild for example allocating huge virtual memory mapped scan0 and blit them quite efficiently.
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)

仙气飘飘 2024-12-02 15:50:54

我不确定你这样做是否有原因。也许有。看来您已经走得很远了,因此您可能会尝试做一些比问题标题所暗示的更高级的事情...

但是,从字节数组创建位图的传统方法是:

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

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:

using (MemoryStream stream = new MemoryStream(byteArray))
{
     Bitmap bmp = new Bitmap(stream);
     // use bmp here....
}
泪之魂 2024-12-02 15:50:54

这是我编写的示例代码,用于将像素字节数组转换为 8 位灰度图像(bmp)
该方法接受像素数组、图像宽度和高度作为参数
//

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;
}

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
//

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文