Thursday, April 12, 2007

Converting from a DIB to a System.Drawing.Bitmap

Yes, I even do some .NET programming in C# these days. Don't worry, its just at my day job. In any case, since everyone and their brother, cousin, and best friend seems to have a blog with random .NET tidbits, I figured I'd add one here too. (Speaking of which... Please, people, your blogs aren't so important that you need to fill 75% of it with links to other people's blog postings, lest your "special" readers never know about them. It makes Googling painful at times.)

In any case, I found myself inside some code that got an image (in native Windows "DIB" format, as an object that was of type "byte[]") and needed to convert it into a System.Drawing.Bitmap. The code actually did this already, by extracting the image size from the DIB header, creating a new Bitmap, then proceeding to iterate over every single pixel to directly set it in the Bitmap. Obviously that was way too slow, and a better solution was needed.

I then searched the web far and wide, not really finding anything useful. Sure, there were a lot of hints, but no real solutions. Bitmap itself actually has no easy way to be created from a byte array. What it does have, strangely enough, is the ability to be created from a pointer to unmanaged memory and some hints:

public Bitmap (
int width,
int height,
int stride,
PixelFormat format,
IntPtr scan0
)


Since I had my data in a byte[], not as a chunk referenced by an IntPtr, the first step was to remedy the situation. Here is how you do it, as weird as it may look:

GCHandle handle = GCHandle.Alloc(dib, GCHandleType.Pinned);
IntPtr handlePtr = handle.AddrOfPinnedObject();


(When you are done using the handle, don't forget to call "handle.Free()".)

Now all I had to do was offset the IntPtr by 40 (size of the DIB header), add in the information the existing code already extracted from the header, and create a new bitmap. Here's the final result:

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
...
GCHandle handle = GCHandle.Alloc(dib, GCHandleType.Pinned);
IntPtr handlePtr = handle.AddrOfPinnedObject();
IntPtr scan0 = new IntPtr(handlePtr.ToInt32() + 40);
Bitmap bitmap =
new Bitmap(width, height, width * 3, PixelFormat.Format24bppRgb, scan0);
handle.Free();
...


It may look ugly, and very icky, but it does work. In fact, this allowed me to take an operation that took two whole seconds of wall-clock time and make it instantaneous.

2 comments:

Lucas said...

I don't know if Windows DIB is supported in GDI+, but for supported formats (JPG, PNG, GIF, etc), you can convert a byte[] to Bitmap like this:

Bitmap bmp = new Bitmap(new MemoryStream(bytes));

Common mistake: Do NOT dispose the MemoryStream object until your are done with the Bitmap object. Accessing the Bitmap object might require access to the stream because not all bytes are read when Bitmap is constructed.

Rennie said...

Thank you very much, this was really great - just what I was looking for.

Only one possible extra wish: Did you ever extend your program to work with more general DIBs, for example with color depth other than 24 bits? Or do you know of anyone else who has done that?