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.
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
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