Posts Tagged '32Bit Argb'

C# How to: Bitmap Colour Substitution implementing thresholds

Article Purpose

This article is aimed at detailing how to implement the process of substituting the colour values that form part of a image. Colour substitution is implemented by means of a threshold value. By implementing a threshold a range of similar colours can be substituted.

Sample source code

This article is accompanied by a sample source code Visual Studio project which is available for download here.

Using the sample Application

The provided sample source code builds a Windows Forms application which can be used to test/implement the concepts described in this article. The sample application enables the user to load an file from the file system, the user can then specify the colour to replace, the replacement colour and the threshold to apply. The following image is a screenshot of the sample application in action.

BitmapColourSubstitution_Scaled

The scenario detailed in the above screenshot shows the sample application being used to create an where the sky has more of a bluish hue when compared to the original .

Notice how replacement colour does not simply appear as a solid colour applied throughout. The replacement colour gets implemented matching the intensity of the colour being substituted.

The colour filter options:

FilterOptions

The colour to replace was taken from the original , the replacement colour is specified through a colour picker dialog. When a user clicks on either displayed, the colour of the pixel clicked on sets the value of the replacement colour. By adjusting the threshold value the user can specify how wide or narrow the range of colours to replace should be. The higher the threshold value, the wider the range of colours that will be replaced.

The resulting image can be saved by clicking the “Save Result” button. In order to apply another colour substitution on the resulting image click the button labelled “Set Result as Source”.

Colour Substitution Filter Data

The sample source code provides the definition for the ColorSubstitutionFilter class. The purpose of this class is to contain data required when applying colour substitution. The ColorSubstitutionFilter class is defined as follows:

public class ColorSubstitutionFilter
{
    private int thresholdValue = 10;
    public int ThresholdValue
    {
        get { return thresholdValue; }
        set { thresholdValue = value; }
    }

private Color sourceColor = Color.White; public Color SourceColor { get { return sourceColor; } set { sourceColor = value; } }
private Color newColor = Color.White; public Color NewColor { get { return newColor; } set { newColor = value; } } }

To implement a colour substitution filter we first have to create an object instance of type ColorSubstitutionFilter. A colour substitution requires specifying a SourceColor, which is the colour to replace/substitute and a NewColour, which defines the colour that will replace the SourceColour. Also required is a ThresholdValue, which determines a range of colours based on the SourceColor.

Colour Substitution implemented as an Extension method

The sample source code defines the ColorSubstitution extension method which targets the class. Invoking the ColorSubstitution requires passing a parameter of type ColorSubstitutionFilter, which defines how colour substitution is to be implemented. The following code snippet contains the definition of the ColorSubstitution method.

public static Bitmap ColorSubstitution(this Bitmap sourceBitmap, ColorSubstitutionFilter filterData)
{
    Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height, PixelFormat.Format32bppArgb);

BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte[] resultBuffer = new byte[resultData.Stride * resultData.Height]; Marshal.Copy(sourceData.Scan0, resultBuffer, 0, resultBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
byte sourceRed = 0, sourceGreen = 0, sourceBlue = 0, sourceAlpha = 0; int resultRed = 0, resultGreen = 0, resultBlue = 0;
byte newRedValue = filterData.NewColor.R; byte newGreenValue = filterData.NewColor.G; byte newBlueValue = filterData.NewColor.B;
byte redFilter = filterData.SourceColor.R; byte greenFilter = filterData.SourceColor.G; byte blueFilter = filterData.SourceColor.B;
byte minValue = 0; byte maxValue = 255;
for (int k = 0; k < resultBuffer.Length; k += 4) { sourceAlpha = resultBuffer[k + 3];
if (sourceAlpha != 0) { sourceBlue = resultBuffer[k]; sourceGreen = resultBuffer[k + 1]; sourceRed = resultBuffer[k + 2];
if ((sourceBlue < blueFilter + filterData.ThresholdValue && sourceBlue > blueFilter - filterData.ThresholdValue) &&
(sourceGreen < greenFilter + filterData.ThresholdValue && sourceGreen > greenFilter - filterData.ThresholdValue) &&
(sourceRed < redFilter + filterData.ThresholdValue && sourceRed > redFilter - filterData.ThresholdValue)) { resultBlue = blueFilter - sourceBlue + newBlueValue;
if (resultBlue > maxValue) { resultBlue = maxValue;} else if (resultBlue < minValue) { resultBlue = minValue;}
resultGreen = greenFilter - sourceGreen + newGreenValue;
if (resultGreen > maxValue) { resultGreen = maxValue;} else if (resultGreen < minValue) { resultGreen = minValue;}
resultRed = redFilter - sourceRed + newRedValue;
if (resultRed > maxValue) { resultRed = maxValue;} else if (resultRed < minValue) { resultRed = minValue;}
resultBuffer[k] = (byte)resultBlue; resultBuffer[k + 1] = (byte)resultGreen; resultBuffer[k + 2] = (byte)resultRed; resultBuffer[k + 3] = sourceAlpha; } } }
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); resultBitmap.UnlockBits(resultData);
return resultBitmap; }

