Friday, 4 April 2014

C# How to: Image filtering by directly manipulating Pixel ARGB values

Article purpose

In this article we discover creating basic  filters implemented by directly manipulating the ARGB colour values associated with an ’s pixels. The different types of filters discussed are: GrayscaleTransparencyImage Negative and Sepia tone. All filters are implemented as targeting the  class, as well as the  class as the result of inheritance and upcasting.

ARGB Overview

ARGB is an abbreviation for the term: “AlphaRedGreen and Blue”. ARGBrefers to four colour components represented by each pixel that forms part of an . In C# 32 bit ARGB  are a fairly common occurrence,  each pixel being of a fixed size, namely 32 bits or 4 , which also equates to a standard integer. Each colour component consists of 8 bits or 1 , equating to a range of possible values starting at 0 inclusive and a maximum value of 255 inclusive. It can thus be logically deduced that each of the four ARGBcomponents can be expressed as a value ranging from 0 to 255 inclusive.
A pixel’s alpha component represents a level of transparency, 255 being no transparency and 0 being completely transparent. The combination of Red,Green and Blue values together represent a single colour, the colour associated with an individual pixel.
We can apply filtering on an  by manipulating the individual AlphaRed,Green and Blue components of each pixel.

Extracting the ARGB components of each pixel in an image

In C# an ’s ARGB components are actually stored in the format Blue,GreenRedAlpha. Before attempting to extract each pixel’s individual components we need to ensure that our source  is in fact formatted as a 32Bit ARGB . The source code snippet listed below converts source images into 32Bit ARGB formatted :
private static Bitmap GetArgbCopy(Image sourceImage)
{
    Bitmap bmpNew = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format32bppArgb);

  
    using(Graphics graphics = Graphics.FromImage(bmpNew))
    {
        graphics.DrawImage(sourceImage, new Rectangle (0, 0, bmpNew.Width, bmpNew.Height), new  Rectangle (0, 0, bmpNew.Width, bmpNew.Height), GraphicsUnit.Pixel);
        graphics.Flush();
    }

  
    return bmpNew;
}
 
The GetArgbCopy method creates a blank memory  having the same size dimensions as the source . The newly created  is explicitly specified to conform to a 32Bit ARGB format. By making use of a object of which the context is bound to the new  instance the source code draws the original image to the new .

The Transparency Filter

The transparency filter is intended to create a copy of an , increase the copy’s level of transparency and return the modified copy to the calling code. Listed below is source code which defines the CopyWithTransparency.
public static Bitmap CopyWithTransparency(this Image sourceImage, byte alphaComponent = 100)
{
    Bitmap bmpNew = GetArgbCopy(sourceImage);
    BitmapData bmpData = bmpNew.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

  
    IntPtr ptr = bmpData.Scan0;

  
    byte[] byteBuffer = new byte[bmpData.Stride * bmpNew.Height];

  
    Marshal.Copy(ptr, byteBuffer, 0, byteBuffer.Length);

  
    for (int k = 3; k < byteBuffer.Length; k += 4)
    {
        byteBuffer[k] = alphaComponent;
    }

  
    Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length);

  
    bmpNew.UnlockBits(bmpData);

  
    bmpData = null;
    byteBuffer = null;

  
    return bmpNew;
}
As discussed earlier, the CopyWithTransparency method creates a 32BitARGB formatted copy of the specified source  by invoking the methodGetArgbCopy. In order to extract the  pixel data we implement the class and the  method defined by the  class. Next we create an  which references the very first pixel. points to the memory address of the first pixel. Next the source code instantiates a  array which will used to represent pixel components.
The  method copies  from memory, starting at the address of the first pixel continuing up until the last pixel. The alpha component can be specified by the calling code, or if not specified defaults to a value of 100 due to being a .
How much transparency results from an alpha component of 100? If the maximum value is set at 255 representing no transparency 100 expressed as a percentage of 255 equates to roughly 39.2%. Defining an alpha value of 100 will thus result in an image being roughly 60% transparent.
Notice how the for loop only affects every fourth array element, beginning at index 3. Each element in the  array represents a pixel colour component, either AlphaRedGreen or Blue. Remember as mentioned earlier, the components are in fact stored in the format BlueGreenRedAlpha. Every four array elements together represents one pixel. We only want to change the value of Alpha components, thus we start at the first pixel’s Alphacomponent at index 3 and then continue to iterate through the  array, incrementing the index by four with each loop.
After having updated every pixel’s Alpha component the modified  array is copied back into the actual  object and returned to the calling code.
Transparency Filter
Image Filters Transparency

The Grayscale Filter

The  filter operates in a fashion similar to the transparency filter discussed in the previous section. The following details the source code implementation of the CopyAsGrayScale .
public static Bitmap CopyAsGrayscale(this Image sourceImage)
{
    Bitmap bmpNew = GetArgbCopy(sourceImage);
    BitmapData bmpData = bmpNew.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

  
    IntPtr ptr = bmpData.Scan0;

  
    byte[] byteBuffer = new byte[bmpData.Stride * bmpNew.Height];

  
    Marshal.Copy(ptr, byteBuffer, 0, byteBuffer.Length);

  
    float rgb = 0;

  
    for (int k = 0; k < byteBuffer.Length; k += 4)
    {
        rgb = byteBuffer[k] * 0.11f;
        rgb += byteBuffer[k+1] * 0.59f;
        rgb += byteBuffer[k+2] * 0.3f;

  
        byteBuffer[k] = (byte)rgb;
        byteBuffer[k + 1] = byteBuffer[k];
        byteBuffer[k + 2] = byteBuffer[k];

  
        byteBuffer[k + 3] = 255;
    }

  
    Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length);

  
    bmpNew.UnlockBits(bmpData);

  
    bmpData = null;
    byteBuffer = null;

  
    return bmpNew;
}
 
