Home Course Index << Prev Next >> PDF Version of this Page

Course IPCis: Image Processing with C#
Chapter C2a: The Complete Code of the Histogram Project with Threads


Copyright © by V. Miszalok, last update: 06-05-2010

Copy all this code into an empty Form1.cs of a new Windows Application C#-project HistoWithThreads and clear Form1.Designer.cs and Program.cs.
This code is 95% identical with CIPCisHisto_Code.htm which just has one main thread. The differences are highlighted in red.
The trick is to slice the image horizontally into an upper part processed by thread1 and a lower part processed by thread2. With DualCore-CPUs this code runs 200% faster than the code without threading. Try it out with big images!
Problem+Solution 1: A thread-subroutine just accepts one single object as parameter. Simple solution: Pass just one integer number 1 or 2 to determine what thread to run and set the appropriate parameters inside the subroutine.
Problem+Solution 2: Both threads need the properties bmp.Width and bmp.Height and the value threshold. In order to avoid access-collisions to bmp and to avoid parameters, we define int w=bmp.Width;, int h=bmp.Height; and Byte threshold; as global variables.
Caution: Solutions 1 and 2 are far from being professional, but this is an introduction for newcomers who may be grateful for the slim code.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Threading;

public class Form1 : Form
{ [STAThread] static void Main() { Application.Run( new Form1() );   }
  Brush bbrush = SystemBrushes.ControlText;
  Brush wbrush = new SolidBrush( Color.White );
  Pen   bpen   = SystemPens.ControlText;
  Pen   rpen   = new Pen( Color.Red );
  Bitmap bmp, bmp_binary, bmp_histo;
  BitmapData binaryData; //Descriptor of the Binary Output Image
  Byte[,] grayarray;     //2D-Byte-Array
  Int32[] Histogram = new Int32[256];
  Rectangle histo_r = new Rectangle( 0,0,257,101 );
  Graphics g, g_histo;
  Byte[]  ORmask = { 128,  64,  32,  16,   8,   4,   2,   1 };// 1 bit  each
  Byte[] ANDmask = { 127, 191, 223, 239, 247, 251, 253, 254 };// 7 bits each
  Byte threshold;
  Int32 w, h; //bmp.Width and bmp.Height

  public Form1()
  { MenuItem miRead = new MenuItem( "&Read", new EventHandler( MenuFileRead ) );
    MenuItem miExit = new MenuItem( "&Exit", new EventHandler( MenuFileExit ) );
    MenuItem miFile = new MenuItem( "&File", new MenuItem[] { miRead, miExit } );
    Menu = new System.Windows.Forms.MainMenu( new MenuItem[] { miFile } );
    Text = "Histo1";
    SetStyle( ControlStyles.ResizeRedraw, true );
    Width  = 800;
    Height = 600;
  }

  private void MenuFileRead( object obj, EventArgs ea )
  { OpenFileDialog dlg = new OpenFileDialog();
    if ( dlg.ShowDialog() != DialogResult.OK ) return;
    try
    { Cursor.Current = Cursors.WaitCursor;
      bmp = (Bitmap)Image.FromFile( dlg.FileName );
      w = bmp.Width; h = bmp.Height;
      GenerateTheHistogram();
      Cursor.Current = Cursors.Arrow;
      Invalidate();
    } catch {}
  }

  private void GenerateTheHistogram()
  { if ( bmp == null ) return;
    bmp_binary = new Bitmap( w, h, PixelFormat.Format1bppIndexed );
    grayarray = new Byte[h, w];
    Color color;
    for ( Int32 y=0; y < h; y++ )
      for ( Int32 x=0; x < w; x++ )
      { color = bmp.GetPixel( x, y );
        Int32 gray = ( color.R + color.G + color.B ) / 3;
        grayarray[y, x] = (Byte)gray;
        Histogram[gray]++;
      }
    Int32 hmax = 0;
    for ( Int32 i=0; i < 256; i++ )
      if ( Histogram[i] > hmax ) hmax = Histogram[i];
    for ( Int32 i=0; i < 256; i++ )
      Histogram[i] = (100*Histogram[i]) / hmax;
    bmp_histo = new Bitmap( histo_r.Width, histo_r.Height, PixelFormat.Format32bppRgb );
    g_histo = Graphics.FromImage( bmp_histo );
    g_histo.FillRectangle( wbrush, 0,0,256,100 );
    g_histo.DrawString( "click here and move !", Font, bbrush, 1, 1 );
    for ( Int32 i=0; i <256; i++ ) g_histo.DrawLine( bpen, i, 100, i, 100 - Histogram[i] );
    g_histo.DrawRectangle( rpen, 0,0,256,100 );
  }

  private void MenuFileExit( object obj, EventArgs ea )
  { Application.Exit(); }

  protected override void OnMouseMove( MouseEventArgs e )
  { if ( e.Button == MouseButtons.None ) return;
    if ( !histo_r.Contains( e.X, e.Y ) ) return;
    if ( bmp == null ) return;
    threshold = (Byte)(e.X - histo_r.X);
    //lock bmp_binary from being shifted in memory by the garbage collector
    binaryData = bmp_binary.LockBits( new Rectangle( 0,0,w,h ),
                 ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed );
    Thread th1 = new Thread( myThreads ); th1.Start( 1 );
    Thread th2 = new Thread( myThreads ); th2.Start( 2 );
    th1.Join(); th2.Join();
    bmp_binary.UnlockBits( binaryData ); // unlock bmp_binary
    g = CreateGraphics();
    g.DrawImage( bmp_binary, ClientRectangle );
    g.DrawImage( bmp_histo, histo_r );
    g.DrawLine( rpen, histo_r.X+threshold, histo_r.Y,
                      histo_r.X+threshold, histo_r.Y+histo_r.Height-1 );
  }

  public void myThreads( object number )
  { // Default values for th1: process upper half of the image
    Int32 grayarrayOffset = 0, firstLine = 0, lastLine = h/2;
    if ( (Int32)number == 2 ) // values for th2: process lower half of the image
    { grayarrayOffset = (h/2) * w; firstLine = h/2; lastLine = h; }
    unsafe
    { Byte* p2fix, p2row, p2run;
      fixed ( Byte* p1fix = grayarray )
      { Byte* p1run = p1fix + grayarrayOffset;
        p2fix = (byte*)binaryData.Scan0;
        for ( int y = firstLine; y < lastLine; y++ )
        { p2row = p2fix + y * binaryData.Stride;
          for ( int x=0; x < w; x++ )
          { p2run = p2row + x / 8;
            if ( *p1run++ > threshold ) *p2run |=  ORmask[ x % 8 ];
            else                        *p2run &= ANDmask[ x % 8 ];
          } // end of for x
        }   // end of for y
      }     // end of fixed, end of p1fix
    } // end of unsafe
  }

  protected override void OnMouseUp(MouseEventArgs e)
  { Invalidate(); }

  protected override void OnPaint( PaintEventArgs e )
  { if ( bmp == null )
    { e.Graphics.DrawString( "Open an Image File !", Font, bbrush, 0, 0 ); return; }
    e.Graphics.DrawImage( bmp, ClientRectangle );
    histo_r.X  = ClientRectangle.Width  - histo_r.Width  - 10;
    histo_r.Y  = ClientRectangle.Height - histo_r.Height - 10;
    e.Graphics.DrawImage( bmp_histo, histo_r );
  }
}