Article Purpose
This article explores the concept of manipulating the Colour Balance of a Bitmap image. Colour Balance values are updated by directly manipulating a Bitmap’s underlying pixel data, no GDI drawing code required.
Sample source code
This article is accompanied by a sample source code Visual Studio project which is available for download here.

Using the sample Application
The 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 Bitmap Colour Balance application enables the user to load an input image file from the local file system. The user interface provides three trackbar controls representing the colour components Blue, Green and Red. Possible values range from 0 to 255 inclusive. The application performs image filtering on the specified image as the user moves the colour component sliders.
If the user desires to save modified/filtered images to the local file system the sample application makes provision through the Save Button.
The following image provides a screenshot of the Bitmap Colour Balance application in action:

Colour Balance
Whilst developing the sample source code and writing this article I found the Color Balance article on Wikipedia.org to be very informative and comprehensive. I would recommend it as a must read for developers with little or no experience around colour representation in digital imaging.
From Wikipedia we get the following quote:
In photography and image processing, color balance is the global adjustment of the intensities of the colors (typically red, green, and blue primary colors). An important goal of this adjustment is to render specific colors – particularly neutral colors – correctly; hence, the general method is sometimes called gray balance, neutral balance, or white balance. Color balance changes the overall mixture of colors in an image and is used for color correction; generalized versions of color balance are used to get colors other than neutrals to also appear correct or pleasing.
From the quoted text we determine that Colour Balance refers to a method of implementing image filtering as a corrective action when the colours expressed by an image vary from the expected norm.
Literally as the name implies Colour Balance filtering attempts to provide greater balance in image colours. The colours considered to be out of balance with the rest of the image will be filtered out to varying degrees resulting in an image having a more natural appearance.
Accessing Pixel data directly
In this article we do not perform traditional Image drawing operations such as the functionality exposed by the GDI+ Library. We are going to explore the tasks involved in directly accessing and manipulating the pixel buffer data that underlies a Bitmap object instance. In order for our changes to persist we will also explore the tasks required to re-create an instance of the Bitmap class and explicitly set/populate pixel data. The tasks referred to are discussed and implemented in the following section.
Locking the bytes inside a Bitmap
In this section we’ll be exploring a brief overview of how to access a Bitmap’s underlying byte array of colour components. Its important to remember that colour components are ordered: Blue, Green, Red, Alpha.
To access a Bitmap’s internal byte array of colour components we make use of the Bitmap.LockBits method. From MSDN documentation:
Use the LockBits method to lock an existing bitmap in system memory so that it can be changed programmatically. You can change the color of an image with the SetPixel method, although the LockBits method offers better performance for large-scale changes.
The BitmapData specifies the attributes of the Bitmap, such as size, pixel format, the starting address of the pixel data in memory, and length of each scan line (stride).
When calling this method, you should use a member of the System.Drawing.Imaging.PixelFormat enumeration that contains a specific bits-per-pixel (BPP) value. Using System.Drawing.Imaging.PixelFormat values such as Indexed and Gdi will throw an System.ArgumentException. Also, passing the incorrect pixel format for a bitmap will throw an System.ArgumentException.
Why lock a Bitmap in memory? The architecture behind the Common Language Runtime (CLR) and memory management through the Garbage Collector necessitates locking a Bitmap in memory before accessing the underlying data. As an application executes periodically the CLR invokes various Garbage Collection operations. Part of the Garbage Collector’s function involves relocating objects in memory. As described by MSDN documentation:
A garbage collection has the following phases:
-
A marking phase that finds and creates a list of all live objects.
-
A relocating phase that updates the references to the objects that will be compacted.
-
A compacting phase that reclaims the space occupied by the dead objects and compacts the surviving objects. The compacting phase moves objects that have survived a garbage collection toward the older end of the segment.
When locking a Bitmap through invoking the Bitmap.LockBits method you are effectively signalling the Garbage Collector to not relocate in memory the Bitmap being locked. Consider a scenario where, whilst accessing and manipulating a byte array of colour components, the Garbage Collector starts to relocate the associated Bitmap instance in memory. Without warning the memory being referenced is no longer associated with the Bitmap object initially referenced.
Note: If you lock a Bitmap into memory you must also unlock the Bitmap object by invoking the Bitmap.UnlockBits method.
The Bitmap.LockBits method defines a return value of type BitmapData. Properties exposed by the BitmapData class to pay attention to: BitmapData.Scan0 and BitmapData.Stride, as discussed in the following section.
Copying bytes from a Bitmap
The BitmapData.Stride property is described by MSDN documentation as follows:
The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary. If the stride is positive, the bitmap is top-down. If the stride is negative, the bitmap is bottom-up.
In essence a scan line refers to how many bytes are required to represent a row of pixels. A 32 bits per pixel Argb Bitmap equates to each pixel occupying 4 bytes of memory. The value of the BitmapData.Stride property can be calculated as [Bitmap Width x 4] : rounded up to the first multiple of 4.
Also defined by the BitmapData class is the property BitmapData.Height. We can consider a Bitmap as a grid of rows and columns, essentially a two dimensional array with each element being 1 byte. The BitmapData.Stride property provides us with how many colour components are expressed by a Bitmap’s row of pixels. To determine the total number of colour components expressed by a Bitmap we simply have to multiply the properties BitmapData.Stride and BitmapData.Height.
The BitmapData.Scan0 property, which is of type IntPtr, is described by MSDN documentation as follows:
Gets or sets the address of the first pixel data in the bitmap. This can also be thought of as the first scan line in the bitmap.
We now know the size of the data we want to copy, we also know the address in memory where to start copying a Bitmap’s internal colour component byte array. To perform the actual copy operation we invoke the Marshal.Copy method.
The ColorBalance Extension method
The crux of this article can be found in the ColorBalance extension method. It is only within the ColorBalance method that we will be referencing pixel data directly. Note that this method is defined as an extension method targeting the Bitmap class.
The ColorBalance method requires 3 byte parameters. The parameters represent the values to be used in calculating resulting Blue, Green and Red colour component values. The following code snippet details the definition of the ColorBalance method:
public static Bitmap ColorBalance(this Bitmap sourceBitmap, byte blueLevel,
byte greenLevel, byte redLevel)
{
BitmapData sourceData = sourceBitmap.LockBits(new ectangle (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);
float blue = 0;
float green = 0;
float red = 0;
float blueLevelFloat = blueLevel;
float greenLevelFloat = greenLevel;
float redLevelFloat = redLevel;
for (int k = 0; k + 4 < pixelBuffer.Length; k += 4)
{
blue = 255.0f / blueLevelFloat * (float )pixelBuffer[k];
green = 255.0f / greenLevelFloat * (float)pixelBuffer[k + 1];
red = 255.0f / redLevelFloat * (float)pixelBuffer[k + 2];
if (blue > 255) {blue = 255;}
else if (blue < 0) {blue = 0;}
if (green > 255) {green = 255;}
else if (green < 0) {green = 0;}
if (red > 255) {red = 255;}
else if (red < 0) {red = 0;}
pixelBuffer[k] = (byte)blue;
pixelBuffer[k + 1] = (byte)green;
pixelBuffer[k + 2] = (byte)red;
}
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;
}
As discussed in previous sections we create an array of bytes and copy the specified Bitmap object’s underlying byte values.
The bulk of the work performed by this method happens within the for loop which iterates through the byte buffer of colour components. Notice how with each loop we increment by 4, the reason being each loop operation modifies 4 bytes at a time. One pixel in terms of the ARGB format equates to 4 bytes, thus each time we loop, we modify 1 pixel.
The Colour Balance formula implemented can be expressed as follows:
R = 255 / Rw * R1
G = 255 / Gw * G1
B = 255 / Bw * B1
Where Rw Gw and Bw represents the value of a colour component believed to represent White and R1 G1 and B1 representing the original value of a colour component before implementing the colour balance formula.
The value of a colour component can only range between 0 and 255 inclusive. Before setting the newly calculated values we have to check if calculated values range between 0 and 255.
The last operation performed by the ColorBalance Extension method involves creating a new Bitmap image based on the same size dimensions as the source image. Once the Bitmap has been locked into memory we copy the modified byte buffer using the Marshal.Copy method.
Sample Images
This section illustrates a few scenarios implementing the Colour Balance filter.
Fun Fact: The first image is a photograph I snapped at OR Tambo International Airport, Johannesburg, South Africa. The second photograph I snapped at Hosea Kutako International Airport, Windhoek, Namibia.
The Original Image
Colour Balanced Images
The Original Image
Colour Balanced Images
Related Articles
Like this:
Like Loading...