The ColorSubstitution method can be labelled as due to its implementation. Being implies that the source/input data will not be modified, instead a new instance will be created reflecting the source data as modified by the operations performed in the particular method.

The first statement defined in the ColorSubstitution method body instantiates an instance of a new , matching the size dimensions of the source object. Next the method invokes the method on the source and result instances. When invoking the underlying data representing a will be locked in memory. Being locked in memory can also be described as signalling/preventing the Garbage Collector to not move around in memory the data being locked. Invoking results in the Garbage Collector functioning as per normal, moving data in memory and updating the relevant memory references when required.

The source code continues by copying all the representing the source to an array of bytes that represents the resulting . At this stage the source and result s are exactly identical and as yet unmodified. In order to determine which pixels based on colour should be modified the source code iterates through the byte array associated with the result .

Notice how the for loop increments by 4 with each loop. The underlying data represents a 32 Bits per pixel Argb , which equates to 8 bits/1 representing an individual colour component, either Alpha, Red, Green or Blue. Defining the for loop to increment by 4 results in each loop iterating 4 or 32 bits, in essence 1 pixel.

Within the for loop we determine if the colour expressed by the current pixel adjusted by the threshold value forms part of the colour range that should be updated. It is important to remember that an individual colour component is a byte value and can only be set to a value between 0 and 255 inclusive.

The Implementation

The ColorSubstitution method is implemented by the sample source code  through a Windows Forms application. The ColorSubstitution method requires that the source specified must be  formatted as a 32 Bpp Argb . When the user loads a source image from the file system the sample application attempts to convert the selected file by invoking the Format32bppArgbCopy which targets the class. The definition is as follows:

public static Bitmap Format32bppArgbCopy(this Bitmap sourceBitmap)
{
    Bitmap copyBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height, PixelFormat.Format32bppArgb);

using (Graphics graphicsObject = Graphics.FromImage(copyBitmap)) { graphicsObject.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; graphicsObject.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphicsObject.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; graphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphicsObject.DrawImage(sourceBitmap, new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), GraphicsUnit.Pixel); }
return copyBitmap; }

Colour Substitution Examples

The following section illustrates a few examples of colour substitution result . The source image features Bellis perennis also known as the common European Daisy (see Wikipedia). The image file is licensed under the Creative Commons Attribution-Share Alike 2.5 Generic license. The original image can be downloaded here. The following image is a scaled down version of the original:

Bellis_perennis_white_(aka)_scaled

Light Blue Colour Substitution

Colour Component Source Colour Substitute Colour
Red   255   121
Green   223   188
Blue   224   255

Daisy_light_blue

Medium Blue Colour Substitution

Colour Component Source Colour Substitute Colour
Red   255   34
Green   223   34
Blue   224   255

Daisy_medium_blue

Medium Green Colour Substitution

Colour Component Source Colour Substitute Colour
Red   255   0
Green   223   128
Blue   224   0

Daisy_medium_green

Purple Colour Substitution

Colour Component Source Colour Substitute Colour
Red   255   128
Green   223   0
Blue   224   255

Daisy_purple

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:

C# How to: Blending Bitmap images using colour filters

Article purpose

In this we explore how to combine or blend two by implementing various colour filters affecting how an appear as part of the resulting blended . The concepts detailed in this are reinforced and easily reproduced by making use of the sample source code that accompanies this .

