Fast Image Processing in C#
This example shows how to process the images in C# comparatively. Users often use GetPixel/SetPixel methods in System.Drawing.Bitmap class but these methods have bad performance, especially for big images. It is the simplest approach and it does not require you to know anything about pixel format(number of bits per pixel) of processed picture. If we use the memory access and parallel programming(using threads), image processing time is less than PixelGet and PixelSet methods.
Following samples show how to use comparatively.
Sample Usages :
Stopwatch sw = new Stopwatch(); sw.Start(); ProcessUsingGetPixelSetPixel(bmp); sw.Stop(); Console.WriteLine(string.Format("Processed using ProcessUsingGetPixel method in {0} ms.", sw.ElapsedMilliseconds)); sw.Restart(); ProcessUsingLockbits(bmp); sw.Stop(); Console.WriteLine(string.Format("Processed using ProcessUsingLockbits method in {0} ms.", sw.ElapsedMilliseconds)); sw.Restart(); ProcessUsingLockbitsAndUnsafe(bmp); sw.Stop(); Console.WriteLine(string.Format("Processed using ProcessUsingLockbitsAndUnsafe method in {0} ms.", sw.ElapsedMilliseconds)); sw.Restart(); ProcessUsingLockbitsAndUnsafeAndParallel(bmp); sw.Stop(); Console.WriteLine(string.Format("Processed using ProcessUsingLockbitsAndUnsafeAndParallel method in {0} ms.", sw.ElapsedMilliseconds)); //Result: (For ~7500x5000 pixel) //Processed using ProcessUsingGetPixel method in 87118 ms. //Processed using ProcessUsingLockbits method in 333 ms. //Processed using ProcessUsingLockbitsAndUnsafe method in 177 ms. //Processed using ProcessUsingLockbitsAndUnsafeAndParallel method in 78 ms.
Processing using GetPixel and SetPixel :
private void ProcessUsingGetPixelSetPixel(Bitmap processedBitmap) { int width = processedBitmap.Width; int height = processedBitmap.Height; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Color oldPixel = processedBitmap.GetPixel(x, y); // calculate new pixel value Color newPixel = oldPixel; processedBitmap.SetPixel(x, y, newPixel); } } }
Processing using Bitmap.Lockbits and Marshal.Copy :
private void ProcessUsingLockbits(Bitmap processedBitmap) { BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat); int bytesPerPixel = Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8; int byteCount = bitmapData.Stride * processedBitmap.Height; byte[] pixels = new byte[byteCount]; IntPtr ptrFirstPixel = bitmapData.Scan0; Marshal.Copy(ptrFirstPixel, pixels, 0, pixels.Length); int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel; for (int y = 0; y < heightInPixels; y++) { int currentLine = y * bitmapData.Stride; for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int oldBlue = pixels[currentLine + x]; int oldGreen = pixels[currentLine + x + 1]; int oldRed = pixels[currentLine + x + 2]; // calculate new pixel value pixels[currentLine + x] = (byte)oldBlue; pixels[currentLine + x + 1] = (byte)oldGreen; pixels[currentLine + x + 2] = (byte)oldRed; } } // copy modified bytes back Marshal.Copy(pixels, 0, ptrFirstPixel, pixels.Length); processedBitmap.UnlockBits(bitmapData); }
Processing using Bitmap.LockBits and direct memory access in unsafe context :
private void ProcessUsingLockbitsAndUnsafe(Bitmap processedBitmap) { unsafe { BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel; byte* ptrFirstPixel = (byte*)bitmapData.Scan0; for (int y = 0; y < heightInPixels; y++) { byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride); for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int oldBlue = currentLine[x]; int oldGreen = currentLine[x + 1]; int oldRed = currentLine[x + 2]; // calculate new pixel value currentLine[x] = (byte)oldBlue; currentLine[x + 1] = (byte)oldGreen; currentLine[x + 2] = (byte)oldRed; } } processedBitmap.UnlockBits(bitmapData); } }
Processing using Bitmap.LockBits and direct memory access in unsafe context with System.Threading.Tasks.Parallel class :
private void ProcessUsingLockbitsAndUnsafeAndParallel(Bitmap processedBitmap) { unsafe { BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel; byte* PtrFirstPixel = (byte*)bitmapData.Scan0; Parallel.For(0, heightInPixels, y => { byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride); for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int oldBlue = currentLine[x]; int oldGreen = currentLine[x + 1]; int oldRed = currentLine[x + 2]; currentLine[x] = (byte)oldBlue; currentLine[x + 1] = (byte)oldGreen; currentLine[x + 2] = (byte)oldRed; } }); processedBitmap.UnlockBits(bitmapData); } }
Image Size (MP) | GetPixel/SetPixel (ms) | LockBits + Marshal.Copy (ms) | LockBits + unsafe context (ms) | Lockbits + unsafe context + Parallel.For (ms) |
~10 | 22314 | 82 | 31 | 19 |
~38 | 87118 | 333 | 117 | 78 |
hi . thanks for this articles .
Thank you for your interesting article!
I test your code on a 2014×1038 jpg file, however, get the following results:
GetPixel/SetPixel: 12011 (ms)
LockBits: 12050 (ms)
LockBits + unsafe: 12077 (ms)
Lockbits + unsafe + Parallel: 12112 (ms)
Any idea why the process time even increases?
My bad, I copy and pasted sw.Start(); for each methods.
Only the first one is sw.Start(), and the rest of them should be sw.Restart();
The results looks reasonable after the correction with something like:
GetPixel/SetPixel: 13204 (ms)
LockBits: 74 (ms)
LockBits + unsafe: 48 (ms)
Lockbits + unsafe + Parallel: 73 (ms)
The image is small, so the parallel method does not improve much. Will try it on a larger one. For image size larger than 5m, may worth a try.
What is the properties of CPU in your computer? If it is only one core, it may not improve much.
bytesPerPixel -> bitsPerPixel
scrub that comment, my browser wasn’t showing the “/8” at the end of the line – sorry.
Thank you for this code! In my computer the difference between the the different processing systems was huge
Now I’ll just modify this code for what i need in my application
I am using usafe code to detect a point in image from one camera(60fps,1280×1024,1.3mp).i am getting 10ms for one frame in 12 core cpu.it is fine and working well.now i am using 3 cameras at a time and i am running 3 unsafe functions at this case am getting inconstant delays in 3 functions.
can any one help me regarding this issue.
Thank you.
Can you give a sample code about your usage?
Very nice post. Can you please give me the sample code about your usage?
I am not seeing any benefits of Parallel code while running on my MacBookPro with i7 and VMware Fusion VM running Windows 10 (with 4 cores turned on). Will repeat this on a real PC.
Thanks for sharing this. Helped much!
How can I access the alpha channel?
Got it. Its on the offset x + 3
It’s the most comprehensive discussion of this topic that I could find in the interwebs. It really spared me a lot of time in figuring out the details.
Thanks so much for this code!
Any from doing fill..But this from scanning and create picture /image and this image find in all attribute and this attribute is store database automatically in c#.
So can you send me any query using this method..
Pingback: C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App - QuestionFocus
Thank you for this code example. I have a problem working with it though. The traditional double for loops work fine for me. But the other 3 functions do not return the correct image, if I create a bitmap and try to set the pixels with the presented functions. I just get a black image.
Any hint, where the problem could be?
I found the solution. My problem was, that I’ve created a bitmap of a specific size but did not fill it with any pixel data. So for the faster functions to work properly, you need to read an image from a harddrive or you have to set the pixels at least once after creating a new bitmap container.
Hi to all:
I’ve been looking into this and other forums for the following.
I’m not a programmer myself, but I work for an s/w development company as an operations mgr.
Within the company we have a photo booth side line we use for trade shows and so.
We use an “out of the box” s/w by: photo booth solutions.
I would like to implement the virtual background with the RealSense technology without the use of chroma green screen.
I have experimented with the RealSense technology and put it to work.
But, my question is if there is a Code routine lightweight enough that I can use and call from this photo booth application for RealSense virtual background?
One more test case that would be useful to add: LockBits + Parallel. Based on the results here, that would probably run about as fast as LockBits + unsafe (without Parallel). If that is so, then its only necessary to go “unsafe”, in the absolutely most time-critical situations, and only after trying Parallel.
Agree with Steve. LockBits + Parallel seems to be the way to go.
Hi to all!
I would like to set an image per each layer, someone have tried this?
Regards!
thank, you worked at me
Amazing tutorial, thanks!
I’m trying to process the image in reverse order (so bottom right to op left pixel) but having a rough time with it. Can you help?
Hello,
I tried to adapt the code to my application, where there is a source bitmap and a destination bitmap.
I’m getting destination with “holes” in the process when using Parallel.For, and a perfect result when just using a classical for.
What am I missing ?
Please control the data access for multi threading. I can answer much more clearly if you can send a sample code.
hi guys! can someone explain me what is byte*?
Hi, It is a byte pointer. Pointers are blocks of memory (8 bytes on 64-bit machines) that reference memory addresses of any data type. It is declared using the * character.
Hi, I must to talk you, THANKS a lot, helpful
This is a nice article, clearly and impact.
Have a good day!