Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

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.

like image 418
Pedro77 Avatar asked Jul 21 '11 20:07

Pedro77


2 Answers

  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; 
like image 149
Peter Wishart Avatar answered Sep 21 '22 06:09

Peter Wishart


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)

like image 22
Boing Avatar answered Sep 21 '22 06:09

Boing