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