Click here to Skip to main content
Click here to Skip to main content
Go to top

Fast Pointerless Image Processing in .NET

, 15 Nov 2006
Rate this:
Please Sign up or sign in to vote.
Process GDI+ images at blazing speeds, with no pointers or unsafe code. Eliminate the need for LockBits(), so you can edit the bits directly and update in real time.

Introduction

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.

//member variables
Bitmap bitmap;
byte[] bits;
GCHandle handle;
int stride;
int pixelFormatSize;

...

//creation routine
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);
//'source' is the source bitmap
g.DrawImageUnscaledAndClipped(source, new Rectangle
            (0, 0, source.Width, source.Height));
g.Dispose();

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

J. Dunlap
Web Developer
United States United States
My main goal as a developer is to improve the way software is designed, and how it interacts with the user. I like designing software best, but I also like coding and documentation. I especially like to work with user interfaces and graphics.
 
I have extensive knowledge of the .NET Framework, and like to delve into its internals. I specialize in working with VG.net and MyXaml. I also like to work with ASP.NET, AJAX, and DHTML.

Comments and Discussions

 
GeneralFantastic Pinmemberrodo_1985_221-Mar-14 0:04 
QuestionNice. Pinmemberpdoxtader13-Feb-14 10:47 
GeneralMy vote of 5 Pinmemberwaangyan19-May-13 20:31 
QuestionGreat Pinmembersalmanabbasi5-Feb-13 20:21 
GeneralMy vote of 5 Pinmembermanoj kumar choubey18-Feb-12 3:26 
GeneralForms? Pinmemberfzivkovi13-Jun-11 13:17 
Generalvery interesting PinmemberCIDev4-Oct-10 7:07 
GeneralThank you so much PinmemberJayOkay9-Nov-09 1:23 
GeneralError Pinmembervelichko4-Feb-08 23:07 
GeneralRe: Error Pinmembericetea9430-Mar-10 6:57 
GeneralCompact Framework Pinmembermcdaida25-Oct-07 4:51 
GeneralProblems with 8 bit Bitmaps Pinmemberluening12-Jul-07 2:20 
QuestionHierarchical Distributed Genetic Algorithm for Image Segmentation PinmemberHana Al-Momani7-Jun-07 10:38 
GeneralMono Compatibility PinmemberJack Schitt18-Jan-07 19:51 
Questioninteresting... Pinmemberboops boops12-Dec-06 9:25 
AnswerRe: interesting... PinmemberJack Schitt18-Jan-07 19:53 
Generalgreat work! PinmemberHerre Kuijpers19-Nov-06 21:53 
Generalgreat article Pinmemberneurorebel_17-Nov-06 5:00 
GeneralRe: great article PinmemberCerberu58-Jun-07 2:01 
GeneralDispose pattern PinmemberJuan Felipe Machado16-Nov-06 11:09 
GeneralRe: Dispose pattern Pinmemberstixoffire23-Mar-07 10:10 
General10% improvement Pinmemberf216-Nov-06 8:02 
GeneralRe: 10% improvement Pinmemberpvandijk2820-Nov-06 21:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 15 Nov 2006
Article Copyright 2006 by J. Dunlap
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid