Saturday, June 14, 2008

For Delphi's Scanline Property, in .NET We Use LockBits

When I was in college, I used Delphi to do my assignment in Digital Image Processing subject. No particular reason though. It just because Delphi is the most popular tools to be used when working with image manipulation. My senior told me that those tools are quite good to use, because we can do an image manipulation in bit level and accessing the pointer. Since Delphi using Pascal as the core language, and Pascal do support pointer, I think the statement is right. CMIIW.

The most important one is that Delphi has a very powerful method to work with image. The method called Scanline. For a brief explanation of this method, just read it here.

Once, a friend asked me about how to implement Delphi's ScanLine when working in .NET. Hmm... that's new to me. As far as I now, there is no such method which have same ability with
Delphi's ScanLine. Common methods used to work with Image (specifically Bitmap) is Bitmap.GetPixel and Bitmap.SetPixel method. But these methods are extremely slow to be used when working with a huge image.

After browsing through uncle Google for a while, I found out that there is a method which can be used when we want to work directly accessing image data in memory. The method is Lockbits. Please refer to this article for the explanation. The writer has just explain the method in very detail and good way, so I believe you'll understand quickly.

Here is an example to do a Grayscale operation with
Bitmap.GetPixel and Bitmap.SetPixel method.


///
/// Grayscale method
///

public void Grayscale()
{
int width = _bmp.Size.Width;
int height = _bmp.Size.Height;

for (int heightIdx = 0; heightIdx < height; heightIdx++)
{
for (int widthIdx = 0; widthIdx < width; widthIdx++)
{
Color objImgColor;
objImgColor = _bmp.GetPixel(widthIdx, heightIdx);

int grayScaleBase = (int)((objImgColor.R + objImgColor.G + objImgColor.B) / 3);
Color grayScaleColor;
grayScaleColor = Color.FromArgb(grayScaleBase, grayScaleBase, grayScaleBase);

_bmp.SetPixel(widthIdx, heightIdx, grayScaleColor);
}
}
}



When woring with a large image, the method above is extremely slow. Just try it If you don't believe me. And here is how we implement Lockbits method to do a Grayscale operation on image.


///
/// Grayscale method by using pointer access
///

public void SpeedGrayscale()
{

// Specify width, height, anf pixel format
int imgWidth = _bmp.Width;
int imgHeight = _bmp.Height;
PixelFormat imgPixFormat = _bmp.PixelFormat;

// Create BitmapData and Rectangle object, which will be used
// in Lockbits method
BitmapData bmpData = null;
Rectangle rct = new Rectangle(0, 0, imgWidth, imgHeight);

try
{
// We use unsafe keyword, because this is a pointer operation
unsafe
{

//Lockbits = lock bitmap data to memory
bmpData = _bmp.LockBits(rct, ImageLockMode.ReadWrite, imgPixFormat);

byte* row; // Pointer for a bitmap row
byte* rowElement; // Pointer for a pixel
int intGrayscaleValue; // grayscale value for one pixel

// Loop based on image height (y)
for (int y = 0; y < bmpData.Height; y++)
{
// Find starting memory pointer address for each row
row = (byte*)bmpData.Scan0 + (y * bmpData.Stride);

// Loop through the row (x)
for (int x = 0; x < bmpData.Width; x++)
{
rowElement = row + (x * 3); // find the pixel pointer address

intGrayscaleValue = (rowElement[0] + rowElement[1] + rowElement[2]) / 3;

// assign grayscale value for each pixel element
rowElement[0] = (byte)intGrayscaleValue;
rowElement[1] = (byte)intGrayscaleValue;
rowElement[2] = (byte)intGrayscaleValue;
}
}

// Unlock the bitmap from memory
_bmp.UnlockBits(bmpData);
}

}
catch (Exception ex)
{
throw ex;
}
}