The purpose of this article is to explore and illustrate the concept of creating bitonal bitmap images. Colour images are manipulated in such a fashion to only express two colours. The colours expressed are configurable. A threshold value determines which of the two configured colours will be applied to a pixel.

There are no special requirements or instructions for building the sample source code.

The concepts explored in this article are easily illustrated using the Sample Application provided with the sample source code. The sample application is implemented as a Windows Forms application.
The Bitonal Bitmap application enables the user to load an input image file from the local file system. The user interface defines two panels representing the two colours used when creating the resulting bitonal Bitmap. When clicking on either panel the user will be presented with a colour dialog, allowing the user to change the colour of the specific panel.
The user interface also provides a trackbar which allows the user to set the threshold used to calculate if a pixel colour should be set to the dark colour or light colour value.
If the user desires to save resulting bitonal images to the local file system the sample application makes provision through the Save Button.
The following image provides a screenshot of the Bitonal Bitmap application in action:

The Bitonal Extension method defines all of the operations involved in creating Bitonal images. This method is an extension method targeting the Bitmap class. Note that the Bitonal extension method manipulates pixel colour components directly, in other words updating a pixel’s Alpha, Red, Green and Blue values directly.
The following code snippet provides the definition of the Bitonal method:
public static Bitmap Bitonal(this Bitmap sourceBitmap, Color darkColor,
Color lightColor, int threshold)
{
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);
for (int k = 0; k + 4 < pixelBuffer.Length; k += 4)
{
if (pixelBuffer[k] + pixelBuffer[k + 1] +
pixelBuffer[k + 2] <= threshold)
{
pixelBuffer[k] = darkColor.B;
pixelBuffer[k + 1] = darkColor.G;
pixelBuffer[k + 2] = darkColor.R;
}
else
{
pixelBuffer[k] = lightColor.B;
pixelBuffer[k + 1] = lightColor.G;
pixelBuffer[k + 2] = lightColor.R;
}
}
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(pixelBuffer, 0, resultData.Scan0, pixelBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap;
}
public static Bitmap Bitonal(this Bitmap sourceBitmap, Color darkColor, Color lightColor, int threshold) { 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); for (int k = 0; k + 4 < pixelBuffer.Length; k += 4) { if (pixelBuffer[k] + pixelBuffer[k + 1] + pixelBuffer[k + 2] <= threshold) { pixelBuffer[k] = darkColor.B; pixelBuffer[k + 1] = darkColor.G; pixelBuffer[k + 2] = darkColor.R; } else { pixelBuffer[k] = lightColor.B; pixelBuffer[k + 1] = lightColor.G; pixelBuffer[k + 2] = lightColor.R; } } 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(pixelBuffer, 0, resultData.Scan0, pixelBuffer.Length); resultBitmap.UnlockBits(resultData); return resultBitmap; }
The next step involves iterating through the byte buffer of colour components. Notice how each iteration modifies an entire pixel by iterating by 4. In order to determine to which colour a pixel should be set, the sum of Red, Green and Blue colour components is to the threshold parameter.
The last step performed is to copy the modified pixel buffer into a new Bitmap object.

The original source image used to create all of the bitonal sample images in this article has been licensed under the Creative Commons Attribution-Share Alike 3.0 Unported, 2.5 Generic, 2.0 Generic and 1.0 Generic license. The original image is attributed to Kenneth Dwain Harrelson and can be downloaded from Wikipedia.
The Original Image |
|
![]() |
|
Bitonal Images |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
This article is based on an article originally posted on my blog: http://softwarebydefault.com/2013/04/12/bitonal-bitmaps/ If you have any questions/comments please feel free to make use of the Q&A section on this page, also please remember to rate this article.