How to: Create an image deconstructor (C#).

Published:
Greetings!
For my first article, I decided do something that many people actually think it's hard: Image handling.  We'll be doing the simplest possible form of image decomposition -- breaking out the red, green, and blue components and creating variations of the the original image that are:
GrayScale
RedScale
GreenScale
BlueScale
It's a very simple code that does a very simple task

Editor's Note:
Although the author uses the term, decompose, this article is not about actual image decomposition.  That term usually indicates complex mathematical processing to locate edges and so forth.  This article describes a simple manipulation of the RGB color components of the image's individual pixels.

Here we go.

The Form
Create a form similar to this (the Lorem Ipsum at the corner is not necessary, of course):
 The form of our application.You are not obligated to write its labels in Portuguese, of course.   The white squares are nothing but a PictureBox with a SingleFixed border, white BackColor and a label ahead it.

The "Open" button
For the "Open" button ("Abrir..." in the image above), write the following code as the Click event:
private void button2_Click(object sender, EventArgs e)
                              {
                                  OpenFileDialog OpenDiag = new OpenFileDialog();
                                  OpenDiag.Filter = "Bitmap|*.bmp|JPeg|*.jpeg|JPeg|*.jpg|Portable Network Graphics|*.png";
                                  if (OpenDiag.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                                  {
                                      pictureBoxOriginal.Image = Image.FromFile(OpenDiag.FileName);
                                      pictureBoxOriginal.BringToFront();
                                  }
                              }

Open in new window


A basic "enum"
We need this enum because this will tell the "Decompose" method (that we will create) which color you want to separate from the others.

Very simple:
private enum RGB
                              {
                                  /// <summary>
                                  /// Escala de vermelho
                                  /// </summary>
                                  Red,
                                  /// <summary>
                                  /// Escala de verde
                                  /// </summary>
                                  Green,
                                  /// <summary>
                                  /// Escala de azul
                                  /// </summary>
                                  Blue,
                                  /// <summary>
                                  /// Escala de cinza
                                  /// </summary>
                                  Gray
                              }

Open in new window


NOTE: The value Gray will tell the "Decompose" method to turn it grayscale; that means, no color.  But if we literally take all colors away the result will be a white square (that will be explained in the "Decompose" method)

The main reason of why you're reading this:  The "Decompose" method
The code is very simple but I'll explain part by part so everyone can follow.

First create the method:
/// <summary>
                      /// Separates a specific color of an image from another or take 'em all without erasing the image
                      /// </summary>
                      /// <param name="Original">The image to decompose</param>
                      /// <param name="WhichColor">Color to separate</param>
                      /// <returns>The image only in a color tone or grayscale</returns>
                      private Image Decompose(Bitmap Original, RGB WhichColor)
                              {
                                   //return Decomposed;
                              }

Open in new window

That's right, it must return an image.

Now inside the method, let's start by the following part:
//This is the bitmap we will create and futurely, convert  to Image (by casting) and return.
                      Bitmap BDecomposed = new Bitmap(Original.Width, Original.Height);
                      //Also create an integer that will be used by one of our loops.
                      int X = 0;

Open in new window

Now the loops.
We will create two loops, one inside another.
The outer loop is has nothing, but the inner loop and two lines of code see:
while (X < Original.Width)
                      {
                          int Y = 0; //This variable will be used by the "inner" loop.
                          //The inner loop
                          X++;
                      }

Open in new window

So, as far we have this in our "Decompose" Method:

private Image Decompose(Bitmap Original, RGB WhichColor)
                      {
                          Bitmap BDecomposed = new Bitmap(Original.Width, Original.Height);
                          int X = 0;
                          while (X < Original.Width)
                          {
                              int Y = 0; //This variable will be used by the "inner" loop.
                              //The inner loop
                              X++;
                              //return Decomposed;
                          }
                      }

Open in new window


Now the Inner loop.
Let's start with this part:
int Ro = Original.GetPixel(X, Y).R;
                      int Go = Original.GetPixel(X, Y).G;
                      int Bo = Original.GetPixel(X, Y).B;
                      
                      int Rn = 0;
                      int Gn = 0;
                      int Bn = 0;

Open in new window

The Xo variable are the Red, Green and Blue values of the original pixel in that position and the Xn variables are the Red, Green and Blue values for our new pixel color.

Now we'll use that enum we created earlier. In this case the Decompose method parameter for that enum (that I called RGB) is called "WhichColor".

Before we proceed with this code, let's remember that a computer deals with colors like lighting; that means this way:

Red(255) + Green(255) + Blue(255) = White
Red(255) + Green(255) + Blue(0) = Yellow
Red(0) + Green(255) + Blue(255) = Cyan
Red(0) + Green(0) + Blue(0) = Black

Brings some memories from high school doesn't?
These values goes from 0 to 255, and by changing these values we can get any color.

Now, let's write the code that will set the Xn variables; that means, the color of the pixel currently being worked on in our new Bitmap.

if (WhichColor == RGB.Red)
                      {
                          Rn = (Ro + Go + Bo) / 3;
                      }
                      else if (WhichColor == RGB.Green)
                      {
                          Gn = (Ro + Go + Bo) / 3;
                      }
                      else if (WhichColor == RGB.Blue)
                      {
                          Bn = (Ro + Go + Bo) / 3;
                      }
                      else if (WhichColor == RGB.Gray)
                      {
                          Rn = (Ro + Go + Bo) / 3;
                          Gn = (Ro + Go + Bo) / 3;
                          Bn = (Ro + Go + Bo) / 3;
                      }

Open in new window

Let's understand it.

If  (WhichColor == RGB.Gray)
Remember that every time that the R, G and B values of a color are the same, no color prevails, so we have a gray tone.  And the grey tone is lighter (or darker) as the values become higher (or lower) -- until it gets to 255 (or 0) which means white (or black).

So if we want to get a gray tone (where the R, G and B values are all equal) equivalent to a color tone (where the R, G and B values are different), we have to get an average and set the R, G, and B values to this average.  Note that the operation used is a simple math operation to get averages.

If  (WhichColor == RGB.OtherColor)
So we want to colorize an image with Red for example. It's simple but many people mistake the operation.  Many people think that all we've got to do is to set the G and B values of each pixel to 0 and leave the R value untouched. Actually no. That would cause a different effect...  That would REALLY separate that color from the others, so pixels where R = 0 will simply become black.

In our case, we want to colorize the image to a color scale, so we do something just a bit different: We take the average of the three values, but we will only set the color we want (example Red) as that average, and the others (example Green and Blue) are set to 0. (Similar to the method I just mentioned).

In the code above we only have to set the color we want to the average, no need to set the remaining color values to 0 since it was set to 0 when it was created.

So for now we have this in the "Inner' loop´:
// The variable used by the inner loop that is set in the outer loop
                      int Y = 0;
                      while (Y < Original.Height)
                      {
                          int Ro = Original.GetPixel(X, Y).R;
                          int Go = Original.GetPixel(X, Y).G;
                          int Bo = Original.GetPixel(X, Y).B;
                      
                          int Rn = 0;
                          int Gn = 0;
                          int Bn = 0;
                                          
                          if (WhichColor == RGB.Red)
                          {
                              Rn = (Ro + Go + Bo) / 3;
                          }
                          else if (WhichColor == RGB.Green)
                          {
                              Gn = (Ro + Go + Bo) / 3;
                          }
                          else if (WhichColor == RGB.Blue)
                          {
                              Bn = (Ro + Go + Bo) / 3;
                          }
                          else if (WhichColor == RGB.Gray)
                          {
                              Rn = (Ro + Go + Bo) / 3;
                              Gn = (Ro + Go + Bo) / 3;
                              Bn = (Ro + Go + Bo) / 3;
                          }
                      }

Open in new window

Now that we've got the color, it's easy:  Just set the color of the pixel we're currently working on in our new image to the color we've got:

Color NewTone = Color.FromArgb(Rn, Gn, Bn);
                      
                      BDecomposed.SetPixel(X, Y, GrayTone);
                      
                      //This is to set the loop to work on the next pixel in the coordinate Y 
                      Y++;

Open in new window

So, the whole Decompose method looks like this:
private Image Decompose(Bitmap Original, RGB WhichColor)
                      {
                          Bitmap BDecomposed = new Bitmap(Original.Width, Original.Height);
                          //We will set it to work on the pixels with the X coordinate 0, that means, the first column of pixels in the image.
                          int X = 0;
                          while (X < Original.Width)
                          {
                              //Everytime the outer loop restarts, this will set the inner loop to start from the top of the column of pixels.
                              int Y = 0;
                              while (Y < Original.Height)
                              {
                                  int Ro = Original.GetPixel(X, Y).R;
                                  int Go = Original.GetPixel(X, Y).G;
                                  int Bo = Original.GetPixel(X, Y).B;
                      
                                  int Rn = 0;
                                  int Gn = 0;
                                  int Bn = 0;
                                  
                                  if (WhichColor == RGB.Red)
                                  {
                                      Rn = (Ro + Go + Bo) / 3;
                                  }
                                  else if (WhichColor == RGB.Green)
                                  {
                                      Gn = (Ro + Go + Bo) / 3;
                                  }
                                  else if (WhichColor == RGB.Blue)
                                  {
                                      Bn = (Ro + Go + Bo) / 3;
                                  }
                                  else if (WhichColor == RGB.Gray)
                                  {
                                      Rn = (Ro + Go + Bo) / 3;
                                      Gn = (Ro + Go + Bo) / 3;
                                      Bn = (Ro + Go + Bo) / 3;
                                  }
                                  
                                  Color GrayTone = Color.FromArgb(Rn, Gn, Bn);
                      
                                  BDecomposed.SetPixel(X, Y, GrayTone);
                                 
                                  //This sets the inner loop to work with the next pixel in that column
                                  Y++;
                              }
                              //This will pass the current column of pixels being worked on by the inner loop, to the next column in the image.
                              X++;
                          }
                          //This converts our created Bitmap to an Image
                          Image Decomposed = (Image)BDecomposed;
                          return Decomposed;
                      }

Open in new window


So that's the Decompose method.  Now, for the "Decompose" button, set the following code as the Click event:
private void button1_Click(object sender, EventArgs e)
                      {
                          pictureBoxGrayScale.Image = Decompose((Bitmap)pictureBoxOriginal.Image, RGB.Gray);
                          pictureBoxGrayScale.BringToFront();
                      
                          pictureBoxRed.Image = Decompose((Bitmap)pictureBoxOriginal.Image, RGB.Red);
                          pictureBoxRed.BringToFront();
                      
                          pictureBoxGreen.Image = Decompose((Bitmap)pictureBoxOriginal.Image, RGB.Green);
                          pictureBoxGreen.BringToFront();
                      
                          pictureBoxBlue.Image = Decompose((Bitmap)pictureBoxOriginal.Image, RGB.Blue);
                          pictureBoxBlue.BringToFront();
                      }

Open in new window

NOTE: The "BringToFront" is being called so the Labels won't be ahead the picture box, after its image is set.

This method takes about 1.5 seconds to colorize a 640x480 image.  That means about 6 seconds to make the grayscale, redscale, greenscale, and blue scale of a 640x480 image.

You can put the method to be run in another thread and ask the user of the application to wait, or else, it will stop the program and an impatient user would think it isn't going to work and close it.

I tried to upload the whole project but the Expert Exchange refused to accept several file extensions, so I uploaded only the Form Source code.
Form-1-Source-Code--C--.zip
Thanks for reading, I hope it was useful! ;)
0
5,011 Views

Comments (2)

CERTIFIED EXPERT
Most Valuable Expert 2011
Top Expert 2015

Commented:
I vote for a change in the article's title. When I read it on the TA carousel, I thought it was talking about creating a finalizer or destructor for the Image class (which didn't quite make sense to me).

Just a suggestion  : )
Chinmay PatelChief Technology Ninja
CERTIFIED EXPERT
Distinguished Expert 2021

Commented:
Absolutely agree with kaufmed.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.