C# How to: Bitmap Colour Balance

Article Purpose

This explores the concept of manipulating the of a  . values are updated by directly manipulating a ’s underlying pixel data, no GDI drawing code required.

Sample source code

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

Download Sample Source Code

Using the sample Application

The concepts explored in this are easily illustrated using the Sample Application provided with the sample source code. The sample application is implemented as a .

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:


BitmapColorBalance


Colour Balance

Whilst developing the sample source code and writing this I found the article on 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 and , color balance is the global adjustment of the intensities of the colors (typically red, green, and blue ). 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 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 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 we do not perform traditional 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 object instance. In order for our changes to persist we will also explore the tasks required to re-create an instance of the 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 ’s underlying array of colour components. Its important to remember that colour components are ordered: Blue, Green, Red, Alpha.

To access a ’s internal array of colour components we make use of the method. From :

Use the 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 method, although the LockBits method offers better performance for large-scale changes.

The specifies the attributes of the , 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 enumeration that contains a specific bits-per-pixel (BPP) value. Using values such as and will throw an . Also, passing the incorrect pixel format for a bitmap will throw an .

Why lock a in memory? The architecture behind the and memory management through the necessitates locking a in memory before accessing the underlying data. As an application executes periodically the invokes various operations. Part of the ’s function involves relocating objects in memory. As described by :

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 through invoking the method you are effectively signalling the to not relocate in memory the being locked. Consider a scenario where, whilst accessing and manipulating a array of colour components, the starts to relocate the associated instance in memory. Without warning the memory being referenced is no longer associated with the object initially referenced.

Note: If you lock a into memory you must also unlock the object by invoking the method.

The method defines a return value of type . Properties exposed by the class to pay attention to: and , as discussed in the following section.

Copying  bytes from a Bitmap

The property is described by 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 are required to represent a row of pixels. A 32 bits per pixel Argb equates to each pixel occupying 4 bytes of memory. The value of the property can be calculated as [Bitmap Width x 4] : rounded up to the first multiple of 4.

Also defined by the class is the property . We can consider a as a grid of rows and columns, essentially a two dimensional array with each element being 1 . The property provides us with how many colour components are expressed by a ’s row of pixels. To determine the total number of colour components expressed by a we simply have to multiply the properties and .

The property, which is of type , is described by 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 ’s internal colour component array. To perform the actual copy operation we invoke the method.

The ColorBalance Extension method

The crux of this 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 class.

The ColorBalance method requires 3 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 and copy the specified object’s underlying values.

The bulk of the work performed by this method happens within the for loop which iterates through the buffer of colour components. Notice how with each loop we increment by 4, the reason being each loop operation modifies 4 at a time. One pixel in terms of the ARGB format equates to 4 , thus each time we loop, we modify 1 pixel.

The 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 image based on the same size dimensions as the source image. Once the has been locked into memory we copy the modified buffer using the method.

Sample Images

This section illustrates a few scenarios implementing the 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


Airport_Original1


Colour Balanced Images

 

Airport1 Airport2
Airport3 Airport4
Airport5 Airport6

The Original Image


Airport_Original2


Colour Balanced Images

 

Airport7 Airport8
Airport9 Airport10
Airport11 Airport12

Related Articles