Notice how this filter starts iterating the pixel components at index 0. The source code assigns a weight to each colour component. The  filter is achieved by adding together 11% Blue59% Green and 30% Red, then assigning the total value to each colour component. Transparency is set to 255, effectively disabling any level of transparency.
Grayscale Filter
ImageFilters_Grayscale

The Sepia Tone Filter

The sepia tone filter is implemented in the CopyAsSepiaTone. Notice how this method follows the same convention as the previously discussed filters. The source code listing is detailed below.
public static Bitmap CopyAsSepiaTone(this Image sourceImage)
{
    Bitmap bmpNew = GetArgbCopy(sourceImage);
    BitmapData bmpData = bmpNew.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

  
    IntPtr ptr = bmpData.Scan0;

  
    byte[] byteBuffer = new byte[bmpData.Stride * bmpNew.Height];

  
    Marshal.Copy(ptr, byteBuffer, 0, byteBuffer.Length);

  
    byte maxValue = 255;
    float r = 0;
    float g = 0;
    float b = 0;

  
    for (int k = 0; k < byteBuffer.Length; k += 4)
    {
        r = byteBuffer[k] * 0.189f + byteBuffer[k + 1] * 0.769f + byteBuffer[k + 2] * 0.393f;
        g = byteBuffer[k] * 0.168f + byteBuffer[k + 1] * 0.686f + byteBuffer[k + 2] * 0.349f;
        b = byteBuffer[k] * 0.131f + byteBuffer[k + 1] * 0.534f + byteBuffer[k + 2] * 0.272f;

  
        byteBuffer[k+2] = (r > maxValue ? maxValue : (byte)r);
        byteBuffer[k + 1] = (g > maxValue ? maxValue : (byte)g);
        byteBuffer[k] = (b > maxValue ? maxValue : (byte)b);
    }

  
    Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length);

  
    bmpNew.UnlockBits(bmpData);

  
    bmpData = null;
    byteBuffer = null;

  
    return bmpNew;
}
The formula used to calculate a sepia tone differs significantly from the filter discussed previously. The formula can be simplified as follows:
  • Red Component: Sum total of: 18.9% blue76.9% green39.3% red
  • Green Component: Sum total of: 16.8% blue68.6% green34.9% red
  • Blue Component: Sum total of: 13.1% blue53.4% green27.2% red
If any of the totalled values exceeds the value of 255 that value is then defined as 255.
Sepia Tone Filter
ImageFilters_Sepia

The Negative Image Filter

Non digital film based cameras produce what is referred to as negatives, which have to be developed into printed photographs using various chemical processes. (I shudder at the thought of labelling 35mm film cameras as “old school” or vintage. I’m old enough to remember a time when film cameras were the norm yet young enough to never have used a rotary phone.)
We can implement an  filter that resembles film negatives by literally inverting every pixel’s colour components. It is fairly simple to invert colour data by implementing the bitwise compliment operator ~, the result being each bit will be reversed. Note: Only colour components are inverted, the Alphacomponent remains unchanged. Listed below is the source code implementation of the CopyAsNegative extension method.
public static Bitmap CopyAsNegative(this Image sourceImage)
{
    Bitmap bmpNew = GetArgbCopy(sourceImage);
    BitmapData bmpData = bmpNew.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

 
    IntPtr ptr = bmpData.Scan0;

 
    byte[] byteBuffer = new byte[bmpData.Stride * bmpNew.Height];

 
    Marshal.Copy(ptr, byteBuffer, 0, byteBuffer.Length);
    byte[] pixelBuffer = null;

 
    int pixel = 0;

 
    for (int k = 0; k < byteBuffer.Length; k += 4)
    {
        pixel = ~BitConverter.ToInt32(byteBuffer, k);
        pixelBuffer = BitConverter.GetBytes(pixel);

 
        byteBuffer[k] = pixelBuffer[0];
        byteBuffer[k + 1] = pixelBuffer[1];
        byteBuffer[k + 2] = pixelBuffer[2];
    }

 
    Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length);

 
    bmpNew.UnlockBits(bmpData);

 
    bmpData = null;
    byteBuffer = null;

 
    return bmpNew;
}
The negative filter formula extracts the four ARGB components storing the result as an integer value which represents a pixel. All of the pixel’s bits are reversed using the bitwise compliment operator. The resulting integer value is converted back into the four pixel components and assigned to replace the pixel’s original values, all except the alpha component.
Negative Filter
ImageFilters_Negative

The implementation

The  filters described in this article are all implemented by means of a filtering is applied by selecting the corresponding radio button. The source  loaded from the file system serves as input to the various  filter methods, the filtered image copy returned will be displayed next to the original source .
private void OnCheckChangedEventHandler(object sender, EventArgs e)
{
    if (picSource.BackgroundImage != null)
    {
        if (rdGrayscale.Checked == true)
        {
            picOutput.BackgroundImage = picSource.BackgroundImage.CopyAsGrayscale();
        }
        else if (rdTransparency.Checked == true)
        {
            picOutput.BackgroundImage = picSource.BackgroundImage.CopyWithTransparency();
        }
        else if (rdNegative.Checked == true)
        {
            picOutput.BackgroundImage = picSource.BackgroundImage.CopyAsNegative();
        }
        else if (rdSepia.Checked == true)
        {
            picOutput.BackgroundImage = picSource.BackgroundImage.CopyAsSepiaTone();
        }
    }
}
 


No comments:

Post a Comment