Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#
Article

Fast Pointerless Image Processing in .NET

Rate me:
Please Sign up or sign in to vote.
4.88/5 (54 votes)
15 Nov 20064 min read 128K   2.1K   116   26
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.

C#
//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):

C#
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


Written By
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

 
Questionpixel color Pin
ProcopioPi26-Jul-15 19:20
ProcopioPi26-Jul-15 19:20 
Hi,

I just tried to use this to access and paint in real time this technique and it did work, the only thing is that the pixel, wont look as sharp or bright as normal. I mean... I paint a pixel as 255,255,255 but it looks like 128,128,128 and when I read again the bytes, they did were in 255 each... Do you know whats happening ???

greets
GeneralFantastic Pin
rodo_1985_221-Mar-14 0:04
rodo_1985_221-Mar-14 0:04 
QuestionNice. Pin
pdoxtader13-Feb-14 10:47
professionalpdoxtader13-Feb-14 10:47 
GeneralMy vote of 5 Pin
waangyan19-May-13 20:31
waangyan19-May-13 20:31 
QuestionGreat Pin
salmanabbasi5-Feb-13 20:21
salmanabbasi5-Feb-13 20:21 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey18-Feb-12 3:26
professionalManoj Kumar Choubey18-Feb-12 3:26 
GeneralForms? Pin
fzivkovi13-Jun-11 13:17
fzivkovi13-Jun-11 13:17 
Generalvery interesting Pin
BillW334-Oct-10 7:07
professionalBillW334-Oct-10 7:07 
GeneralThank you so much Pin
JayOkay9-Nov-09 1:23
JayOkay9-Nov-09 1:23 
GeneralJust what I needed Pin
Mal Ross7-Aug-08 7:11
Mal Ross7-Aug-08 7:11 
GeneralError Pin
velichko4-Feb-08 23:07
velichko4-Feb-08 23:07 
GeneralRe: Error Pin
fatho130-Mar-10 6:57
fatho130-Mar-10 6:57 
GeneralCompact Framework Pin
mcdaida25-Oct-07 4:51
mcdaida25-Oct-07 4:51 
GeneralProblems with 8 bit Bitmaps Pin
luening12-Jul-07 2:20
luening12-Jul-07 2:20 
QuestionHierarchical Distributed Genetic Algorithm for Image Segmentation Pin
Petra7-Jun-07 10:38
Petra7-Jun-07 10:38 
GeneralMono Compatibility Pin
UnderscoreC18-Jan-07 19:51
UnderscoreC18-Jan-07 19:51 
Questioninteresting... Pin
boops boops12-Dec-06 9:25
boops boops12-Dec-06 9:25 
AnswerRe: interesting... Pin
UnderscoreC18-Jan-07 19:53
UnderscoreC18-Jan-07 19:53 
GeneralRe: interesting... Pin
skyscanner18-Apr-23 3:38
skyscanner18-Apr-23 3:38 
Generalgreat work! Pin
Herre Kuijpers19-Nov-06 21:53
Herre Kuijpers19-Nov-06 21:53 
Generalgreat article Pin
neurorebel_17-Nov-06 5:00
neurorebel_17-Nov-06 5:00 
GeneralRe: great article Pin
Cerberu58-Jun-07 2:01
Cerberu58-Jun-07 2:01 
GeneralDispose pattern Pin
Juan Felipe Machado16-Nov-06 11:09
Juan Felipe Machado16-Nov-06 11:09 
GeneralRe: Dispose pattern Pin
stixoffire23-Mar-07 10:10
stixoffire23-Mar-07 10:10 
General10% improvement Pin
f216-Nov-06 8:02
f216-Nov-06 8:02 

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

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