本文介绍了从像素数据的字节数组创建位图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这问题是关于如何读/写,分配和管理一个位图的像素数据。

下面是如何分配的像素数据的字节数组(管理存储器)和一个例子创建它使用位图:​​

 尺寸大小=新的大小(800,600);
的PixelFormat pxFormat = PixelFormat.Format8bppIndexed;
//获取步幅,在这种情况下,将有宽度的长度相同。
//由于图像像素格式1字节/像素。
//通常跨距=ByterPerPixel*宽

//但它并非总是如此。 信息。

  INT跨距= GetStride(size.Width,pxFormat);
字节[]数据=新的字节[步幅* size.Height]。
的GCHandle手柄= GCHandle.Alloc(数据,GCHandleType.Pinned);
BMP位图=新位图(size.Width,size.Height,步幅,
             pxFormat,handle.AddrOfPinnedObject());//做你的东西后,自由的位图和取消固定阵列。
bmp.Dispose();
handle.Free();公共静态INT GetStride(INT宽度的PixelFormat pxFormat)
{
    //浮动bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(格式);
    INT bitsPerPixel =((int)的pxFormat>→8)及0xFF的;
    //用于存储每行的图象数据位的数目(仅有效数据)
    INT validBitsPerLine =宽* bitsPerPixel;
    // 4字节,每INT32(32位)
    INT跨距=((validBitsPerLine + 31)/ 32)* 4;
    返回步幅;
}

我认为,位图将使阵列数据的副本,但它实际上指向相同的数据。是你可以看到:

 颜色℃;
C = bmp.GetPixel(0,0);
Console.WriteLine(前颜色:+ c.ToString());
//输出:前颜色:彩色[A = 255,R = 0,G = 0,B = 0]
数据[0] = 255;
C = bmp.GetPixel(0,0);
Console.WriteLine(颜色后:+ c.ToString());
//输出:后颜色:彩色[A = 255,R = 255,G = 255,B = 255]

问题:


  1. 它是安全的确实创造从byte []数组(管理存储器)的位图和free()的的GCHandle?如果不是安全的,生病需要保持一个固定阵列,多么糟糕的是,与GC /性能?


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


  3. 一SCAN0的地址可以由GC改变?我的意思是,我得到的SCAN0从锁定的位图,然后解锁,一段时间后,再次锁定时,SCAN0可以是不同的?


  4. 什么是ImageLockMode.UserInputBuffer在LockBits方法的目的是什么?这是很难找到有关的信息! MSDN不解释清楚!


修改1:一些后续


  1. 您需要保持固定。它会减慢GC? I've这里要求它。这取决于图像和它的尺寸的数量。有没有人给我一个量化的答案。它接缝,这是很难确定。
    您也可以使用元帅的Alloc内存或使用位图分配的非托管内存。


  2. 我做了使用两个线程很多考验。只要位图已被锁定,这是确定。如果位图是解锁,比它是不是安全! My相关岗位有关的读/写,直接向SCAN0 。博英的回答:我已经在上面为什么你是幸运的能够使用SCAN0锁之外解释。因为你用原来的BMP的PixelFormat和GDI是在这种情况下进行了优化,让您的指针,而不是一个副本。这个指针是有效的直到操作系统将决定释放它。有一个保证唯一的一次,LockBits和UnLockBits。期之间的


  3. 是的,它可以发生,但大的内存区域由GC处理不同,它的动作/不经常释放此大对象。因此,它可能需要一段时间来GC移动这个数组。 From MSDN 的:以任何分配大于或等于 85000字节那张大对象堆(LOH)...蕙只是一个第2代集中收集的。 .NET 4.5有LOH的改进。


  4. 此问题已被@Boing回答。不过,我要承认。我没有完全理解它。所以,如果或其他人可能会请澄清,我会很高兴。顺便说一句,我为什么不能只是directly读/写SCA0无锁?不,你不能。 SCAN0指向由非托管存储器(内部GDI)所取得的位图数据的副本。解锁后,该内存可以被重新分配给其他的东西,它不是garantee了这SCAN0将指向实际的位图数据。这可以被复制得到SCAN0中锁定,解锁,并做一些旋转,掠过解锁位图。一段时间后,SCAN0将指向一个无效的区域,你会得到试图读取/写入到内存中的位置时异常。



解决方案

  1. 其安全,如果你marshal.copy数据,而不是设置SCAN0(直接或通过的BitMap的过载())。你不想让管理对象固定,这将限制垃圾回收器。

  2. 如果您复制,绝对安全。

  3. 输入数组进行管理,并且可以通过GC进行移动,SCAN0是一个非托管指针,将得到过时如果阵列移动。位图对象本身进行管理,而是通过一个手柄设置在Windows SCAN0指针。

  4. ImageLockMode.UserInputBuffer是什么?显然,它可以传递给LockBits,也许它会告诉位图()复制输入数组数据。

举例code创建阵列从一个灰度位图:

 变种B =新位图(宽度,高度,PixelFormat.Format8bppIndexed);    ColorPalette NCP = b.Palette;
    的for(int i = 0; I< 256;我++)
        ncp.Entries [I] = Color.FromArgb(255,我,我,我);
    b.Palette = NCP;    VAR BoundsRect =新的Rectangle(0,0,宽度,高度);
    的BitmapData bmpData = b.LockBits(BoundsRect,
                                    ImageLockMode.WriteOnly,
                                    b.PixelFormat);    IntPtr的PTR = bmpData.Scan0;    INT字节= bmpData.Stride * b.Height;
    VAR rgbValues​​ =新的字节[字节];    //填写rgbValues​​,例如一个for循环在一个输入数组    Marshal.Copy(rgbValues​​,0,PTR,字节);
    b.UnlockBits(bmpData);
    返回b;

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? No you can't. 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 garantee 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.

解决方案
  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;

这篇关于从像素数据的字节数组创建位图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-31 06:15