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
Using the sample 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:
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.
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.
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.
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 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.
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:
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.
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
- C# How to: Image filtering by directly manipulating Pixel ARGB values
- C# How to: Image filtering implemented using a ColorMatrix
- C# How to: Blending Bitmap images using colour filters
- C# How to: Bitmap Colour Substitution implementing thresholds
- C# How to: Generating Icons from Images
- C# How to: Swapping Bitmap ARGB Colour Channels
- C# How to: Bitmap Pixel manipulation using LINQ Queries
- C# How to: Linq to Bitmaps – Partial Colour Inversion
- C# How to: Bitmap Colour Balance
- C# How to: Bi-tonal Bitmaps
- C# How to: Bitmap Colour Tint
- C# How to: Bitmap Colour Shading
- C# How to: Image Solarise
- C# How to: Image Contrast
- C# How to: Bitwise Bitmap Blending