Image Processing in C#

Most image files tend to have a large number of pixels when it comes to scanned documents and newer digital photos. One of the challenges of managed code in the .NET environment is efficiently processing large amounts of data. Image processing is highly iterative over very large amounts of data in the form of millions of pixels. There are calls such as System.Drawing.Bitmap.GetPixel(x, y) and System.Drawing.Bitmap.SetPixel(x, y, color) that give you access to this data, but they are very inefficient. These kinds of tasks are best left to unmanaged code.

C# provides a mechanism to execute code as unmanaged within the same application. This is done by setting the build options to “Allow unsafe code” and including the System.Runtime.InteropServices namespace in the file where the unmanaged access will take place. The class making the calls will need to be declared as unsafe as well. The sample file below demonstrates this rather well.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace MyImageTool
{
   public unsafe class GrayscaleConverter
   {
      public GrayscaleConverter()
      {
      }

      public Bitmap Convert(Image image)
      {
         int width = image.Width;
         int height = image.Height;
         Rectangle bounds = new Rectangle(0, 0, width, height);
         Bitmap newImage = new Bitmap(width, height);
         newImage.SetResolution(image.HorizontalResolution,
            image.VerticalResolution);
         using (Graphics g = Graphics.FromImage(newImage))
         {
            g.DrawImageUnscaled(image, bounds);
         }

         BitmapData sourceData = null;
         try
         {
            // Get the source image bits and lock into memory
            sourceData = newImage.LockBits(bounds,

               ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

            // Define the source data pointers. The source row is a
            // byte to keep addition of the stride value easier
            // (as this is in bytes)
            byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();
            Quantizer.Color32* pCurrentRow;

            // Loop through each row
            for (int row = 0; row < height; row++)
            {
               // Set the source pixel to the first pixel in this row
               pCurrentRow = (Quantizer.Color32*)pSourceRow;

               // And loop through each column
               for (int col = 0; col < width; col++)
               {
                  int red = pCurrentRow[col].Red;
                  int green = pCurrentRow[col].Green;
                  int blue = pCurrentRow[col].Blue;
                  pCurrentRow[col].Red = pCurrentRow[col].Green =
                     pCurrentRow[col].Blue =
                     (byte)((red + green + blue) / 3);
               }

               // Add the stride to the source row
               pSourceRow += sourceData.Stride;
            }
         }
         finally
         {
            // Ensure that the bits are unlocked
            newImage.UnlockBits(sourceData);
         }

         return newImage;
      }
   }
}

The key to gaining unsafe access to memory are the System.Drawing.Bitmap.LockBits(...) and System.Drawing.BitmapData.Scan0.ToPointer() calls. Once the memory is locked and an unsafe pointer is returned, the memory can be directly manipulated for fastest performance.