Search Results for 'image filters'



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 Distortion Blur

Article Purpose

This article explores the process of implementing an Image Distortion Blur filter. This image filter is classified as a non-photo realistic image filter, primarily implemented in rendering artistic effects.

Flower: Distortion Factor 15

Flower: Distortion Factor 15

Sample Source Code

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

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Using the Sample Application

The sample source code that accompanies this article includes a based sample application. The concepts explored in this article have all been implemented as part of the sample application. From an end user perspective the following configurable options are available:

  • Load/Save Images – Clicking the Load Image button allows a user to specify a source/input . If desired, output filtered can be saved to the local system by clicking the Save Image button.
  • Distortion Factor – The level or intensity of distortion applied when implementing the filter can be specified when adjusting the Distortion Factor through the user interface. Lower factor values result in less distortion being evident in resulting . Specifying higher factor values result in more intense distortion being applied.

The following image is screenshot of the Image Distortion Blur sample application:

ImageDistortionBlur_SampleApplication

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Image Distortion

In this article and the accompanying sample source code are distorted through slightly adjusting each individual ’s coordinates. The direction and distance by which coordinates are adjusted differ per as a result of being randomly selected. The maximum distance offset applied depends on the user specified Distortion Factor. Once all coordinates have been updated, implementing a provides smoothing and an effect.

Applying an Image Distortion Filter requires implementing the following steps:

  1. Iterate Pixels – Each forming part of the source/input should be iterated.
  2. Calculate new Coordinates – For every being iterated generate two random values representing XY-coordinate offsets to be applied to a ’s current coordinates. Offset values can equate to less than zero in order to represent coordinates above or to the left of the current .
  3. Apply Median Filter – The newly offset will appear somewhat speckled in the resulting . Applying a reduces the speckled appearance whilst retaining a distortion effect.

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Median Filter

Applying a is the final step required when implementing an Image Distortion Blur filter. are often implemented in reducing . The method of image distortion illustrated in this article express similarities when compared to . In order to soften the appearance of we implement a .

A can be applied through implementing the following steps:

  1. Iterate Pixels – Each forming part of the source/input should be iterated.
  2. Inspect Pixel Neighbourhood – Each neighbouring in relation to the currently being iterated should be added to a temporary collection.
  3. Determine Neighbourhood Median – Once all neighbourhood have been added to a temporary collection, sort the collection by value. The element value located at the middle of the collection represents the neighbourhood’s value.

Flower: Distortion Factor 10

Flower: Distortion Factor 10

Flower: Distortion Factor 15

Flower: Distortion Factor 15

Implementing Image Distortion

The sample source code defines the DistortionBlurFilter method, an targeting the class. The following code snippet illustrates the implementation:

public static Bitmap DistortionBlurFilter( 
         this Bitmap sourceBitmap, int distortFactor) 
{
    byte[] pixelBuffer = sourceBitmap.GetByteArray(); 
    byte[] resultBuffer = sourceBitmap.GetByteArray(); 

int imageStride = sourceBitmap.Width * 4; int calcOffset = 0, filterY = 0, filterX = 0; int factorMax = (distortFactor + 1) * 2; Random rand = new Random();
for (int k = 0; k + 4 < pixelBuffer.Length; k += 4) { filterY = distortFactor - rand.Next(0, factorMax); filterX = distortFactor - rand.Next(0, factorMax);
if (filterX * 4 + (k % imageStride) < imageStride && filterX * 4 + (k % imageStride) > 0) { calcOffset = k + filterY * imageStride + 4 * filterX;
if (calcOffset >= 0 && calcOffset + 4 < resultBuffer.Length) { resultBuffer[calcOffset] = pixelBuffer[k]; resultBuffer[calcOffset + 1] = pixelBuffer[k + 1]; resultBuffer[calcOffset + 2] = pixelBuffer[k + 2]; } } }
return resultBuffer.GetImage(sourceBitmap.Width, sourceBitmap.Height).MedianFilter(3); }

Flower: Distortion Factor 15

Flower: Distortion Factor 15

Implementing a Median Filter

The MedianFilter targets the class. The implementation as follows:

public static Bitmap MedianFilter(this Bitmap sourceBitmap, 
                                  int matrixSize) 
{ 
    byte[] pixelBuffer = sourceBitmap.GetByteArray(); 
    byte[] resultBuffer = new byte[pixelBuffer.Length]; 
    byte[] middlePixel; 

int imageStride = sourceBitmap.Width * 4; int filterOffset = (matrixSize - 1) / 2; int calcOffset = 0, filterY = 0, filterX = 0; List<int> neighbourPixels = new List<int>();
for (int k = 0; k + 4 < pixelBuffer.Length; k += 4) { filterY = -filterOffset; filterX = -filterOffset; neighbourPixels.Clear();
while (filterY <= filterOffset) { calcOffset = k + (filterX * 4) + (filterY * imageStride);
if (calcOffset > 0 && calcOffset + 4 < pixelBuffer.Length) { neighbourPixels.Add(BitConverter.ToInt32( pixelBuffer, calcOffset)); }
filterX++;
if (filterX > filterOffset) { filterX = -filterOffset; filterY++; } }
neighbourPixels.Sort(); middlePixel = BitConverter.GetBytes( neighbourPixels[filterOffset]);
resultBuffer[k] = middlePixel[0]; resultBuffer[k + 1] = middlePixel[1]; resultBuffer[k + 2] = middlePixel[2]; resultBuffer[k + 3] = middlePixel[3]; }
return resultBuffer.GetImage(sourceBitmap.Width, sourceBitmap.Height); }

Flower: Distortion Factor 25

Flower: Distortion Factor 25

Sample Images

This article features a number of sample images. All featured images have been licensed allowing for reproduction. The following images feature as sample images:

674px-Lil_chalcedonicum_01EB_Griechenland_Hrisomiglia_17_07_01

683px-Lil_carniolicum_subsp_ponticum_01EB_Tuerkei_Ikizdere_02_07_93

1022px-LiliumSargentiae

1024px-Lilium_longiflorum_(Easter_Lily)

1280px-LiliumBulbiferumCroceumBologna

LiliumSuperbum1

Orange_Lilium_-_Relic38_-_Ontario_Canada

White_and_yellow_flower

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: Image Abstract Colours Filter

Article Purpose

