When I worked with GDI, I was used to being able to create a DIBSection, and directly work with the actual bitmap bits in real time. When I got acquainted with GDI+, I looked high and low for a direct pixel manipulation method that did not (internally or externally) involve locking and copying a part of or the entire image to a separate buffer, working with it via unsafe code, and then copying it back, and was extremely disappointed to find none.
So, I settled for using
Bitmap.LockBits(), and was, for a while, resigned to the fact that under GDI+, I could no longer make repeated realtime changes directly to the image and immediately update the changes to the screen, and I couldn't work with images in a partial-trust zone.
The logical thing to do is to look for a way to get at the actual bits of an existing bitmap object, which is what most people look for, but to my knowledge no such thing exists in GDI+. But - surprise! - there is a way to create a new bitmap whose bits are stored pre-allocated area of memory, and then copy an existing bitmap onto its surface.
Since we have control of the memory the bits are stored in, we can then manipulate those bits directly. Not only that, but there is a way to cause the bitmap to use a managed array as its pixel buffer. This gives us the added advantage of not having to use 'unsafe' code, which means that we can do pixel manipulation in a low-trust environment, and that .NET languages that have not been blessed with the ability to use pointers can use this method as well.
The following code creates an empty bitmap whose pixel data is directly editable as a managed byte array.
pixelFormatSize = Image.GetPixelFormatSize(format) / 8;
stride = width * pixelFormatSize;
bits = new byte[stride * height];
handle = GCHandle.Alloc(bits, GCHandleType.Pinned);
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(bits, 0);
bitmap = new Bitmap(width, height, stride, format, pointer);
The steps are as follows:
- Create a 1-dimensional byte array of the right size
((bytes per pixel * width) * height)
- Pin the array preventing the GC from moving it around. This is important, because if the GC moves it, the address passed to GDI+ will be invalid. Note that pinning a lot of smaller objects (except temporarily) can degrade the performance of the GC. The cause of this is that since pinned objects cannot be relocated in memory, the .NET memory manager's Small Object Heap compaction routine cannot compact pinned objects, causing the managed memory heap to become fragmented. However, if an object is larger than 85000 bytes, it is allocated on the Large Object Heap (LOH), which is never compacted, so pinning a large object makes no difference in GC performance. Any image larger than 145x145x32bpp will be allocated on the LOH, so with most bitmaps, this will not be an issue. In cases where it could be an issue (i.e. with a large number of small bitmaps), there are various viable workarounds (using a single large array buffer for smaller bitmaps and allocating a chunk for each bitmap, storing multiple smaller images in one larger bitmap (imagelist style), etc).
- Get the address of the first element of the array.
- Pass the address of the array to the Bitmap(width,height,stride,format,scan0) constructor, so that it will be used as the bitmap's pixel data buffer.
We can now edit the bitmap's pixel data via the array. However, in order to edit an existing bitmap, we must copy the bitmap data into our newly created blank bitmap. To do this, we use
DrawImageUnscaledAndClipped (this only works with a non-indexed destination pixel format):
Graphics g = Graphics.FromImage(bitmap);
g.DrawImageUnscaledAndClipped(source, new Rectangle
(0, 0, source.Width, source.Height));
Note that this "only" copies the active frame of the image. It does "not" copy EXIF properties, multiple frames, etc. If you need to preserve this information, you can use the array-based
Bitmap as the managed equivalent of the working buffer that
LockBits() allocates, or you can copy the bitmap properties over to it manually.
What of the performance cost of using a managed array instead of pointers? Wouldn't using a managed array be slower than using pointers? It might surprise you, but based on my tests, the answer is no. In my tests, have found that the array method is at least 10% faster than the pointer method.
To see this method in action in a simple scenario, see my article, Queue-Linear Flood Fill: A Fast Flood Fill Algorithm, where I demonstrate a super-fast
Floodfill routine that is not prone to stack overflows, no matter how large the image is. You will be surprised at how fast image processing in .NET can be!
But that's not all. This image manipulation technique allows for some additional tricks. You can reuse the same memory buffer multiple times for different bitmaps in different formats, or create multiple smaller bitmaps that are "views" on a single larger bitmap.