Sample source code

This is accompanied by a sample source code Visual Studio project which is available for download .

Using the sample Application

The provided sample source code builds a which can be used to blend two separate files read from the file system. The methods employed when applying blending operations on two can be adjusted to a fine degree when using the sample application. In addition the sample application provides the functionality allowing the user to save the resulting blended and also being able to save the exact colour filters/algorithms that were applied. Filters created and saved previously can be loaded from the file system allowing users to apply the exact same filtering without having to reconfigure filtering as exposed through the application front end.

Here is a screenshot of the sample application in action:

Blending Bitmap images using colour filters

This scenario blends an of an airplane with an of a pathway in a forest. As a result of the colour filtering implemented when blending the two the output appears to provide somewhat equal elements of both , without expressing all the elements of the original .

What is Colour Filtering?

In general terms making reference to a filter would imply some or other mechanism being capable of sorting or providing selective inclusion/exclusion. There is a vast number of filter algorithms in existence, each applying some form of logic in order to achieve a desired outcome.

In C# implementing colour filter algorithms targeting have the ability to modify or manipulate the colour values expressed by an . The extent to which colour values change as well as the level of detail implemented  is determined by the algorithms/calculations performed when a filter operation becomes active.

An can be thought of as being similar to a two dimensional array with each array element representing the data required to be expressed as an individual pixel. Two dimensional arrays are similar to the concept of a grid defined by rows and columns. The “rows” of pixels represented by an are required to all be of equal length. When thinking in terms of a two dimensional array or grid analogy its likely to make sense that the number of rows and columns in fact translate to an ’s width and height.

An ’s colour data can be expressed beyond rows and columns of pixels. Each pixel contained in an defines a colour value. In this we focus on 32bit of type ARGB.

Image Bit Depth and Pixel Colour components

bit depth is also referred to as the number of bits per pixel. Bits per pixel (Bpp) is an indicator of the amount of storage space required to store an image. A bit value is smallest unit of storage available in a storage medium, it can simply be referred to as being either a value of 1 or 0.

In C# natively the smallest unit of storage expressed comes in the form of the integral type. A value occupies 8 bits of storage, it is not possible in C# to express a value as a bit. The .net framework provides functionality enabling developers to develop in the concept of bit values, which in reality occupies far more than 1 bit of memory when implemented.

If we consider as an example an formatted as 32 bits per pixel, as the name implies, for each pixel contained in an 32 bits of additional storage is required. Knowing that a represents 8 bits logic dictates that 32 bits are equal to four , determined by dividing 32 bits by 8. as 32 bits per pixel therefore can be considered as being encoded as 4 per pixel images. 32 Bpp require 4 to express a single pixel.

The data represented by a pixel’s bytes

32 Bpp equating to 4 per pixel, each in turn representing a colour component, together being capable of expressing a single colour representing the underlying pixel. When an format is referred to as 32 Bpp Argb the four colour components contained in each pixel are: Red, Green, Blue and the Alpha component.

An Alpha component is in indication of a particular pixel’s transparency. Each colour component is represented by a value. The possible value range in terms of stretch from 0 to 255, inclusive, therefore a colour component can only contain a value within this range. A colour value is in fact a representation of that colour’s intensity associated with a single image pixel. A value of 0 indicating the highest possible intensity and 255 reflecting no intensity at all. When the Alpha component is set to a value of 255 it is an expression of no transparency. In the  same fashion when an Alpha component equates to 0 the associated pixel is considered completely transparent, thus negating whatever colour values are expressed by the remaining colour components.

Take note that not all support transparency, almost similar to how certain can only represent grayscale or just black and white pixels. An expressed only in terms of Red, Blue and Green colour values, containing no Alpha component can be implemented as a 24 Bit RGB . Each pixel’s storage requirement being 24 bits or 3 bytes, representing colour intensity values for Red, Green and Blue, each limited to a value ranging from 0 to 255.

Manipulating a Pixel’s individual colour components

In C# it is possible to manipulate the value of a pixel’s individual colour components. Updating the value expressed by a colour component will most likely result in the colour value expressed by the pixel as a whole to also change. The resulting change affects the colour component’s intensity. As an example, if you were to double the value of the Blue colour component the result will most likely be that the associated pixel will represent a colour having twice the intensity of Blue when compared to the previous value.