41 Responses to “C# How to: Bitmap Colour Balance”


  1. 1 neticous May 29, 2013 at 11:06 PM

    I would expect color balance to work more like this.
    change
    //blue = 255.0f / blueLevelFloat * (float)pixelBuffer[k];
    //green = 255.0f / greenLevelFloat * (float)pixelBuffer[k + 1];
    //red = 255.0f / redLevelFloat * (float)pixelBuffer[k + 2];
    To
    blue = blueLevelFloat / 255.0f * (float)pixelBuffer[k];
    green = greenLevelFloat / 255.0f * (float)pixelBuffer[k + 1];
    red = redLevelFloat / 255.0f * (float)pixelBuffer[k + 2];

  2. 2 neticous May 29, 2013 at 11:13 PM

    Maybe not. I just need it the work that way.
    Full up 255 for all is full color.
    I still do not think this is true color balance.


  1. 1 C# How to: Bitmap Colour Tint | Software by Default Trackback on April 26, 2013 at 1:07 AM
  2. 2 C# How to: Bitwise Bitmap Blending | Software by Default Trackback on April 26, 2013 at 1:13 AM
  3. 3 C# How to: Image Contrast | Software by Default Trackback on April 26, 2013 at 1:15 AM
  4. 4 C# How to: Image Solarise | Software by Default Trackback on April 26, 2013 at 1:17 AM
  5. 5 C# How to: Bitmap Colour Shading | Software by Default Trackback on April 26, 2013 at 1:19 AM
  6. 6 C# How to: Bi-tonal Bitmaps | Software by Default Trackback on April 26, 2013 at 1:20 AM
  7. 7 C# How to: Image Arithmetic | Software by Default Trackback on April 27, 2013 at 8:21 AM
  8. 8 C# How to: Image Convolution | Software by Default Trackback on May 1, 2013 at 5:42 PM
  9. 9 C# How to: Image filtering implemented using a ColorMatrix | Software by Default Trackback on May 1, 2013 at 9:04 PM
  10. 10 C# How to: Blending Bitmap images using colour filters | Software by Default Trackback on May 1, 2013 at 10:50 PM
  11. 11 C# How to: Decoding/Converting Base64 strings to Bitmap images | Software by Default Trackback on May 2, 2013 at 9:35 PM
  12. 12 C# How to: Image Edge Detection | Software by Default Trackback on May 11, 2013 at 1:22 PM
  13. 13 C# How to: Difference Of Gaussians | Software by Default Trackback on May 18, 2013 at 12:50 AM
  14. 14 C# How to: Image Median Filter | Software by Default Trackback on May 18, 2013 at 4:15 AM
  15. 15 C# How to: Image Unsharp Mask | Software by Default Trackback on May 18, 2013 at 12:18 PM
  16. 16 C# How to: Image Colour Average | Software by Default Trackback on May 18, 2013 at 9:48 PM
  17. 17 C# How to: Image Erosion and Dilation | Software by Default Trackback on May 19, 2013 at 10:24 AM
  18. 18 C# How to: Morphological Edge Detection | Software by Default Trackback on May 25, 2013 at 8:22 AM
  19. 19 C# How to: Boolean Edge Detection | Software by Default Trackback on June 1, 2013 at 2:08 AM
  20. 20 C# How to: Gradient Based Edge Detection | Software by Default Trackback on June 1, 2013 at 4:46 PM
  21. 21 C# How to: Image Cartoon Effect | Software by Default Trackback on June 2, 2013 at 4:13 PM
  22. 22 C# How to: Sharpen Edge Detection | Software by Default Trackback on June 7, 2013 at 5:11 AM
  23. 23 C# How to: Calculating Gaussian Kernels | Software by Default Trackback on June 8, 2013 at 10:58 AM
  24. 24 C# How to: Image Blur | Software by Default Trackback on June 9, 2013 at 10:19 PM
  25. 25 C# How to: Image Transform Rotate | Software by Default Trackback on June 16, 2013 at 10:39 AM
  26. 26 C# How to: Image Transform Shear | Software by Default Trackback on June 16, 2013 at 5:45 PM
  27. 27 C# How to: Compass Edge Detection | Software by Default Trackback on June 22, 2013 at 9:34 PM
  28. 28 C# How to: Oil Painting and Cartoon Filter | Software by Default Trackback on June 30, 2013 at 10:47 AM
  29. 29 C# How to: Stained Glass Image Filter | Software by Default Trackback on June 30, 2013 at 10:49 AM
  30. 30 C# How to: Generate a Web Service from WSDL | Software by Default Trackback on June 30, 2013 at 4:07 PM
  31. 31 C# How to: Bitmap Colour Substitution implementing thresholds | Software by Default Trackback on July 6, 2013 at 4:32 PM
  32. 32 C# How to: Swapping Bitmap ARGB Colour Channels | Software by Default Trackback on July 6, 2013 at 5:01 PM
  33. 33 C# How to: Image filtering by directly manipulating Pixel ARGB values | Software by Default Trackback on July 8, 2013 at 2:57 AM
  34. 34 C# How to: Image ASCII Art | Software by Default Trackback on July 14, 2013 at 7:22 AM
  35. 35 C# How to: Weighted Difference of Gaussians | Software by Default Trackback on July 14, 2013 at 8:11 PM
  36. 36 C# How to: Image Boundary Extraction | Software by Default Trackback on July 21, 2013 at 10:23 AM
  37. 37 C# How to: Image Abstract Colours Filter | Software by Default Trackback on July 28, 2013 at 7:41 PM
  38. 38 C# How to: Fuzzy Blur Filter | Software by Default Trackback on August 9, 2013 at 6:39 AM
  39. 39 C# How to: Image Distortion Blur | Software by Default Trackback on August 9, 2013 at 10:13 PM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




about.me :: Dewald Esterhuizen

Dewald Esterhuizen

Blog Stats

  • 144,752 hits

Gravatar :: Dewald Esterhuizen

SoftwareByDefault QR Code

SoftwareByDefault QR Code

I review for the O'Reilly Blogger Review Program

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

Join 151 other followers

Archives

Twitter feed

del.icio.us Links

http://softwarebydefault.com
http://softwarebydefault.com
http://softwarebydefault.com
http://softwarebydefault.com
http://softwarebydefault.com

Follow

Get every new post delivered to your Inbox.

Join 151 other followers

%d bloggers like this: