Image Processing is a widely used technique that allows capturing the information of an image and transform it to derive more refined information. This series of tutorials have two objectives: Describing the fundamentals of image processing using C# and generating an image processing library that can be used in .Net applications (C# and VB.net). Each chapter will include a new process that will be added to the library. The library will be released under BSD License. The project will be hosted at www.diphernet.com/web/improclib
The input to an image process is of course an image .Net class Bitmap contains the necessary information to read an image. Our first function will the next.
static public Bitmap LoadImage(string filename)
{
Bitmap bp;
bp = (Bitmap) Bitmap.FromFile(filename);
return bp;
}In this function first we create an Object of class Bitmap then we use the function FromFile() to read the file wich contains the Image and return an Object of class Bitmap wich contains the data read form the image file. The function returns this object.
Once we have processed our images we will want to store the result of that process. In many process the result is another image. The following funciton stores the information of a Bitmap object in an image file.
public static void SaveImage(Bitmap b,string filename)
{
b.Save(filename);
}
Now we are going to create our first function. This function will invert the colors of our image. The mathematical equation that we will apply is:
![]()
Where orig(i,j) represents the pixel of row i , column j of the source image and dest(i,j) the same pixel of the destination image.
public static bool Invert(Bitmap b)
{
// GDI+ still lies to us - the return format is BGR, NOT RGB.
//BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
// ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, b.PixelFormat);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
int nbands = 3;
if (b.PixelFormat == PixelFormat.Format24bppRgb)
nbands = 3;
if (b.PixelFormat == PixelFormat.Format8bppIndexed)
nbands = 1;
try
{
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - b.Width * nbands;
int nWidth = b.Width * nbands;
for (int y = 0; y < b.Height; ++y)
{
for (int x = 0; x < nWidth; ++x)
{
p[0] = (byte)(255 - p[0]);
++p;
}
p += nOffset;
}
}
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show("error escribiendo" + e.Message + " bandas " + nbands);
return false;
}
b.UnlockBits(bmData);
return true;
}
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, b.PixelFormat);
The function LockBits allows exclusive acces to the image data. This is necessary to perform unsafe operations with pointers. Pointers allows very fast access to the data this is needed when dealing with images with medium-big sizes. The following lines obtains a ponter to the data.
System.IntPtr Scan0 = bmData.Scan0;
To sucessfully interpret the information refered by the pointe we must obtain the formata of the image. The format the describes the order of the bytes in the data. Each image can have 1 or more color chanels and each channel can have a diferent number of bits. In the following exambles we check for 2 formats the first is a typical color image format and the second is a grayscale image with just 1 color channel with 8 bits.
int nbands = 3;
if (b.PixelFormat == PixelFormat.Format24bppRgb)
nbands = 3;
if (b.PixelFormat == PixelFormat.Format8bppIndexed)
nbands = 1;
Next block do the real job, first declare de unsafe block
unsafe
{
Then we obtan an byte pointer to the thata through a cast to scan0:
byte* p = (byte*)(void*)Scan0;
Rows of the image are concatenated in memory therefore we must obtain the real size of the rows. Sometimes the size of a row is ajusted to a stride. To estimate the number of bytes added to each row we do
int nOffset = stride - b.Width * nbands;
Next step is constructing a nested for loop were we access each each pixell secuentially. Inside the loop we apply the transformation using pointer p,
for (int y = 0; y < b.Height; ++y)
{
for (int x = 0; x < nWidth; ++x)
{
p[0] = (byte)(255 - p[0]);
++p;
}
p += nOffset;
}
To end we must unlock the image data to allow other processes access it.
b.UnlockBits(bmData);
Here finish this tutorial. We will introduce more advanced functions in next chapters.