When you manipulate colour components you are in affect applying a colour filter to the underlying . Iterating the values and performing updates based on a calculation or a conditional check qualifies as an algorithm, an /colour filter in other words.

Retrieving Image Colour components as an array of bytes

As a result of the .net Framework’s memory handling infrastructure and the implementation of the it is a likely possibility that the memory address assigned to a variable when instantiated could change during the variable’s lifetime and scope, not necessarily reflecting the same address in memory when going out of scope. It is the responsibility of the to ensure updating a variable’s memory reference when an operation performed by the results in values kept in memory moving to a new address in memory.

When accessing an ’s underlying data expressed as a array we require a mechanism to signal the that an additional memory reference exist, which might not be updated when values shift in memory  to a new address.

The class provides the method, when invoked the values representing pixel data will not be moved in memory by the until the values are unlocked by invoking the method also defined by the class.

The method defines a return value of type , which contains data related to the lock operation. When invoking the method you are required to pass the object returned when you invoked .

The method provides an overridden implementation allowing the calling code to specify only a certain part of a to lock in memory, expressed as

The sample code that accompanies this updates the entire and therefore specifies the portion to lock as a equal in size to the whole .

The class defines the property of type . contains the address in memory of the ’s first pixel, in other words the starting point of a ’s underlying data.

Creating an algorithm to Filter Bitmap Colour components

The sample code is implemented to blend two , considering one as as source or base canvas and the second as an overlay . The algorithm used to implement a blending operation is defined as a class exposing public properties which affect how blending is achieved.

The source code that implements the algorithm functions by creating an object instance of BitmapFilterData class and setting the associated public properties to certain values, as specified by the user through the user interface at runtime. The BitmapFilterData class can thus be considered to qualify as a dynamic algorithm. The following source code contains the definition of the BitmapFilterData class.

[Serializable]
public class BitmapFilterData
{
    private bool sourceBlueEnabled = false;
    public bool SourceBlueEnabled { get { return sourceBlueEnabled; }set { sourceBlueEnabled = value; } }

private bool sourceGreenEnabled = false; public bool SourceGreenEnabled { get { return sourceGreenEnabled; }set { sourceGreenEnabled = value; } }
private bool sourceRedEnabled = false; public bool SourceRedEnabled { get { return sourceRedEnabled; } set { sourceRedEnabled = value; } }
private bool overlayBlueEnabled = false; public bool OverlayBlueEnabled { get { return overlayBlueEnabled; } set { overlayBlueEnabled = value; } }
private bool overlayGreenEnabled = false; public bool OverlayGreenEnabled { get { return overlayGreenEnabled; } set { overlayGreenEnabled = value; } }
private bool overlayRedEnabled = false; public bool OverlayRedEnabled { get { return overlayRedEnabled; }set { overlayRedEnabled = value; } }
private float sourceBlueLevel = 1.0f; public float SourceBlueLevel { get { return sourceBlueLevel; } set { sourceBlueLevel = value; } }
private float sourceGreenLevel = 1.0f; public float SourceGreenLevel { get { return sourceGreenLevel; } set { sourceGreenLevel = value; } }
private float sourceRedLevel = 1.0f; public float SourceRedLevel {get { return sourceRedLevel; } set { sourceRedLevel = value; } }
private float overlayBlueLevel = 0.0f; public float OverlayBlueLevel { get { return overlayBlueLevel; } set { overlayBlueLevel = value; } }
private float overlayGreenLevel = 0.0f; public float OverlayGreenLevel { get { return overlayGreenLevel; } set { overlayGreenLevel = value; } }
private float overlayRedLevel = 0.0f; public float OverlayRedLevel { get { return overlayRedLevel; } set { overlayRedLevel = value; } }
private ColorComponentBlendType blendTypeBlue = ColorComponentBlendType.Add; public ColorComponentBlendType BlendTypeBlue { get { return blendTypeBlue; }set { blendTypeBlue = value; } }
private ColorComponentBlendType blendTypeGreen = ColorComponentBlendType.Add; public ColorComponentBlendType BlendTypeGreen { get { return blendTypeGreen; } set { blendTypeGreen = value; } }
private ColorComponentBlendType blendTypeRed = ColorComponentBlendType.Add; public ColorComponentBlendType BlendTypeRed { get { return blendTypeRed; } set { blendTypeRed = value; } }
public static string XmlSerialize(BitmapFilterData filterData) { XmlSerializer xmlSerializer = new XmlSerializer(typeof(BitmapFilterData));
XmlWriterSettings xmlSettings = new XmlWriterSettings(); xmlSettings.Encoding = Encoding.UTF8; xmlSettings.Indent = true;
MemoryStream memoryStream = new MemoryStream(); XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlSettings);
xmlSerializer.Serialize(xmlWriter, filterData); xmlWriter.Flush();
string xmlString = xmlSettings.Encoding.GetString(memoryStream.ToArray());
xmlWriter.Close(); memoryStream.Close(); memoryStream.Dispose();
return xmlString; }
public static BitmapFilterData XmlDeserialize(string xmlString) { XmlSerializer xmlSerializer = new XmlSerializer(typeof(BitmapFilterData)); MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlString));
XmlReader xmlReader = XmlReader.Create(memoryStream);
BitmapFilterData filterData = null;
if(xmlSerializer.CanDeserialize(xmlReader) == true) { xmlReader.Close(); memoryStream.Position = 0;
filterData = (BitmapFilterData)xmlSerializer.Deserialize(memoryStream); }
memoryStream.Close(); memoryStream.Dispose();
return filterData; } }
public enum ColorComponentBlendType { Add, Subtract, Average, DescendingOrder, AscendingOrder }

