mdibb.net

Faster pixel manipulation with GetPixel and SetPixel in .NET

If you have been playing around much with VB.NET or C#, chances are you have had a go at manipulating images using the System.Drawing.Bitmap namespace. The Bitmap class is great and gives us a nice easy set of functions for finding out the colour of a pixel at a given coordinate, and changing it if we want. But if you are reading this now you’ve probably found out – like I did - that this is really really slow!

The reasons for this being so slow are deeply rooted in the .NET runtime (in particular the way that it manages code so that it is “safe”) that I’m not going to go into now. By using a fairly simple technique we can get around this problem and use the Bitmap in an “unsafe” way to get direct access to the memory, and speed things up a lot along the way.



The code I am going to show you below was based on a MSDN article by Eric Gunnerson where he showed how the “unsafe” technique can be used to make an image greyscale. I’ve basically just modified his code into a general purpose class, so you can just drop this class in and use its faster GetPixel and SetPixel methods how you would with the Bitmap class.

I’ve not done any proper benchmarks here, but what was taking about 7 seconds with the original and slow GetPixel methods, now happens pretty much instantly (certainly quicker than I could time manuaully) so its pretty fast.

The FastBitmap Class


This is the main code – a simple class that deals with the “unsafe” memory for the bitmap. Note that if you try using this in your project, you’ll need to use the “/unsafe” compiler flag to get it to compile – to set this up in Visual Studio, just right click on the project in the solution explorer, go to “Properties”, select the “Build” tab and tick the “Allow unsafe code” box; if you are using the command line or some other IDE, just stick the “/unsafe” switch in compilation command line somewhere.

using System; using System.Drawing; using System.Drawing.Imaging; namespace Image { public unsafe class FastBitmap { public struct PixelData { public byte blue; public byte green; public byte red; } Bitmap Subject; int SubjectWidth; BitmapData bitmapData = null; Byte* pBase = null; public FastBitmap(Bitmap SubjectBitmap) { this.Subject = SubjectBitmap; try { LockBitmap(); } catch (Exception ex) { throw ex; } } public void Release() { try { UnlockBitmap(); } catch (Exception ex) { throw ex; } } public Bitmap Bitmap { get { return Subject; } } public void SetPixel(int X, int Y, Color Colour) { try { PixelData* p = PixelAt(X, Y); p->red = Colour.R; p->green = Colour.G; p->blue = Colour.B; } catch (AccessViolationException ave) { throw (ave); } catch (Exception ex) { throw ex; } } public Color GetPixel(int X, int Y) { try { PixelData* p = PixelAt(X, Y); return Color.FromArgb((int)p->red, (int)p->green, (int)p->blue); } catch (AccessViolationException ave) { throw (ave); } catch (Exception ex) { throw ex; } } private void LockBitmap() { GraphicsUnit unit = GraphicsUnit.Pixel; RectangleF boundsF = Subject.GetBounds(ref unit); Rectangle bounds = new Rectangle((int)boundsF.X, (int)boundsF.Y, (int)boundsF.Width, (int)boundsF.Height); SubjectWidth = (int)boundsF.Width * sizeof(PixelData); if (SubjectWidth % 4 != 0) { SubjectWidth = 4 * (SubjectWidth / 4 + 1); } bitmapData = Subject.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); pBase = (Byte*)bitmapData.Scan0.ToPointer(); } private PixelData* PixelAt(int x, int y) { return (PixelData*)(pBase + y * SubjectWidth + x * sizeof(PixelData)); } private void UnlockBitmap() { Subject.UnlockBits(bitmapData); bitmapData = null; pBase = null; } } }

Using FastBitmap


I tried to make this class as simple to use as possible – you should just be able to drop it in and use the GetPixel and SetPixel methods as you would with any normal Bitmap.

You simply need to provide a normal Bitmap object to the constructor, like so:

FastBitmap FBitmap = new FastBitmap(MyOriginalBitmap);



After that you can just use the usual SetPixel and Get Pixel methods with the usual System.Drawing.Color arguments and so on. Finally when you are done with the FastBitmap, you should release it like so:

FBitmap.Release();
This will basically “tidy up” the memory used by the unsafe mode and a few bits and pieces inside the class.

Please note that although I have tested this class, I cannot vouch for its reliability. It seems pretty stable and reliable to me, and I have put in some basic exception handling that you should surround with try catch blocks but it might do some strange things, particularly if you are dealing with different thre

Back 22.08.2006.

Really liked your code. I was able to incorporate it into my code very easily and it worked great. Definitely much faster.

I was wondering if you could comment on how to modify the code to work include an alpha transparency value? I made a stab at it by adding a byte for alpha transparency to the PixelData struct and then changed the following:

return Color.FromArgb((int)p->alpha, (int)p->red, (int)p->green, (int)p->blue); in GetPixel,

p->alpha = Colour.A; in SetPixel, and

bitmapData = Subject.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); in LockBitmap.

Do you see any downsides in doing this?

Thanks for the help.

Dave

> Dave H > 31.10.2006

Shold be called : BitMapHelper because is not a Bitmap...

Thx for this great code.

> MrTopom > 17.01.2007

Should be called : BitMapHelper because is not a Bitmap...

Thx for this great code.

> MrTopom > 17.01.2007

Thank you so much for this great code.
God bless you!

> Rodrigo (from Argentina) > 02.02.2007

Could you please help me with getting back to the Bitmap object?

We can use this line to get from Bitmap to FastBitmap object:
FastBitmap FBitmap = new FastBitmap(MyOriginalBitmap);

But line FBitmap.Release(); does not cast the Fbitmap object back to the Bitmap format...
Maybe i missed some important issue-
Sorry for my English...

> Viscid > 23.03.2007

Just use the Bitmap accessor after you have released,

e.g. Bitmap b = FBitmap.Bitmap

> Matt > 23.03.2007

do you have the vb .net one ?

> ajong > 02.04.2007

Me is from Ethiopia I totally loved the code. I was working on an Amharic OCRecognition, thanx

> Yodaw_G > 06.06.2007