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);

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.