This article explores Abstract Colour Image filters as a process of Non-photo Realistic Image Rendering. The output produced reflects a variety of artistic effects.

Colour Values Red, Blue Filter Size 9
Edge Tracing Black Edge Threshold 55

Mushroom: Red - Blue, Filter Size 9, Edge Tracing Black, Edge Threshold 55

Sample Source Code

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

Colour Values Blue Filter Size 9
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Blue, Filter Size 9, Color Shift Right, Edge Tracing Double Intensity, Edge Threshold 60

Using the Sample Application

The sample source code that accompanies this article includes a based sample application. The concepts discussed in this article have been illustrated through the implementation of the sample application.

When executing the sample application, through the user interface several options are made available to the user, described as follows:

  • Load/Save Source/input may be loaded from the local system through clicking the Load Image button. If desired by the user, resulting output can be saved to the local file system through clicking the Save Image button.
  • Colour Channels – The colour values Red, Green and Blue can be updated or remain unchanged when applying a filter, indicated through the associated .
  • Filter Size – The level of filter intensity depends on the size of the filter. Larger filter sizes result in more intense filtering being applied. Smaller filter size result in less intense filtering being applied.
  • Colour Shift Type – Colour intensity values can be swapped around through selecting the Colour Shift type: Shift Left and Shift Right.
  • Edge Tracing Type – When applying a filter the edges forming part of the original source/input will be expressed as part of the output . The method in which are indicated/highlighted will be determined through the type of Edge Tracing specified when applying the filter. Supported types of Edge Tracing include: Black, White, Half Intensity, Double Intensity and Inverted.
  • Edge Threshold – Edges forming part of the source/input are determines through means of implementing a threshold value. Lower threshold values result in more emphasized edges expressed within resulting . Higher threshold values reduce edge emphasis in resulting .

 

Colour Values Green Filter Size 9
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Green, Filter Size 9, Color Shift Left, Edge Tracing Double Intensity, Edge Threshold 60

Colour Values Red, Blue Filter Size 9
Edge Tracing Double Intensity Edge Threshold 55

Mushroom: Red - Blue, Filter Size 9, Color Shift Right, Edge Tracing Double Intensity, Edge Threshold 55

The following image is a screenshot of the Image Abstract Colour Filter sample application in action:

Image Abstract Colour Filter Sample Application

Abstracting Image Colours

The Abstract Colour Filter explored in this article can be considered a non-photo realistic filter. As the title implies, non-photo realistic filters transforms an input , usually a photograph, producing a result which visibly lacks the aspects of realism expressed in the input . In most scenarios the objective of non-photo realistic filters can be described as using photographic in rendering having an animated appearance.

Colour Values Blue Filter Size 11
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Blue, Filter Size 11, Color Shift Left, Edge Tracing Double Intensity, Edge Threshold 60

Colour Values Red Filter Size 11
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Red, Filter Size 11, Color Shift Left, Edge Tracing Double Intensity, Edge Threshold 60

The Abstract Colour Filter can be broken down into two main components: Colour Averaging and . Through implementing a variety of colour averaging algorithms resulting express abstract yet uniform colours. Abstract colours result in output no longer appearing photo realistic, instead output appear unconventionally augmented/artistic.

Output express a lesser degree of definition and detail, when compared to input . In some scenarios output might not be easily recognisable. In order to retain some detail, edge/boundary detail detected from input will be emphasised in result .

Colour Values Green Filter Size 11
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Green, Filter Size 11, Color Shift Left, Edge Tracing, Double Intensity, Edge Threshold 60

Colour Values Green, Blue Filter Size 11
Edge Tracing Double Intensity Edge Threshold 75

Mushroom: Green - Blue, Filter Size 11, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 75

The steps required when applying an Abstract Colour Filter can be described as follows:

  1. Perform – Using the source/input perform , producing a binary expressing in the foreground/as white.
  2. Calculate Neighbourhood Colour Averages – Iterate each forming part of the input , inspecting the ’s neighbouring . Calculate the sum total and average of each colour channel, Red, Green and Blue. The value of the currently being iterated should be set depending to neighbourhood average.
  3. Trace Edges within Colour Averages – Simultaneously iterate the colour average and the edge detected . If the being iterated forms part of an edge, update the corresponding in the average colour , depending on the specified method of Edge Tracing.

 

Colour Values Red, Green Filter Size 11
Edge Tracing Double Intensity Edge Threshold 75

Mushroom: Red - Green, Filter Size 11, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 75

Colour Values Red Filter Size 11
Edge Tracing Black Edge Threshold 60

Mushroom: Red, Filter Size 11, Color Shift Left, Edge Tracing Black, Edge Threshold 60

Implementing Pixel Neighbourhood Colour Averaging

In the sample source code neighbourhood colour averaging has been implemented through the definition of the AverageColoursFilter . This method creates a new using the source as input. The following code snippet provides the definition:

public static Bitmap AverageColoursFilter(this Bitmap sourceBitmap,  
                                          int matrixSize,   
                                          bool applyBlue = true, 
                                          bool applyGreen = true, 
                                          bool applyRed = true, 
                                          ColorShiftType shiftType = 
                                          ColorShiftType.None)  
{
    byte[] pixelBuffer = sourceBitmap.GetByteArray(); 
    byte[] resultBuffer = new byte[pixelBuffer.Length]; 

int calcOffset = 0; int byteOffset = 0; int blue = 0; int green = 0; int red = 0; int filterOffset = (matrixSize - 1) / 2;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceBitmap.Width*4 + offsetX * 4;
blue = 0; green = 0; red = 0;
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceBitmap.Width * 4);
blue += pixelBuffer[calcOffset]; green += pixelBuffer[calcOffset + 1]; red += pixelBuffer[calcOffset + 2]; } }
blue = blue / matrixSize; green = green / matrixSize; red = red / matrixSize;
if (applyBlue == false ) { blue = pixelBuffer[byteOffset]; }
if (applyGreen == false ) { green = pixelBuffer[byteOffset + 1]; }
if (applyRed == false ) { red = pixelBuffer[byteOffset + 2]; }
if (shiftType == ColorShiftType.None) { resultBuffer[byteOffset] = (byte)blue; resultBuffer[byteOffset + 1] = (byte)green; resultBuffer[byteOffset + 2] = (byte)red; resultBuffer[byteOffset + 3] = 255; } else if (shiftType == ColorShiftType.ShiftLeft) { resultBuffer[byteOffset] = (byte)green; resultBuffer[byteOffset + 1] = (byte)red; resultBuffer[byteOffset + 2] = (byte)blue; resultBuffer[byteOffset + 3] = 255; } else if (shiftType == ColorShiftType.ShiftRight) { resultBuffer[byteOffset] = (byte)red; resultBuffer[byteOffset + 1] = (byte)blue; resultBuffer[byteOffset + 2] = (byte)green; resultBuffer[byteOffset + 3] = 255; } } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }
Colour Values Green, Blue Filter Size 17
Edge Tracing Black Edge Threshold 85

