Home | Course Index | << Prev | Next >> | PDF Version of this Page | C2 Complete Code |
Course IPCis: Image Processing with C#
|
using System; //Home of the base class of all classes "System.Object" and of all primitive data types such as Int32, Int16, double, string.
using System.Drawing; //Home of the "Graphics" class and its drawing methods such as DrawStirng, DrawLine, DrawRectangle, FillClosedCurve etc.
using System.Drawing.Imaging; //Home of the Bitmap-class.
using System.Windows.Forms; //Home of the "Form" class (base class of our main window Form1) and its method Application.Run.
Entry public class Form1 : Form |
//We derive our window Form1 from the class Form, which the compiler automatically finds in the System.Windows.Forms namespace.
[STAThread] static void Main() { Application.Run( new Form1() ); //Create a single thread instance of Form1 and ask the operating system to start it as main window of our program.
Brush bbrush = SystemBrushes.ControlText; //Create a reference to an already existing Brush object (just a programming shortcut).
Brush wbrush = new SolidBrush( Color.White ); //Create a new global Brush object.
Pen bpen = SystemPens.ControlText; //Create a reference to an already existing Pen object (just a programming shortcut).
Pen rpen = new Pen( Color.Red ); //Create a new global Pen object.
Bitmap bmp, bmp_binary, bmp_histo; //Three Bitmap-objects: bmp = original image, bmp_binary = binary image, bmp_histo = small histogram image in the right lower corner of bmp and bmp_binary.
BitmapData binaryData; //for Versions 2 and 3 //Clone of bmp_binary locked at a fixed position in memory in order to be addressed with a pointer.
Byte[,] grayarray; //2D-Byte-Array //Raster matrix containing bmp as monochrome image. Any gray value is the average of its three correspondent bmp-RGB-values: grayarray[x,y] = ( bmp[x,y].R + bmp[x,y].G + bmp[x,y].B ) / 3;
Int32[] Histogram = new Int32[256]; //Histogram array of fixed length = 256.
Rectangle histo_r = new Rectangle( 0,0,257,101 ); //Size of bmp_histo.
Graphics g, g_histo; //g = whole client area graphics object, g_histo = small graphics object just covering bmp_histo.
Byte[] ORmask = { 128, 64, 32, 16, 8, 4, 2, 1 };// 1 bit each //for Version 3 //Array of byte masks = 10000000,01000000,00100000,00010000,00001000,00000100,00000010,00000001. Needed for setting single bits in a byte.
Byte[] ANDmask = { 127, 191, 223, 239, 247, 251, 253, 254 };// 7 bits each //for Version 3 //Array of byte masks = 01111111,10111111,11011111,11101111,11110111,11111011,11111101,11111110. Needed for removing single bits from a byte.
Constructor public Form1() |
{ MenuItem miRead = new MenuItem( "&Read", new EventHandler( MenuFileRead ) ); //Purpose: Open an image file.
MenuItem miExit = new MenuItem( "&Exit", new EventHandler( MenuFileExit ) ); //Purpose: Leave the program.
MenuItem miFile = new MenuItem( "&File", new MenuItem[] { miRead, miExit } ); //Complete menu with two entries.
Menu = new System.Windows.Forms.MainMenu( new MenuItem[] { miFile } ); //Attach the menu to Form1.
Text = "Histo1"; //Blue title bar.
SetStyle( ControlStyles.ResizeRedraw, true ); //Redirect the OnResize-event to the OnPaint event handler.
Width = 800; //Initial window size.
Height = 600; //Initial window size. Raise the OnResize-event which calls protected override void OnPaint(...).
Event Handler private void MenuFileRead( object obj, EventArgs ea ) |
{ OpenFileDialog dlg = new OpenFileDialog(); //Call the standard modal OpenFileDialog-box.
if ( dlg.ShowDialog() != DialogResult.OK ) return; //Forget it if there was no reasonable user reaction.
Cursor.Current = Cursors.WaitCursor; //Change the mouse cursor to hour glass to inform the user that a time consuming process is underway.
bmp = (Bitmap)Image.FromFile( dlg.FileName ); //Read the image using the powerful Bitmap-class.
GenerateTheHistogram(); //Call a subroutine. (See some lines below.)
Cursor.Current = Cursors.Arrow; //Change back the mouse cursor to its normal form.
Invalidate(); //Call protected override void OnPaint(...) and show the image together with its histogram.
Function private void GenerateTheHistogram() |
{ if ( bmp == null ) return; //Get out if there is no image.
//bmp_binary = new Bitmap( bmp.Width, bmp.Height, PixelFormat.Format32bppRgb ); //Version 1 //Black and white coded by ARGB = 32 bits.
//bmp_binary = new Bitmap( bmp.Width, bmp.Height, PixelFormat.Format32bppRgb ); //Version 2 //Black and white coded by ARGB = 32 bits.
bmp_binary = new Bitmap( bmp.Width, bmp.Height, PixelFormat.Format1bppIndexed ); //Version 3 //Black and white coded by 1 bit.
grayarray = new Byte[bmp.Height, bmp.Width]; //Raster of gray values.
Color color; //ARGB = 32 bit variable.
for ( Int32 y=0; y < bmp.Height; y++ ) //for any row.
for ( Int32 x=0; x < bmp.Width; x++ ) //for any column.
{ color = bmp.GetPixel( x, y ); //Read a pixel.
Int32 gray = ( color.R + color.G + color.B ) / 3; //Average (R+G+B)/3.
grayarray[y, x] = (Byte)gray; //Store the gray value.
Histogram[gray]++; //Increment the corresponding gray column of the histogram
Int32 hmax = 0; //Begin with zero.
for ( Int32 i=0; i < 256; i++ ) //For all columns of the histogram.
if ( Histogram[i] > hmax ) hmax = Histogram[i]; //Find the highest column.
for ( Int32 i=0; i < 256; i++ ) //For all columns of the histogram.
Histogram[i] = (100*Histogram[i]) / hmax; //Reduce the maximal height to 100.
bmp_histo = new Bitmap( histo_r.Width, histo_r.Height, PixelFormat.Format32bppRgb ); //Create an artificial color image of 257x101.
g_histo = Graphics.FromImage( bmp_histo ); //Derive a graphics object from that 257x101 image.
g_histo.FillRectangle( wbrush, 0,0,256,100 ); //Fill the 257x101 image with a white background.
g_histo.DrawString( "click here and move !", Font, bbrush, 1, 1 ); //Write some text into the 257x101 image.
for ( Int32 i=0; i < 256; i++ ) g_histo.DrawLine( bpen, i, 100, i, 100 - Histogram[i] ); //Write the columns of the histogram into the 257x101 image.
g_histo.DrawRectangle( rpen, 0,0,256,100 ); //Draw a red frame around the 257x101 image.
Event Handler private void MenuFileExit( object obj, EventArgs ea ) |
{ Application.Exit(); } //Kill the current instance of program histo1.
Event Handler protected override void OnMouseMove( MouseEventArgs e ) |
{ if ( e.Button == MouseButtons.None ) return; //Do nothing when no mouse button is pressed.
if ( !histo_r.Contains( e.X, e.Y ) ) return; //Do nothing when the mouse is outside the 257x101 image.
if ( bmp == null ) return; //Do nothing when there is no image at all.
Byte threshold = (Byte)(e.X - histo_r.X); //Threshold = distance between the mouse X-position and the left border of the 257x101 image.
//Version 1 (no pointers but slow)*************************************** |
Both GetPixel- and SetPixel methods are slow, because they lock (and unlock) the Bitmap-object from being moved by the garbage collector which sometime tries to optimize memory usage. This locking and unlocking procedure at any call of GetPixel- or SetPixel is quite time consuming.
for ( Int32 y=0; y < bmp.Height; y++ ) //For any row.
for ( Int32 x=0; x < bmp.Width; x++ ) //For any column.
{ if ( grayarray[y ,x] > threshold ) //If brightness higher than threshold.
bmp_binary.SetPixel( x, y, Color.White ); //Set bmp_binary[x,y] to white.
else bmp_binary.SetPixel( x, y, Color.Black ); //Set bmp_binary[x,y] to black.
//Version 2 (fast pointers creating a memory wasting 32-bit binary image)* |
unsafe //In order to use fast pointers, we must declare the block to be unsafe and we have to explicitly set the compiler option /unsafe. The user of the program will be asked by his operating system if he agrees to run this unsafe code.
binaryData = bmp_binary.LockBits( new Rectangle( 0,0,bmp.Width,bmp.Height ), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb ); //Lock bmp_binary from being accidentially shifted in memory by the garbage collector. This is necessary in order to obtain a pointer to the first pixel of bmp_binary.
UInt32* p2fix, p2run; // pointers to binaryData = output image //Declaration of a fixed and a running pointer to binaryData = output image.
fixed ( Byte* p1fix = grayarray ) // lock grayarray in memory //The keyword fixed locks grayarray from being voluntarily shifted in memory by the garbage collector as LockBits(...) does with an image. At the same time it furnishes a pointer Byte* p1fix = grayarray.
{ Byte* p1run = p1fix; // running pointer to grayarray //p1fixcannot be incremented, therefore we must copy its content to another pointer Byte* p1run.
p2fix = (UInt32*)binaryData.Scan0; // pointer to output image //Scan0 furnishes a pointer to the first pixel of bmp_binary.
for ( int y=0; y < bmp.Height; y++ ) //For any column.
{ p2run = p2fix + y * bmp.Width; //p2run points to first byte in row y //Pointer to the leftmost pixel in the row/line.
for ( int x=0; x < bmp.Width; x++ ) //For any row.
{ if ( *p1run++ > threshold ) *p2run++ = 0xFFFFFF; // white //
1) Compare the source pixel with the threshold.
2) Shift the p1run-pointer to the next source pixel.
3) Set color white into the destination pixel.
4) Shift the p2run-pointer to the next destination pixel.
else *p2run++ = 0; // black //
1) Set color black into the destination pixel.
2) Shift the p2run-pointer to the next destination pixel.
bmp_binary.UnlockBits( binaryData ); //end of p2fix, unlock bmp_binary //The garbage collector can do whatever it wants with binaryData.
//Version 3 (fast pointers creating a 1-bit binary image)**************** |
unsafe //In order to use fast pointers, we must declare the block to be unsafe and we have to explicitly set the compiler option /unsafe. The user of the program will be asked by his operating system if he agrees to run this unsafe code.
binaryData = bmp_binary.LockBits( new Rectangle( 0,0,bmp.Width,bmp.Height ), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed ); //Lock bmp_binary from being voluntarily shifted in memory by the garbage collector. This is necessary in order to obtain a pointer to the first pixel of bmp_binary.
Byte* p2fix, p2row, p2run; // pointers to binaryData = output image //Declaration of a fixed and a running pointer to binaryData = output image.
fixed ( Byte* p1fix = grayarray ) // lock grayarray = input image in memory //The keyword fixed locks grayarray from being voluntarily shifted in memory by the garbage collector as LockBits(...) does with an image. At the same time it furnishes a pointer Byte* p1fix = grayarray.
{ Byte* p1run = p1fix; // running pointer to grayarray //p1fixcannot be incremented, therefore we must copy its content to another pointer Byte* p1run.
p2fix = (byte*)binaryData.Scan0; // pointer to output image //Scan0 furnishes a pointer to the first pixel of bmp_binary.
for ( int y=0; y < bmp.Height; y++ ) //For any column.
{ p2row = p2fix + y * binaryData.Stride; //p2row points to first byte in row y //Pointer to the leftmost pixel in the row/line. binaryData.Stride furnishes the real length of a line which is at least bmp_binary.Width/8. Furthermore binaryData.Stride automatically fills up missing bits because bit series do seldom end at byte borders.
for ( int x=0; x < bmp.Width; x++ ) //For any row.
{ p2run = p2row + x / 8; //Running byte pointer = pointer to leftmost byte in the row + (Int32)(bit_number/8). The remainder of this division (= bit_number modulo 8) will be used in the next two lines as index of both bit mask arrays.
if ( *p1run++ > threshold ) *p2run |= ORmask[ x % 8 ];//set 1 bit //1) Compare the source pixel with the treshold. 2) Shift the p1run-pointer to the next source pixel. 3) Select an ORmask depending on the remainder of (Int32)(bit_number/8). 4) Bitwise OR sets the 1-bit that the ORmask carries into the output image.
else *p2run &= ANDmask[ x % 8 ];//remove 1 bit //1) Select an ANDmask depending on the remainder of (Int32)(bit_number/8). 2) Bitwise AND sets the 0-bit that the ANDmask carries into the output image.
bmp_binary.UnlockBits( binaryData ); // end of p2fix, unlock bmp_binary //The garbage collector can do whatever it wants with binaryData.
//End of Version 3 ****************************************************** |
g = CreateGraphics(); //We need a current graphics object of the whole client area of Form1.
g.DrawImage( bmp_binary, ClientRectangle ); //Draw the black and white bmp_binary image stretched to fill the whole client area of Form1.
g.DrawImage( bmp_histo, histo_r ); //Draw the small fixed size bmp_histo image into the lower right corner of bmp_binary.
g.DrawLine( rpen, histo_r.X+threshold, histo_r.Y, histo_r.X+threshold, histo_r.Y+histo_r.Height-1 ); //Draw a vertical red line indicating the current threshold onto bmp_histo.
Event Handler protected override void OnMouseUp(MouseEventArgs e) |
{ Invalidate(); } //Erase bmp_binary and show the original bmp again.
Event Handler protected override void OnPaint( PaintEventArgs e ) |
if ( bmp == null ) { e.Graphics.DrawString( "Open an Image File !", Font, bbrush, 0, 0 ); return; } //Inform the user what to do, if there is no image at all.
e.Graphics.DrawImage( bmp, ClientRectangle ); //Draw the original bmp image stretched to fill the whole client area of Form1.
histo_r.X = ClientRectangle.Width - histo_r.Width - 10; //Horizontal position of the small fixed size bmp_histo image in the lower right corner of the client area.
histo_r.Y = ClientRectangle.Height - histo_r.Height - 10; //Vertical position of the small fixed size bmp_histo image in the lower right corner of the client area.
e.Graphics.DrawImage( bmp_histo, histo_r ); //Draw the small fixed size bmp_histo image into the lower right corner of the original bmp.