The BitmapFilterData class define 6 public properties relating to whether a colour component should be included in calculating the specific component’s new value. The properties are:

  • SourceBlueEnabled
  • SourceGreenEnabled
  • SourceRedEnabled
  • OverlayBlueEnabled
  • OverlayGreenEnabled
  • OverlayRedEnabled

In addition a further 6 related public properties are defined which dictate a factor by which to apply a colour component as input towards the value of the resulting colour component. The properties are:

  • SourceBlueLevel
  • SourceGreenLevel
  • SourceRedLevel
  • OverlayBlueLevel
  • OverlayGreenLevel
  • OverlayRedLevel

Only if a colour component’s related Enabled property is set to true will the associated Level property be applicable when calculating the new value of the colour component.

The BitmapFilterData class next defines 3 public properties of type ColorComponentBlendType. This ’s value determines the calculation performed between source and overlay colour components. Only after each colour component has been modified by applying the associated Level property factor will the calculation defined by the ColorComponentBlendType value be performed.

Source and Overlay colour components can be Added together, Subtracted, Averaged, Discard Larger value or Discard Smaller value. The 3 public properties that define the calculation type to be performed are:

  • BlendTypeBlue
  • BlendTypeGreen
  • BlendTypeRed

Notice the two public method defined by the BitmapFilterData class, XmlSerialize and XmlDeserialize. These two methods enables the calling code to Xml serialize a BitmapFilterData object to a string variable containing the object’s Xml representation, or to create an object instance based on an Xml representation.

The sample application implements Xml serialization and deserialization when saving a BitmapFilterData object to the file system and when creating a BitmapFilterData object previously saved on the file system.

Bitmap Blending Implemented as an extension method

The sample source code provides the definition for the BlendImage method, an targeting the class. This method creates a new memory , of which the colour values are calculated from a source and an overlay as defined by a BitmapFilterData object instance parameter. The source code listing for the BlendImage method:

public static Bitmap BlendImage(this Bitmap baseImage, Bitmap overlayImage, BitmapFilterData filterData)
{
    BitmapData baseImageData = baseImage.LockBits(new Rectangle(0, 0, baseImage.Width, baseImage.Height),
      System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];

Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(0, 0, overlayImage.Width, overlayImage.Height),
                   System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];

Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
float sourceBlue = 0; float sourceGreen = 0; float sourceRed = 0;
float overlayBlue = 0; float overlayGreen = 0; float overlayRed = 0;
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4) { sourceBlue = (filterData.SourceBlueEnabled ? baseImageBuffer[k] * filterData.SourceBlueLevel : 0); sourceGreen = (filterData.SourceGreenEnabled ? baseImageBuffer[k+1] * filterData.SourceGreenLevel : 0); sourceRed = (filterData.SourceRedEnabled ? baseImageBuffer[k+2] * filterData.SourceRedLevel : 0);
overlayBlue = (filterData.OverlayBlueEnabled ? overlayImageBuffer[k] * filterData.OverlayBlueLevel : 0); overlayGreen = (filterData.OverlayGreenEnabled ? overlayImageBuffer[k + 1] * filterData.OverlayGreenLevel : 0); overlayRed = (filterData.OverlayRedEnabled ? overlayImageBuffer[k + 2] * filterData.OverlayRedLevel : 0);
baseImageBuffer[k] = CalculateColorComponentBlendValue(sourceBlue, overlayBlue, filterData.BlendTypeBlue); baseImageBuffer[k + 1] = CalculateColorComponentBlendValue(sourceGreen, overlayGreen, filterData.BlendTypeGreen); baseImageBuffer[k + 2] = CalculateColorComponentBlendValue(sourceRed, overlayRed, filterData.BlendTypeRed); }
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb); BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(0, 0, bitmapResult.Width, bitmapResult.Height),
                 System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

Marshal.Copy(baseImageBuffer, 0, resultImageData.Scan0, baseImageBuffer.Length);
bitmapResult.UnlockBits(resultImageData); baseImage.UnlockBits(baseImageData); overlayImage.UnlockBits(overlayImageData);
return bitmapResult; }

The BlendImage method starts off by creating 2 arrays, intended to contain the source and overlay ’s pixel data, expressed in 32 Bits per pixel Argb . As discussed earlier, both ’s data are locked in memory by invoking the method.

Before copying values we first need to declare a array to copy to. The size in of a ’s raw data can be determined by multiplying and  . We obtained an instance of the class when we invoked . The associated object contains data about the lock operation, hence it needs to be supplied as a parameter when unlocking the bits when invoking .

The property refers to the ’s scan width. The property can also be described as the total number of colour components found within one row of pixels from a . Note that the property is rounded up to a four boundary. We can make the deduction that the property modulus 4 would always equal 0. The source code multiplies the property and the property.  Also known as the number of scan lines, the property is equal to the ’s height in pixels from which the object was derived.

Multiplying and   can be considered the same as multiplying an ’s bit depth, Width and also Height.

Once the two arrays have been obtained from the source and overlay the sample source iterates both arrays simultaneously. Notice how the code iterates through the arrays, the for loop increment statement being incremented by a value of 4 each time the loop executes. The for loop executes in terms of pixel values, remember that one pixel consists of 4 /colour components when the has been encoded as a 32Bit Argb .

Within the for loop the code firstly calculates a value for each colour component, except the Alpha  component. The calculation is based on the values defined by the BitmapFilterData object.

Did you notice that the source code assigns colour components in the order of Blue, Green, Red? An easy oversight when it comes to manipulating Bitmaps comes in the form of not remembering the correct order of colour components. The pixel format is referred to as 32bppArgb, in fact most references state Argb being Alpha, Red, Green and Blue.  The colour components representing a pixel are in fact ordered Blue, Green, Red and Alpha, the exact opposite to most naming conventions. Since the for loop’s increment statement increments by four with each loop we can access the Green, Red and Alpha components by adding the values 1, 2 or 3 to the loop’s index component, in this case defined as the variable k. Remember the colour component ordering, it can make for some interesting unintended consequences/features.

If a colour component is set to disabled in the BitmapFilterData object the new colour component will be set to a value of 0. Whilst iterating the arrays a set of calculations are also performed as defined by each colour component’s associated ColorComponentBlendType, defined by the BitmapFilterData object. The calculations are implemented by invoking the CalculateColorComponentBlendValue method.

Once all calculations are completed a new object is created to which the updated pixel data is copied only after the newly created has been locked in memory. Before returning the which now contains the blended colour component values all are unlocked in memory.

Calculating Colour component Blend Values

The CalculateColorComponentBlendValue method is implemented to calculate blend values as follows:

private static byte CalculateColorComponentBlendValue(float source, float overlay, ColorComponentBlendType blendType)
{
    float resultValue = 0;
    byte resultByte = 0;

if (blendType == ColorComponentBlendType.Add) { resultValue = source + overlay; } else if (blendType == ColorComponentBlendType.Subtract) { resultValue = source - overlay; } else if (blendType == ColorComponentBlendType.Average) { resultValue = (source + overlay) / 2.0f; else if (blendType == ColorComponentBlendType.AscendingOrder) { resultValue = (source > overlay ? overlay : source); } else if (blendType == ColorComponentBlendType.DescendingOrder) { resultValue = (source < overlay ? overlay : source); }
if (resultValue > 255) { resultByte = 255; } else if (resultValue < 0) { resultByte = 0; } else { resultByte = (byte)resultValue; }
return resultByte; }

Different sets of calculations are performed  for each colour component based on the Blend type specified by the BitmapFilterData object. It is important to note that before the method returns a result a check is performed ensuring the new colour component’s value falls within the valid range of values, 0 to 255. Should a value exceed 255 that value will be assigned to 255, also if a value is negative that value will be assigned to 0.

The implementation – A Windows Forms Application

The accompanying source code defines a . The ImageBlending application enables a user to specify the source and overlay input , both of which are displayed in a scaled size on the left-hand side of the . On the right-hand side of the users are presented with various controls that can be used to adjust the current colour filtering algorithm.

The following screenshot shows the user specified input source and overlay :

Blending Bitmap images using colour filters

The next screenshot details the controls available which can be used to modify the colour filtering algorithm being applied:

Blending Bitmap images using colour filters

Notice the checkboxes labelled Blue, Green and Red, the check state determines whether the associated colour component’s value will be used in calculations. The six trackbar controls shown above can be used to set the exact factoring value applied to the associated colour component when implementing the colour filter. Lastly towards the bottom of the screen the three comboboxes provided indicate the ColorComponentBlendType value implemented in the colour filter calculations.

The blended output implementing the specified colour filter is located towards the middle of the . Remember in the screenshot shown above, the input being of a beach scene and the overlay being a rock concert at night. The degree and intensity towards how colours from both feature in the output is determined by the colour filter, as defined earlier through the user interface.

Blending Bitmap images using colour filters

Towards the bottom of this part of the screen two buttons can be seen, labelled “Save Filter” and “Load Filter”. When a user creates a filter the filter values can be persisted to the file system by clicking Save Filter and specifying a file path. To implement a colour filter created and saved earlier users can click the Load Filter button and file browse to the correct file.

For the sake of clarity the screenshot featured below captures the entire application front end. The previous images consist of parts taken from this image:

Blending Bitmap images using colour filters

The trackbar controls provided by the user interface enables a user to quickly and effortlessly test a colour filter. The ImageBlend application has been implemented to provide instant feedback/processing based on user input. Changing any of the colour filter values exposed by the user interface results in the colour filter being applied instantly.

Loading a Source Image

When the user clicks on the Load button associated with source a standard Open File Dialog displays prompting the user to select a source file. Once a source file has been selected the ImageBlend application creates a memory from the file system . The method in which colour filtering is implemented requires that input files have a bit depth of 32 bits and is formatted as an Argb . If a user attempts to specify an that is not in a 32bppArgb format the source code will attempt to convert the input image to the correct format by making use of LoadArgbBitmap .

Converting to the 32BppArgb format

The sample source code defines the LoadArgbBitmap targeting the string class. In addition to converting , this method can also be implemented to resize provided . The LoadArgbBitmap method is invoked when a user specifies a source and also when specifying an overlay . In order to provide better colour filtering source and overlay are required to be the same size. Source are never resized, only overlay images are resized to match the size dimensions of the specified source . The following code snippet provides the implementation of the LoadArgbBitmap method:

public  static  Bitmap  LoadArgbBitmap(this  string  filePath, Size? imageDimensions = null )
{
    StreamReader streamReader = new  StreamReader(filePath);
    Bitmap  fileBmp = (Bitmap)Bitmap.FromStream(streamReader.BaseStream);
    streamReader.Close();

int width = fileBmp.Width; int height = fileBmp.Height;
if(imageDimensions != null) { width = imageDimensions.Value.Width; height = imageDimensions.Value.Height; }
if(fileBmp.PixelFormat != PixelFormat.Format32bppArgb || fileBmp.Width != width || fileBmp.Height != height) { fileBmp = GetArgbCopy(fileBmp, width, height); }
return fileBmp; }

are only resized if the required size is known and if an is not already the same size specified as the required size. A scenario where the required size might not yet be known can occur when the user first selects an overlay with no source specified yet. Whenever the source changes the overlay is recreated from the file system and resized if needed.

If the specified does not conform to a 32bppArgb the LoadArgbBitmap invokes the GetArgbCopy method, which is defined as follows:

private static Bitmap GetArgbCopy(Bitmap sourceImage, int width, int height)
{
    Bitmap bmpNew = new Bitmap(width, height, PixelFormat.Format32bppArgb);

using (Graphics graphics = Graphics.FromImage(bmpNew)) { graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear; graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; graphics.DrawImage(sourceImage, new Rectangle(0, 0, bmpNew.Width, bmpNew.Height), new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), GraphicsUnit.Pixel); graphics.Flush(); }
return bmpNew; }

The GetArgbCopy method implements GDI drawing using the class in order to resize an .

Bitmap Blending examples

RHCP1

RHCP5

DayNightBlend

In the example above two photos were taken from the same location. The first photo was taken in the late afternoon, the second once it was dark. The resulting blended appears to show the time of day being closer to sundown than in the first photo. The darker colours from the second photo are mostly excluded by the filter, lighter elements such as the stage lighting appear pronounced as a yellow tint in the output .

The filter values specified are listed below as Xml. To reproduce the filter you can copy the Xml markup and save to disk specifying the file extension *.xbmp.

<?xml version="1.0" encoding="utf-8"?>
<BitmapFilterData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SourceBlueEnabled>true</SourceBlueEnabled>
  <SourceGreenEnabled>true</SourceGreenEnabled>
  <SourceRedEnabled>true</SourceRedEnabled>
  <OverlayBlueEnabled>true</OverlayBlueEnabled>
  <OverlayGreenEnabled>true</OverlayGreenEnabled>
  <OverlayRedEnabled>true</OverlayRedEnabled>
  <SourceBlueLevel>0.5</SourceBlueLevel>
  <SourceGreenLevel>0.3</SourceGreenLevel>
  <SourceRedLevel>0.2</SourceRedLevel>
  <OverlayBlueLevel>0.75</OverlayBlueLevel>
  <OverlayGreenLevel>0.5</OverlayGreenLevel>
  <OverlayRedLevel>0.6</OverlayRedLevel>
  <BlendTypeBlue>Subtract</BlendTypeBlue>
  <BlendTypeGreen>Add</BlendTypeGreen>
  <BlendTypeRed>Add</BlendTypeRed>
</BitmapFilterData>

Blending two night time :

RHCP5

RHCP6

NightTimePhotoBlend

Fun Fact: In this some of the photos featured were taken at a live concert of The Red Hot Chilli Peppers, performing at Soccer City, Johannesburg, South Africa.

Related Articles

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: Grayscale, Transparency, Image Negative and Sepia tone. All filters are implemented as targeting the class, as well as the class as the result of inheritance and upcasting.

Sample source code

This article is accompanied by a sample source code Visual Studio project which is available for download .

ARGB Overview

ARGB is an abbreviation for the term: “Alpha, Red, Green and Blue”. ARGB refers 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 ARGB components 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 Alpha, Red, 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, Green, Red, Alpha. 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 32Bit ARGB formatted copy of the specified source by invoking the method GetArgbCopy. 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 Alpha, Red, Green or Blue. Remember as mentioned earlier, the components are in fact stored in the format Blue, Green, Red, Alpha. 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 Alpha component 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% Blue, 59% 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% blue, 76.9% green, 39.3% red
  • Green Component: Sum total of: 16.8% blue, 68.6% green, 34.9% red
  • Blue Component: Sum total of: 13.1% blue, 53.4% green, 27.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 Alpha component 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();
        }
    }
}
 

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:


Dewald Esterhuizen

Blog Stats

  • 437,835 hits

Enter your email address to follow and receive notifications of new posts by email.

Join 204 other followers

Archives

Twitter feed


%d bloggers like this: