从像素数据的字节数组创建位图
本题是关于如何读/写、分配和管理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]
问题:
从 byte[] 数组(托管内存)创建位图并 free() GCHandle 是否安全?如果不安全,我需要保留一个固定数组,这对 GC/性能有多糟糕?
更改数据是否安全(例如:data[0] = 255;)?
GC 可以更改 Scan0 的地址吗?我的意思是,我从锁定的位图中获取 Scan0,然后解锁它,一段时间后再次锁定它,Scan0 可以不同吗?
LockBits 方法中 ImageLockMode.UserInputBuffer 的用途是什么?很难找到这方面的信息! MSDN没有解释清楚!
编辑1:一些后续
您需要将其固定。它会减慢 GC 的速度吗? 我在这里问过。这取决于图像的数量及其大小。没有人给我一个定量的答案。看来很难确定。 您还可以使用 Marshal 分配内存或使用位图分配的非托管内存。
我使用两个线程做了很多测试。只要位图被锁定就可以了。如果位图被解锁,那么它就不安全! 我有关直接读/写的相关帖子到扫描0。 Boing 的回答“我已经在上面解释了为什么你很幸运能够在锁之外使用 scan0。因为你使用原始的 bmp PixelFormat 并且 GDI 在这种情况下进行了优化,为你提供指针而不是副本。这个指针是有效的直到操作系统决定释放它。唯一有保证的时间是在 LockBits 和 UnLockBits 之间。”
是的,这可能会发生,但是 GC 对大内存区域的处理有所不同,它移动/释放这个大对象的频率较低。因此 GC 移动这个数组可能需要一段时间。 来自 MSDN:“任何大于或等于
85,000 字节
的分配都会在大型对象堆 (LOH)
上进行”...“LOH 仅是在第 2 代收集期间收集”。 .NET 4.5 对 LOH 进行了改进。这个问题已由@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:
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?
Is it safe to change the data (ex: data[0] = 255;)?
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?
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
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.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."
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 thelarge object heap (LOH)
" ... "LOH is only collected during a generation 2 collection". .NET 4.5 have Improvements in LOH.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 couldplease 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
从数组创建灰度位图的示例代码:
Example code to create a greyscale bitmap from array:
关于您的问题 4:
ImageLockMode.UserInputBuffer
可以让您控制可引用到BitmapData
的大量内存的分配过程。代码>对象。如果您选择自己创建 BitmapData 对象,则可以避免使用 Marshall.Copy。然后,您必须将此标志与另一个
ImageLockMode
结合使用。下面是一个示例,它将在一个镜头中将 24bbp 缓冲区的内容读取到位图上,然后在另一个镜头中将其读回到另一个 48bbp 缓冲区中。
如果您使用 ILSpy,您会看到此方法链接到 GDI+ 这些方法帮助更完整。
然后,您将能够疯狂地分配巨大虚拟内存映射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 aBitmapData
object.If you choose to create yourself the
BitmapData
object you can avoid aMarshall.Copy
. You will then have to use this flag in combinaison with anotherImageLockMode
.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.
If you use ILSpy you'll see that this method link to GDI+ and those methods helps are more complete.
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)
我不确定你这样做是否有原因。也许有。看来您已经走得很远了,因此您可能会尝试做一些比问题标题所暗示的更高级的事情...
但是,从字节数组创建位图的传统方法是:
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:
这是我编写的示例代码,用于将像素字节数组转换为 8 位灰度图像(bmp)
该方法接受像素数组、图像宽度和高度作为参数
//
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
//