Create Bitmap from a byte array of pixel data
Asked Answered
B

4

41

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.

Birt answered 21/7, 2011 at 20:40 Comment(4)
How about new Bitmap, LockBits(), Marshal.Copy?Tomahawk
I'm trying to avoid Marshal.Copy because it would duplicate the memory needed and loose time allocating and copying.Birt
Directly from your question: "I thought that the Bitmap would make a copy of the array data". I was suggesting a way to make that happen, into memory owned by the Bitmap object.Tomahawk
Yeah but it doesn't. Thanks.Birt
S
27
  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;
Strobel answered 5/3, 2012 at 1:9 Comment(10)
So, the bitmap pixel data is unmanaged code. When I create a Bitmap with a handle.AddrOfPinnedObject() as data source, if I Free the pinned object, the Bitmap can get corrupted? I think it will remain locked, but I'm not sure. I've accepted your answer. But for me still are some unclear things around the Bitmap object.Birt
Yes, the bitmap pixel data is unmanaged. If you set the unmanaged data pointer (Scan0), you take responsibility for allocation & preservation of this unmanaged data. If you use managed data as the source but don't pin it, the data can get corrupted (eventually).Strobel
Scan0 will always remain the same because it is unmanaged memory? What LockBits really does? Why I can write to Bitmap data even if I set the ImageLockMode.ReadOnly ? Or even if I unlock the bitmap, I still can read/write data from Scan0.Birt
The API seems to be structured such that it reserves the right to copy/move the data before giving the client access and reserves the right for only writable buffers to be committed after the call to Unlock. This is all that matters if you want your code to work and stay working... If you need maximum speed you should use UserInputBuffer as per @Boing's answer.Strobel
I did not understand the Boings code, it is missing to explain the origin of some objects. I've noticed that I can write freely at the Bitmap memory (Scan0) even from another Thread while displaying it at a PictureBox. I Lock the Bitmap, get the scan0 and unlock it. This is why I don't understand why I need to use LockBits to ready/write to Bitmap pixel data since I can do it freely apparently without memory conflicts after I know the Scan0.Birt
Thanks Peter, this really helped me solve a problem I was having. For anyone else, there are 2 main examples that pop up when you search this ^ this approach is the only one that overwrites the data the bitmap points to and manages that data. If you were to do: bmp = new Bitmap(width, height, stride, pixelFormat, ptr); where ptr is an unsafe intptr to a byte array, then you have to manage the byte array yourself. Letting it go out of scope will lead to interesting images :)Fletafletch
An easier way to make a copy is to use the code in the question, then call Clone(). The cloned bitmap will own the memory for its own pixels.Tomahawk
using Format16bppRgb565 I'm getting an index error on ncp.Entries[i] = Color.FromArgb(255, i, i, i);. Why? @BirtSilvana
@majidarif, I don't know. I think you need to formulate better your problem in a new question, please include a code sample.Birt
int bytes = Math.Abs(bmpData.Stride)*b.Height, Stride can be negative. May not be relevant here, but can be necessary in other cases where the bitmap is read from file.Harborage
C
12

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)

Cysto answered 22/5, 2013 at 21:25 Comment(8)
Thank you very much for the answer. What do you do with bitmaps to know that stuff?Birt
I'm trying to understand what you did here. Can you please edit your post adding the instances of myPrewrittenBuff, Image, bm and myBM ?Birt
@Birt myPrewrittenBuff could be allocated anyway and contains anything from random noise to a mandelbroot fractal "buffer" being written to by MANY other threads without any nasty interaction. (they write to the buff, while main is reading/blitting result every x mili sec). When you do that you do not want the OS to allocate the buff. You do it, and its pin life time is ... forever/resize ;-)Cysto
When you create the Bitmap, it will alloc some memory that you will trow away when you first lock it using WriteOnlyMode. Right? Why you don't just use the pinned IntPtr on the Bitmap constructor avoiding the write Lock?Birt
To update the reading buffer, why not just use Buffer.BlockCopy(buffWrite, 0, buff, 0, buffWrite.Length)?? buffWrite is the allocated and pinned managed array used for myPrewrittenBuff. If I've understood you code, the result should be the same (actually it isn't, but I don't know why). After the first time that you lock the Bitmap to write the buffer, any change to the pinned array will change the bitmap already, because they point to the same location.Birt
No, the Bitmap have its own memory/format, and my guess is it sits in the graphic card. When you lock Write only, you simply ask for the memory in the BitmapData to be copied to the Bitmap (wherever that is). It is done once when calling UnlockBits(). <br/> If the Bitmap do not exist then yes, you can call the ctor with IntPtr. Do not forget that nothing forbids you to have a BitmapData bigger or smaller then the Bitmap.Cysto
The example I give emphasis the fact the the memory is allocated by you, and its life span is different from the Bitmap, and more importantly NO second copy has to be made (first from Bitmap to BitmapData, second from OS BitmapData to your own buff by marshaling).<br/> The usage of ImageLockMode.UserInputBuffer is to give the user the ability to managed the allocation process of the User space buffer. It is not the memory of the Bitmap. Just a buffer that will be copy to or written from (or both) at LockBits() UnLockBits().<br/>Cysto
Finally, the example I give is scholar and theoretical, and the buffers content are different because the pixel format is different. Between write and read you may imagine tapping into hardware accelerated transformation (stretching / alpha blending the Bitmap) and read the buffer transformed (useless but who knows). And I am still using UserInputBuffer to regularly blit a buffer into an existing bitmap without any Marshal.Copy.Cysto
G
5

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....
}
Gigi answered 21/7, 2011 at 20:44 Comment(4)
This requires you to have a BMP file in memory, which is quite different from an array containing the pixel data.Tomahawk
You can access Bitmap data from the byte array, it make things easier. Another reason is that some cameras give the image in byte array format, so I can create the bitmap from it.Birt
Understood. You might want to change the title to "Create Bitmap from byte array of pixel data" to attract more attention.Gigi
please help... I found that today if the object size is > 85,000 bytes it will be allocate at large object heap (LOH). So it is already pinned. Here: msdn.microsoft.com/en-us/magazine/cc534993.aspx. The article says that creating to much temporaly large objects can fragment the memory. But I dont know how much is too much..Birt
N
-2

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;
}
Nishanishi answered 7/2, 2014 at 15:2 Comment(1)
Actually you are returning a 24bpp Bitmap. b is used only to get the Palette. SetPixel is ultra slow, you should use Lockbits. Also, to convert from 8bpp to 24bpp you can use Graphics DrawImage. If width != stride your method fails.Birt

© 2022 - 2024 — McMap. All rights reserved.