Mushroom: Green - Blue, Filter Size 17, Color Shift Left, Edge Tracing Black, Edge Threshold 85

Implementing Gradient Based Edge Detection

When applying an Abstract Colours Filter, one of the required steps involve . The sample source code implements   through the definition of the GradientBasedEdgeDetectionFilter method. This method has been defined as an targeting the class. The as follows:

public static Bitmap GradientBasedEdgeDetectionFilter( 
                        this Bitmap sourceBitmap, 
                        byte threshold = 0) 
{
    BitmapData sourceData = 
               sourceBitmap.LockBits(new Rectangle (0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); sourceBitmap.UnlockBits(sourceData);
int sourceOffset = 0, gradientValue = 0; bool exceedsThreshold = false;
for (int offsetY = 1; offsetY < sourceBitmap.Height - 1; offsetY++) { for (int offsetX = 1; offsetX < sourceBitmap.Width - 1; offsetX++) { sourceOffset = offsetY * sourceData.Stride + offsetX * 4; gradientValue = 0; exceedsThreshold = true;
// Horizontal Gradient CheckThreshold(pixelBuffer, sourceOffset - 4, sourceOffset + 4, ref gradientValue, threshold, 2); // Vertical Gradient exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride, sourceOffset + sourceData.Stride, ref gradientValue, threshold, 2);
if (exceedsThreshold == false) { gradientValue = 0;
// Horizontal Gradient exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - 4, sourceOffset + 4, ref gradientValue, threshold);
if (exceedsThreshold == false) { gradientValue = 0;
// Vertical Gradient exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride, sourceOffset + sourceData.Stride, ref gradientValue, threshold);
if (exceedsThreshold == false) { gradientValue = 0;
// Diagonal Gradient : NW-SE CheckThreshold(pixelBuffer, sourceOffset - 4 - sourceData.Stride, sourceOffset + 4 + sourceData.Stride, ref gradientValue, threshold, 2); // Diagonal Gradient : NE-SW exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride + 4, sourceOffset - 4 + sourceData.Stride, ref gradientValue, threshold, 2);
if (exceedsThreshold == false) { gradientValue = 0;
// Diagonal Gradient : NW-SE exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - 4 - sourceData.Stride, sourceOffset + 4 + sourceData.Stride, ref gradientValue, threshold);
if (exceedsThreshold == false) { gradientValue = 0;
// Diagonal Gradient : NE-SW exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride + 4, sourceOffset + sourceData.Stride - 4, ref gradientValue, threshold); } } } } }
resultBuffer[sourceOffset] = (byte)(exceedsThreshold ? 255 : 0); resultBuffer[sourceOffset + 1] = resultBuffer[sourceOffset]; resultBuffer[sourceOffset + 2] = resultBuffer[sourceOffset]; resultBuffer[sourceOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); resultBitmap.UnlockBits(resultData);
return resultBitmap; }
Colour Values Red, Green Filter Size 5
Edge Tracing Black Edge Threshold 85

Mushroom: Red - Green - Blue, Filter Size 5, Color Shift Right, Edge Tracing Black, Edge Threshold 85

Implementing an Abstract Colour Filter

The AbstractColorsFilter method serves as means of combining an average colour and an edge detected . This targets the class. The following code snippet details the definition:

public static Bitmap AbstractColorsFilter(this Bitmap sourceBitmap, 
                                          int matrixSize, 
                                          byte edgeThreshold, 
                                          bool applyBlue = true, 
                                          bool applyGreen = true, 
                                          bool applyRed = true, 
                                          EdgeTracingType edgeType =  
                                          EdgeTracingType.Black, 
                                          ColorShiftType shiftType = 
                                          ColorShiftType.None) 
{ 
    Bitmap edgeBitmap =  
    sourceBitmap.GradientBasedEdgeDetectionFilter(edgeThreshold); 

Bitmap colorBitmap = sourceBitmap.AverageColoursFilter(matrixSize, applyBlue, applyGreen, applyRed, shiftType);
byte[] edgeBuffer = edgeBitmap.GetByteArray(); byte[] colorBuffer = colorBitmap.GetByteArray(); byte[] resultBuffer = colorBitmap.GetByteArray();
for (int k = 0; k + 4 < edgeBuffer.Length; k += 4) { if (edgeBuffer[k] == 255) { switch (edgeType) { case EdgeTracingType.Black: resultBuffer[k] = 0; resultBuffer[k+1] = 0; resultBuffer[k+2] = 0; break; case EdgeTracingType.White: resultBuffer[k] = 255; resultBuffer[k+1] = 255; resultBuffer[k+2] = 255; break; case EdgeTracingType.HalfIntensity: resultBuffer[k] = ClipByte(resultBuffer[k] * 0.5); resultBuffer[k + 1] = ClipByte(resultBuffer[k + 1] * 0.5); resultBuffer[k + 2] = ClipByte(resultBuffer[k + 2] * 0.5); break; case EdgeTracingType.DoubleIntensity: resultBuffer[k] = ClipByte(resultBuffer[k] * 2); resultBuffer[k + 1] = ClipByte(resultBuffer[k + 1] * 2); resultBuffer[k + 2] = ClipByte(resultBuffer[k + 2] * 2); break; case EdgeTracingType.ColorInversion: resultBuffer[k] = ClipByte(255 - resultBuffer[k]); resultBuffer[k+1] = ClipByte(255 - resultBuffer[k+1]); resultBuffer[k+2] = ClipByte(255 - resultBuffer[k+2]); break; } } }
Bitmap resultBitmap = new Bitmap (sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }
Colour Values Red, Green Filter Size 17
Edge Tracing Double Intensity Edge Threshold 85

Mushroom: Red - Green, Filter Size 17, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 85

Sample Images

This article features a number of sample images. All featured images have been licensed allowing for reproduction. The following images feature as sample images:

1280px-Mycena_atkinsoniana_60804

1024px-Lactarius_indigo_48568

Amanita_muscaria_(fly_agaric)

683px-Pleurotus_pulmonarius_LC0228

Additional Filter Result Images

The following series of images represent additional filter results.

Colour Values Red Filter Size 9
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Red, Filter Size 9, Color Shift Right, Edge Tracing Double, Edge Threshold 60

Colour Values Green, Blue Filter Size 9
Edge Tracing Double Intensity Edge Threshold 60

Mushroom: Green - Blue, Filter Size 9, Color Shift Right, Edge Tracing Double Intensity, Edge Threshold 60

Colour Values Green, Blue Filter Size 9
Edge Tracing Black Edge Threshold 60

Mushroom: Green - Blue, Filter Size 9, Color Shift Left, Edge Tracing Black, Edge Threshold 60

Colour Values Red, Green, Blue Filter Size 9
Edge Tracing Double Intensity Edge Threshold 55

Mushroom: Red - Green - Blue, Filter Size 9, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 55

Colour Values Red, Green, Blue Filter Size 11
Edge Tracing Black Edge Threshold 75

Mushroom: Red - Green - Blue, Filter Size 11, Color Shift None, Edge Tracing Black, Edge Threshold 75

Colour Values Red, Blue Filter Size 11
Edge Tracing Double Intensity Edge Threshold 75

Mushroom: Red - Blue, Filter Size 11, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 75

Colour Values Green Filter Size 17
Edge Tracing Black Edge Threshold 85

Mushroom: Green, Filter Size 17, Color Shift None, Edge Tracing Black, Edge Threshold 85

Colour Values Red Filter Size 17
Edge Tracing Black Edge Threshold 85

Mushroom: Red, Filter Size 17, Color Shift None, Edge Tracing Black, Edge Threshold 85

Colour Values Red, Green, Blue Filter Size 5
Edge Tracing Half Edge Edge Threshold 85

Mushroom: Red - Green - Blue, Filter Size 5, Color Shift None, Edge Tracing Half Edge, Threshold 85

Colour Values Blue Filter Size 5
Edge Tracing Double Intensity Edge Threshold 75

Mushroom: Blue, FilterSize 5, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 75

Colour Values Green Filter Size 5
Edge Tracing Double Intensity Edge Threshold 75

Mushroom: Green, Filter Size 5, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 75

Colour Values Red Filter Size 5
Edge Tracing Double Intensity Edge Threshold 75

Mushroom: Red, Filter Size 5, Color Shift None, Edge Tracing Double Intensity, Edge Threshold 75

Colour Values Red, Green, Blue Filter Size 5
Edge Tracing Black Edge Threshold 75

Mushroom: Red - Green - Blue, Filter Size 3, Color Shift Left, Edge Tracing Black, Edge Threshold 75

Colour Values Red, Green, Blue Filter Size 9
Edge Tracing Black Edge Threshold 55

Mushroom: Red - Green - Blue, Filter Size 3, Color Shift None, Edge Tracing Black, Edge Threshold 75

Colour Values Red, Green, Blue Filter Size 3
Edge Tracing Black Edge Threshold 75

Mushroom: Red - Green - Blue, Filter Size 3, Color Shift Right, Edge Tracing Black, Edge Threshold 75

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: Image Boundary Extraction

Article Purpose

This article explores various concepts, which feature in combination when implementing Image Boundary Extraction. Concepts covered within this article include: Morphological and , Addition and Subtraction, Boundary Sharpening, Boundary Tracing and Boundary Extraction.

Parrot: Boundary Extraction, 3×3, Red, Green, Blue

Parrot: Boundary Extraction, 3x3, Red, Greed, Blue

Sample Source Code

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

Using the Sample Application

This article’s accompanying sample source code includes the definition of a sample application. The sample application serves as an implementation of the concepts discussed in this article. In using the sample application concepts can be easily tested and replicated.

The sample application has been defined as a . The user interface enables the user to configure several options which influence the output produced from filtering processes. The following section describes the options available to a user when executing the sample application:

  • Loading and Saving files – Users can specify source/input through clicking the Load Image button. If desired, resulting filtered can be saved to the local system when clicking the Save Image button.
  • Filter Type – The types of filters implemented represent variations on Image Boundary Extraction. The supported filter types are: Conventional Boundary extraction, Boundary Sharpening and Boundary Tracing.
  • Filter Size – Filter intensity/strength will mostly be reliant on the filter size implemented. A Filter size represents the number of neighbouring examined when applying filters.
  • Colours Applied – The sample source code and sample application provides functionality allowing a filter to only effect user specified colour components. Colour components are represented in the form of an RGB colour scheme. The inclusion or exclusion of the colour components Red, Green and Blue will be determined through user configuration.
  • Structuring Element – As mentioned, the Filter Size option determines the size of neighbourhood examined. The ’s setup determine the neighbouring   within the neighbourhood size bounds that should be used as input when calculating filter results.

The following is a screenshot of the Image Boundary Extraction sample application in action:

Image Boundary Extaction Sample  Application

Parrot: Boundary Extraction, 3×3, Green

Parrot: Boundary Extraction, 3x3, Green

Morphological Boundary Extraction

Image Boundary Extraction can be considered a method of . In contrast to more commonly implemented   methods, Image Boundary Extraction originates from Morphological Image Filters.

When drawing a comparison, Image Boundary Extraction and express strong similarities. results from the difference in and . Considered from a different point of view, creating one expressing thicker edges and another expressing thinner edges provides the means to calculate the difference in edges.

Image Boundary Extraction implements the same concept as . The base concept can be regarded as calculating the difference between two which rendered the same , but expressing a difference in . Image Boundary Extraction relies on calculating the difference between either and the source or and the source . The difference between and in most cases result in more of difference than the difference between and the source or and the source . The result of Image Boundary Extraction representing less of a difference than can be observed in Image Boundary Extraction being expressed in finer/smaller width lines.

is another method of which functions along the same basis. Edges are determined by calculating the difference between two , each having been filtered from the same source , using a of differing intensity levels.

Parrot: Boundary Extraction, 3×3, Red, Green, Blue

Parrot: Boundary Extraction, 3x3, Red, Green, Blue

Boundary Sharpening

The concept of Boundary Sharpening refers to enhancing or sharpening the boundaries or edges expressed in a source/input . Boundaries can be easily determined or extracted as discussed earlier when exploring Boundary Extraction.

The steps involved in performing Boundary Sharpening can be described as follows:

  1. Extract Boundaries – Determine boundaries by performing and calculating the difference between the dilated and the source .
  2. Match Source Edges and Extracted Boundaries – The boundaries extracted in the previous step represent the difference between and the original source . Ensure that extracted boundaries match the source through performing on a copy of the source/input .
  3. Emphasise Extracted boundaries in source image – Perform addition using the extracted boundaries and dilated copy of the source .

Parrot: Boundary Extraction, 3×3, Red, Green, Blue

Parrot: Boundary Extraction, 3x3, Red, Green, Blue

Boundary Tracing

Boundary Tracing refers to applying filters which result in /boundaries appearing darker or more pronounced. This type of filter also relies on Boundary Extraction.

Boundary Tracing can be implemented in two steps, described as follows:

  1. Extract Boundaries – Determine boundaries by performing and calculating the difference between the dilated and the source .
  2. Emphasise Extracted boundaries in source image – Subtract the extracted boundaries from the original source .

Parrot: Boundary Extraction, 3×3, Red, Green, Blue

Parrot: Boundary Extraction, 3x3, Red, Green, Blue

Implementing Morphological Erosion and Dilation

The accompanying sample source code defines the MorphologyOperation method,  defined as an targeting the class. In terms of parameters this method expects a two dimensional array representing a . The other required  parameter represents an value indicating which Morphological Operation to perform, either or .

The following code snippet provides the definition in full:

private static Bitmap MorphologyOperation(this Bitmap sourceBitmap,
                                          bool[,] se,
                                          MorphologyOperationType morphType,
                                          bool applyBlue = true,
                                          bool applyGreen = true,
                                          bool applyRed = true)
{ 
    BitmapData sourceData =
               sourceBitmap.LockBits(new Rectangle(0, 0,
               sourceBitmap.Width, sourceBitmap.Height),
               ImageLockMode.ReadOnly,
               PixelFormat.Format32bppArgb);

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int filterOffset = (se.GetLength(0) - 1) / 2; int calcOffset = 0, byteOffset = 0; byte blueErode = 0, greenErode = 0, redErode = 0; byte blueDilate = 0, greenDilate = 0, redDilate = 0;
for (int offsetY = 0; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = 0; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
blueErode = 255; greenErode = 255; redErode = 255; blueDilate = 0; greenDilate = 0; redDilate = 0;
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { if (se[filterY + filterOffset, filterX + filterOffset] == true) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
calcOffset = (calcOffset < 0 ? 0 : (calcOffset >= pixelBuffer.Length + 2 ? pixelBuffer.Length - 3 : calcOffset));
blueDilate = (pixelBuffer[calcOffset] > blueDilate ? pixelBuffer[calcOffset] : blueDilate);
greenDilate = (pixelBuffer[calcOffset + 1] > greenDilate ? pixelBuffer[calcOffset + 1] : greenDilate);
redDilate = (pixelBuffer[calcOffset + 2] > redDilate ? pixelBuffer[calcOffset + 2] : redDilate);
blueErode = (pixelBuffer[calcOffset] < blueErode ? pixelBuffer[calcOffset] : blueErode);
greenErode = (pixelBuffer[calcOffset + 1] < greenErode ? pixelBuffer[calcOffset + 1] : greenErode);
redErode = (pixelBuffer[calcOffset + 2] < redErode ? pixelBuffer[calcOffset + 2] : redErode); } } }
blueErode = (applyBlue ? blueErode : pixelBuffer[byteOffset]); blueDilate = (applyBlue ? blueDilate : pixelBuffer[byteOffset]);
greenErode = (applyGreen ? greenErode : pixelBuffer[byteOffset + 1]); greenDilate = (applyGreen ? greenDilate : pixelBuffer[byteOffset + 1]);
redErode = (applyRed ? redErode : pixelBuffer[byteOffset + 2]); redDilate = (applyRed ? redDilate : pixelBuffer[byteOffset + 2]);
if (morphType == MorphologyOperationType.Erosion) { resultBuffer[byteOffset] = blueErode; resultBuffer[byteOffset + 1] = greenErode; resultBuffer[byteOffset + 2] = redErode; } else if (morphType == MorphologyOperationType.Dilation) { resultBuffer[byteOffset] = blueDilate; resultBuffer[byteOffset + 1] = greenDilate; resultBuffer[byteOffset + 2] = redDilate; }
resultBuffer[byteOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height); BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Parrot: Boundary Extraction, 3×3, Red, Green

Parrot: Boundary Extraction, 3x3, Red, Green

Implementing Image Addition

The sample source code encapsulates the process of combining two separate through means of addition. The AddImage method serves as a single declaration of addition functionality. This method has been defined as an targeting the class. Boundary Sharpen filtering implements addition.

The following code snippet provides the definition of the AddImage :

private static Bitmap AddImage(this Bitmapsource Bitmap, 
                               Bitmap addBitmap)
{
    BitmapData sourceData =
               sourceBitmap.LockBits(new Rectangle (0, 0,
               sourceBitmap.Width, sourceBitmap.Height),
               ImageLockMode.ReadOnly,
               PixelFormat.Format32bppArgb);

byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, resultBuffer, 0, resultBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
BitmapData addData = addBitmap.LockBits(new Rectangle(0, 0, addBitmap.Width, addBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] addBuffer = new byte[addData.Stride * addData.Height];
Marshal.Copy(addData.Scan0, addBuffer, 0, addBuffer.Length);
addBitmap.UnlockBits(addData);
for (int k = 0; k + 4 < resultBuffer.Length && k + 4 < addBuffer.Length; k += 4) { resultBuffer[k] = AddColors(resultBuffer[k], addBuffer[k]); resultBuffer[k + 1] = AddColors(resultBuffer[k + 1], addBuffer[k + 1]); resultBuffer[k + 2] = AddColors(resultBuffer[k + 2], addBuffer[k + 2]); resultBuffer[k + 3] = 255; }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }
private static byte AddColors(byte color1, byte color2) 
{
    int result = color1 + color2; 

return (byte)(result < 0 ? 0 : (result > 255 ? 255 : result)); }

Parrot: Boundary Extraction, 3×3, Red, Green, Blue

Parrot: Boundary Extraction, 3x3, Red, Green, Blue

Implementing Image Subtraction

In a similar fashion regarding the AddImage method the sample code defines the SubractImage method.  By definition this method serves as an targeting the class. Image subtraction has been implemented in Boundary Extraction and Boundary Tracing.

The definition of the SubtractImage method listed as follows:

private static Bitmap SubtractImage(this Bitmap sourceBitmap,  
                                         Bitmap subtractBitmap) 
{
    BitmapData sourceData = 
               sourceBitmap.LockBits(new Rectangle(0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, resultBuffer, 0, resultBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
BitmapData subtractData = subtractBitmap.LockBits(new Rectangle(0, 0, subtractBitmap.Width, subtractBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] subtractBuffer = new byte[subtractData.Stride * subtractData.Height];
Marshal.Copy(subtractData.Scan0, subtractBuffer, 0, subtractBuffer.Length);
subtractBitmap.UnlockBits(subtractData);
for (int k = 0; k + 4 < resultBuffer.Length && k + 4 < subtractBuffer.Length; k += 4) { resultBuffer[k] = SubtractColors(resultBuffer[k], subtractBuffer[k]);
resultBuffer[k + 1] = SubtractColors(resultBuffer[k + 1], subtractBuffer[k + 1]);
resultBuffer[k + 2] = SubtractColors(resultBuffer[k + 2], subtractBuffer[k + 2]);
resultBuffer[k + 3] = 255; }
Bitmap resultBitmap = new Bitmap (sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }
private static byte SubtractColors(byte color1, byte color2) 
{
    int result = (int)color1 - (int)color2; 

return (byte)(result < 0 ? 0 : result); }

 Parrot: Boundary Extraction, 3×3, Green

Parrot: Boundary Extraction, 3x3, Green

Implementing Image Boundary Extraction

In the sample source code processing Image Boundary Extraction can be achieved when invoking the BoundaryExtraction method. Defined as an , the BoundaryExtraction method targets the class.

As discussed earlier, this method performs Boundary Extraction through subtracting the source from a dilated copy of the source .

The following code snippet details the definition of the BoundaryExtraction method:

private static Bitmap
BoundaryExtraction(this Bitmap sourceBitmap, 
                   bool[,] se, bool applyBlue = true, 
                   bool applyGreen = true, bool applyRed = true) 
{
    Bitmap resultBitmap = 
           sourceBitmap.MorphologyOperation(se,  
           MorphologyOperationType.Dilation, applyBlue,  
                                  applyGreen, applyRed); 

resultBitmap = resultBitmap.SubtractImage(sourceBitmap);
return resultBitmap; }

Parrot: Boundary Extraction, 3×3, Red, Blue

Parrot: Boundary Extraction, 3x3, Red, Blue

Implementing Image Boundary Sharpening

Boundary Sharpening in the sample source code has been implemented through the definition of the BoundarySharpen method. The BoundarySharpen targets the class. The following code snippet provides the definition:

private static Bitmap 
BoundarySharpen(this Bitmap sourceBitmap, 
                bool[,] se, bool applyBlue = true, 
                bool applyGreen = true, bool applyRed = true) 
{
    Bitmap resultBitmap = 
           sourceBitmap.BoundaryExtraction(se, applyBlue, 
                                           applyGreen, applyRed); 

resultBitmap = sourceBitmap.MorphologyOperation(se, MorphologyOperationType.Dilation, applyBlue, applyGreen, applyRed).AddImage(resultBitmap);
return resultBitmap; }

Parrot: Boundary Extraction, 3×3, Green

Parrot: Boundary Extraction, 3x3, Green

Implementing Image Boundary Tracing

Boundary Tracing has been defined through the BoundaryTrace , which targets the class. Similar to the BoundarySharpen method this method performs Boundary Extraction, the result of which serves to be subtracted from the original source . Subtracting boundaries/edges result in those boundaries/edges being darkened, or traced. The definition of the BoundaryTracing detailed as follows:

private static Bitmap
BoundaryTrace(this Bitmap sourceBitmap, 
              bool[,] se, bool applyBlue = true, 
              bool applyGreen = true, bool applyRed = true) 
{
    Bitmap resultBitmap =
    sourceBitmap.BoundaryExtraction(se, applyBlue,  
                                    applyGreen, applyRed); 

resultBitmap = sourceBitmap.SubtractImage(resultBitmap);
return resultBitmap; }

Parrot: Boundary Extraction, 3×3, Green, Blue

Parrot: Boundary Extraction, 3x3, Green, Blue

Implementing a Wrapper Method

The BoundaryExtractionFilter method is the only method defined as publicly accessible. Following convention, this method’s definition signals the method as an targeting the class. This method has the intention of acting as a wrapper method, a single method capable of performing Boundary Extraction, Boundary Sharpening and Boundary Tracing, depending on method parameters.

The definition of the BoundaryExtractionFilter method detailed by the following code snippet:

public static Bitmap
BoundaryExtractionFilter(this Bitmap sourceBitmap, 
                         bool[,] se, BoundaryExtractionFilterType  
                         filterType, bool applyBlue = true, 
                         bool applyGreen = true, bool applyRed = true) 
{
    Bitmap resultBitmap = null; 

if (filterType == BoundaryExtractionFilterType.BoundaryExtraction) { resultBitmap = sourceBitmap.BoundaryExtraction(se, applyBlue, applyGreen, applyRed); } else if (filterType == BoundaryExtractionFilterType.BoundarySharpen) { resultBitmap = sourceBitmap.BoundarySharpen(se, applyBlue, applyGreen, applyRed); } else if (filterType == BoundaryExtractionFilterType.BoundaryTrace) { resultBitmap = sourceBitmap.BoundaryTrace(se, applyBlue, applyGreen, applyRed); }
return resultBitmap; }

Parrot: Boundary Extraction, 3×3, Red, Green, Blue

Parrot: Boundary Extraction, 3x3, Red, Green, Blue

Sample Images

This article features a number of sample images. All featured images have been licensed allowing for reproduction. The following images feature as sample images:

1280px-Ara_macao_-Diergaarde_Blijdorp_-flying-8a

Ara_macao_-flying_away-8a

Ara_ararauna_Luc_Viatour

1280px-Macaws_at_Seaport_Village_-USA-8a

Ara_macao_-on_a_small_bicycle-8

Psarisomus_dalhousiae_-_Kaeng_Krachan

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: Image ASCII Art

Article Purpose

This article explores the concept of rendering from source . Beyond exploring concepts this article also provides a practical implementation of all the steps required in creating an ASCII Filter.

Sir Tim Berners-Lee: 2 Pixels Per Character, 12 Characters,  Font Size 4, Zoom 100

Sir Tim Berners-Lee 2 Pixels Per Character, 12 Characters,  Font Size 4, Zoom 100

Sample Source Code

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

Using the Sample Application

The sample source code that accompanies this article includes a sample application. The concepts illustrated in this article can tested and replicated using the sample application.

The sample application user interface implements a variety of functionality which can be described as follows:

  • Loading/Saving Images – Users are able to load source/input from the local system through clicking the Load Image button. Rendered can be saved as an file when clicking the Save Image button.
  • Pixels Per Character – This configuration option determines the number of pixels represented by a single character. Lower values result in better detail/definition and a larger proportional output. Higher values result in less detail/definition and a smaller proportional output.
  • Character Count – The number of unique characters to be rendered can be adjusted through updating this value. This value can be considered similar to number of shades of gray in a  .
  • Font Size – This option determines the Font Size related to the rendered text.
  • Zoom Level – Configure this value in order to apply a scaling level when rendering text.
  • Copy to Clipboard – Copy the current to the Windows Clipboard, in Rich Text Format.

The following image is screenshot of the Image ASCII Art sample application is action:

Image ASCII Art Sample Application

Image ASCII Art Sample Application

Anders Hejlsberg: 1 Pixel Per Character, 24 Characters, Font Size 6, Zoom 20

Anders Hejlsberg: 1 Pixel Per Character, 24 Characters, Font Size 6, Zoom 20

Converting Pixels to Characters

in various forms have been part of computer culture since the pioneering days of computing. From we gain the following:

ASCII art is a graphic design technique that uses computers for presentation and consists of pictures pieced together from the 95 printable (from a total of 128) characters defined by the ASCII Standard from 1963 and ASCII compliant character sets with proprietary extended characters (beyond the 128 characters of standard 7-bit ASCII). The term is also loosely used to refer to text based visual art in general. ASCII art can be created with any text editor, and is often used with free-form languages. Most examples of ASCII art require a fixed-width font (non-proportional fonts, as on a traditional typewriter) such as Courier for presentation.

Among the oldest known examples of ASCII art are the creations by computer-art pioneer Kenneth Knowlton from around 1966, who was working for Bell Labs at the time.[1] "Studies in Perception I" by Ken Knowlton and Leon Harmon from 1966 shows some examples of their early ASCII art.[2]

One of the main reasons ASCII art was born was because early printers often lacked graphics ability and thus characters were used in place of graphic marks. Also, to mark divisions between different print jobs from different users, bulk printers often used ASCII art to print large banners, making the division easier to spot so that the results could be more easily separated by a computer operator or clerk. ASCII art was also used in early e-mail when images could not be embedded.

Bjarne Stroustrup: 1 Pixel Per Character, 12 Characters, Font Size 6, Zoom 60

Bjarne Stroustrup: 1 Pixel Per Character, 12 Characters, Font Size 6, Zoom 60

This article explores the steps involved in rendering text representing , implementing source/input in rendering text representations. The following sections details the steps required to render text from source/input :

  1. Generate Random Characters – Generate a consisting of random characters. The number of characters will be determined through user input relating to the Character Count option. When generating the random ensure that all characters added to the are unique. In addition avoid adding control characters or punctuation characters. Control characters are non-visible characters such as Start of Text, Beep, New Line or Carriage Return. Most punctuation characters occupy a lot less screen space compared to regular alphabet characters.
  2. Determine Row and Column Count – Rows and Columns in terms of the Character Count option indicate the ratio between pixels and characters. The number of rows equate to the height in pixels divided by the Character Count. The number of columns equate to the width in pixels divided by the Character Count.
  3. Iterate Rows/Columns and Determine Colour Averages – Iterate pixels in terms of a rows and columns grid strategy. Calculate the sum total of each grid region’s colour values. Calculate the average/mean colour value through dividing the colour sum total by the Character Count squared.
  4. Map Average Colour Intensity to a Character – Using the average colour values calculate in the previous step, calculate a colour intensity value ranging between 0 and the number of randomly generate characters. The intensity value should be implemented as an array index in accessing the of random characters. All of the pixels included in calculating an average value should be represented by the random character located at the index equating to the colour average intensity value.

Linus Torvalds: 1 Pixel Per Character, 16 Characters, Font Size 5, Zoom 60

Linus Torvalds: 1 Pixel Per Character, 16 Characters, Font Size 5, Zoom 60

Converting Text to an Image

When rendering high definition the resulting text can easily consist of several thousand characters. Attempting to display such a vast number of text in a traditional text editor in most scenarios would be futile. An alternative method of retaining a high definition whilst still being viewable can be achieved through creating an from the rendered text and then reducing the dimensions.

The sample code employs the following steps when converting rendered text to an :

  1. Determine Required Image Dimensions – Determine the dimensions required to fit the rendered text.
  2. Create a new Image and set the background colour – After having determined the required dimensions create a new consisting of those dimensions. Set every pixel in the new to Black.
  3. Draw Rendered Text – The rendered text should be drawn on the new in plain White.
  4. Resize Image – In order to ensure more manageable dimensions resize the with a specified factor.

Alan Turing: 1 Pixel Per Character, 16 Characters, Font Size 4, Zoom 100

Alan Turing: 1 Pixel Per Character, 16 Characters, Font Size 4, Zoom 100

Implementing an Image ASCII Filter

The sample source code implements four methods when implementing an ASCII Filter, the methods are:

  • ASCIIFilter
  • GenerateRandomString
  • RandomStringSort
  • GetColorCharacter

The GenerateRandomString  method, as the name implies, generates a consisting of randomly selected characters. The number of characters contained in the will be determined by the parameter value passed to this method. The following code snippet provides the implementation of the GenerateRandomString method:

private static string GenerateRandomString(int maxSize) 
{
    StringBuilder stringBuilder = new StringBuilder(maxSize); 
    Random randomChar = new Random(); 

char charValue;
for (int k = 0; k < maxSize; k++) { charValue = (char)(Math.Floor(255 * randomChar.NextDouble() * 4));
if (stringBuilder.ToString().IndexOf(charValue) != -1) { charValue = (char)(Math.Floor((byte)charValue * randomChar.NextDouble())); }
if (Char.IsControl(charValue) == false && Char.IsPunctuation(charValue) == false && stringBuilder.ToString().IndexOf(charValue) == -1) { stringBuilder.Append(charValue); randomChar = new Random((int)((byte)charValue * (k + 1) + DateTime.Now.Ticks)); } else { randomChar = new Random((int)((byte)charValue * (k + 1) + DateTime.UtcNow.Ticks)); k -= 1; } }
return stringBuilder.ToString().RandomStringSort(); }

Sir Tim Berners-Lee: 4 Pixels Per Character, 16 Characters, Font Size 6, Zoom 100

Sir Tim Berners-Lee, 4 Pixels Per Character, 16 Characters, Font Size 6, Zoom 100

The RandomStringSort method has been defined as an targeting the . This method provides a means of sorting a in a random manner, in essence shuffling a ’s characters. The definition as follows:

public static string RandomStringSort(this string stringValue) 
{
    char[] charArray = stringValue.ToCharArray(); 

Random randomIndex = new Random((byte)charArray[0]); int iterator = charArray.Length;
while(iterator > 1) { iterator -= 1;
int nextIndex = randomIndex.Next(iterator + 1);
char nextValue = charArray[nextIndex]; charArray[nextIndex] = charArray[iterator]; charArray[iterator] = nextValue; }
return new string(charArray); }

Anders Hejlsberg: 3 Pixels Per Character, 12 Characters, Font Size 5, Zoom 50

Anders Hejlsberg: 3 Pixels Per Character, 12 Characters, Font Size 5, Zoom 50

The sample source code defines the GetColorCharacter method, intended to map pixels to character values. This method has been defined as an targeting the . The definition as follows:

private static string colorCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 

private static string GetColorCharacter(int blue, int green, int red) { string colorChar = String.Empty; int intensity = (blue + green + red) / 3 * (colorCharacters.Length - 1) / 255;
colorChar = colorCharacters.Substring(intensity, 1).ToUpper(); colorChar += colorChar.ToLower();
return colorChar; }

Bjarne Stroustrup: 1 Pixel Per Character, 12 Characters, Font Size 4, Zoom 100

Bjarne Stroustrup: 1 Pixel Per Character, 12 Characters, Font Size 4, Zoom 100

The ASCIIFilter method defined by the sample source code has the task of translating source/input into text based . This method has been defined as an targeting the class. The following code snippet provides the definition:

public static string ASCIIFilter(this Bitmap sourceBitmap, int pixelBlockSize,  
                                                           int colorCount = 0) 
{
    BitmapData sourceData = sourceBitmap.LockBits(new Rectangle (0, 0, 
                            sourceBitmap.Width, sourceBitmap.Height), 
                                              ImageLockMode.ReadOnly, 
                                        PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); sourceBitmap.UnlockBits(sourceData);
StringBuilder asciiArt = new StringBuilder();
int avgBlue = 0; int avgGreen = 0; int avgRed = 0; int offset = 0;
int rows = sourceBitmap.Height / pixelBlockSize; int columns = sourceBitmap.Width / pixelBlockSize;
if (colorCount > 0) { colorCharacters = GenerateRandomString(colorCount); }
for (int y = 0; y < rows; y++) { for (int x = 0; x < columns; x++) { avgBlue = 0; avgGreen = 0; avgRed = 0;
for (int pY = 0; pY < pixelBlockSize; pY++) { for (int pX = 0; pX < pixelBlockSize; pX++) { offset = y * pixelBlockSize * sourceData.Stride + x * pixelBlockSize * 4;
offset += pY * sourceData.Stride; offset += pX * 4;
avgBlue += pixelBuffer[offset]; avgGreen += pixelBuffer[offset + 1]; avgRed += pixelBuffer[offset + 2]; } }
avgBlue = avgBlue / (pixelBlockSize * pixelBlockSize); avgGreen = avgGreen / (pixelBlockSize * pixelBlockSize); avgRed = avgRed / (pixelBlockSize * pixelBlockSize);
asciiArt.Append(GetColorCharacter(avgBlue, avgGreen, avgRed)); }
asciiArt.Append("\r\n" ); }
return asciiArt.ToString(); }

Linus Torvalds: 1 Pixel Per Character, 8 Characters, Font Size 4, Zoom 80

Linus Torvalds: 1 Pixel Per Character, 8 Characters, Font Size 4, Zoom 80

Implementing Text to Image Functionality

The sample source code implements the GDI+ class when drawing rendered text onto . The sample source code defines the TextToImage method, an extending the . The definition listed as follows:

public static Bitmap TextToImage(this string text, Font font,  
                                                float factor) 
{
    Bitmap textBitmap = new Bitmap(1, 1); 

Graphics graphics = Graphics.FromImage(textBitmap);
int width = (int)Math.Ceiling( graphics.MeasureString(text, font).Width * factor);
int height = (int)Math.Ceiling( graphics.MeasureString(text, font).Height * factor);
graphics.Dispose();
textBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
graphics = Graphics.FromImage(textBitmap); graphics.Clear(Color.Black);
graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.ScaleTransform(factor, factor); graphics.DrawString(text, font, Brushes.White, new PointF(0, 0));
graphics.Flush(); graphics.Dispose();
return textBitmap; }

Sir Tim Berners-Lee: 1 Pixel Per Character, 32 Characters, Font Size 4, Zoom 100

Sir Tim Berners-Lee, 1 Pixel Per Character, 32 Characters, Font Size 4, Zoom 100

Sample Images

This article features a number of sample images. All featured images have been licensed allowing for reproduction. The following image files feature a sample images:

The following section lists the original image files that were used as source/input images in generating the images found throughout this article.

Alan Turing

Alan Turing

Anders Hejlsberg

Anders Hejlsberg

Bjarne Stroustrup

Bjarne Stroustrup

Linus Torvalds

Linus Torvalds

Tim Berners-Lee

Tim Berners-Lee

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:

« Previous PageNext Page »


Dewald Esterhuizen

Blog Stats

  • 817,511 hits

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

Join 228 other followers

Archives

Twitter feed


%d bloggers like this: