03 Sep

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

16 thoughts on “Fast Image Processing in C#

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

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

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

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

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

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

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

Leave a Reply

Your email address will not be published. Required fields are marked *