Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Applying a Color Wash to Images

0.00/5 (No votes)
15 Dec 2005 1  
This article demonstrates how to apply a color wash to images within .NET, complimenting what's possible with CSS.

Introduction

Ever had a web site that needs branding for individual customers but found the presence of image elements complicates the process as you can't adjust their color from a simple style sheet? Wouldn't it be nice to use a base set of grayscale images and automatically apply a color wash to them to bring them in line with the customer's color scheme without fiddling with a graphics package?

Background

There's a web application I'm involved in the ongoing development of which makes use of a style sheet in order to allow the look and feel of the application to be customized to match each individual customer's corporate look and feel. This works fine for most elements of the site; however we use a number of duotone brand images in the application, and to make sure these match the color scheme, can be a bit more tedious than the simple editing of the CSS file. As the .NET Framework puts a lot more power in the hands of the developer, I decided to automate the process of applying color washes to these images to streamline the customer customization process.

Fig. 1 - Color wash in action.

Though I've used this functionality in a web application and on grayscale images, the library is not limited to web development or grayscale images.

The method

Not coming from a graphics based background, my method may not be the best solution, but it seems to work just fine.

Theory

To apply a color wash, a base RGB color value is decided upon as a reference point and an adjustment value calculated for the color wash's RGB value to be applied. This adjustment is calculated for each of the red, green and blue components. Once this is done, each of the image pixel's red, green and blue components are adjusted by the calculated amount to produce the color wash.

Each of the RGB components of a color can be represented by values from 0 to 255, so taking the mid point of 127 as the base reference point and taking the desired RGB component values of the color wash to apply, we can calculate the adjustments to be made to each pixel.

Adjusting red, green and blue component values can sometimes result in a value outside the allowed range of 0 to 255; in these cases, we compromise and simply use the minimum or maximum value allowed as appropriate.

The table below illustrates how values are adjusted:

Hex value Red Green Blue Color
Base Color 7F7F7F 127 127 127
Color wash to apply 1280AA 18 128 170
Adjustment value to use 18 - 127 = -109 128 - 127 = 1 170 - 127 = 43
Example Pixel 1 original color A5A5A5 165 165 165
Example Pixel 1 washed color 38A6D0 165 - 109 = 56 165 + 1 = 166 165 + 43 = 208
Example Pixel 2 original color 545454 84 84 84
Example Pixel 2 washed color 00557F 84 - 109 = -25 = 0 * 84 + 1 = 85 84 + 43 = 127
Example Pixel 3 original color 2E2E2E 46 46 46
Example Pixel 3 washed color 002F59 46 - 109 = -63 = 0 * 46 + 1 = 47 46 + 43 = 89

* The value is less than the minimum allowed so we use the minimum instead.

Implementation

The implementation of this technique is in fact very simple:

  1. Load the image to be color washed.
  2. Clone the image using a format that won't result in an indexed palette being present (to prevent errors occurring when we start manipulating pixel colors).
  3. Calculate the adjustments that need to be made to each pixel's red, green and blue components.

    Note that Red, Green and Blue are from the color wash that we are applying:

    'Approx non-fraction mid point between 0 and 255
    
    Const cintAdjustAgainstBase As Integer = 127
    
    ...
    
    'Calculate base adjustment values
    
    Dim intAdjustR As Integer = Red - cintAdjustAgainstBase
    Dim intAdjustG As Integer = Green - cintAdjustAgainstBase
    Dim intAdjustB As Integer = Blue - cintAdjustAgainstBase
  4. Loop through every pixel within the image adjusting red, green and blue components to produce the desired color wash.
    'Adjust RGB values for every pixel 
    
    Dim iX As Integer
    Dim iY As Integer
    iX = 0
    Do While iX <= bmp.Width - 1
        iY = 0
        Do While iY <= bmp.Height - 1
            Dim c As System.Drawing.Color = bmp.GetPixel(iX, iY)
            bmp.SetPixel(iX, iY, c.FromArgb(c.A,_
                 AdjustRGBValue(c.R, intAdjustR),_
                 AdjustRGBValue(c.G, intAdjustG),_
                 AdjustRGBValue(c.B, intAdjustB)))
            iY += 1
        Loop
        iX += 1
    Loop

    Below is the function used to calculate the new red, green and blue components to use and ensure that the values stay within the acceptable limits:

    Private Shared Function AdjustRGBValue(ByVal Value As Integer, _
                ByVal Adjust As Integer) As Integer
        Dim intReturn As Integer = Value + Adjust
        If intReturn > 255 Then intReturn = 255
        If intReturn < 0 Then intReturn = 0
        Return intReturn
    End Function
  5. Save the resulting color washed image.

Using the code

To use this functionality in your projects, simply download the source files accompanying this article, drop GavDev.Image.dll into the bin directory of your project, then make a reference to the class library GavDev.Image.dll. There are two interfaces provided for your convenience:

The first interface accepts individual red, green and blue values:

ColorWashImage(ByVal FilePathOriginal As String,_
    ByVal FilePathNew As String,_
    ByVal Red As Integer,_
    ByVal Green As Integer,_
    ByVal Blue As Integer,_
    Optional ByVal Quality As Integer = 100,_
    Optional ByVal Encoding As String = "image/jpeg")

The second interface allows you to pass a single combined 6 digit hexadecimal RGB value in as the color wash value:

ColorWashImage(ByVal FilePathOriginal As String,_
    ByVal FilePathNew As String,_
    ByVal HexRGBValue As String,_
    Optional ByVal Quality As Integer = 100,_
    Optional ByVal Encoding As String = "image/jpeg")

Both interfaces expect parameters FilePathOriginal, the physical location of the original grayscale image, and FilePathNew, the physical location to save the resulting color washed image. Optional parameters are also present to dictate the quality and encoding of the output images.

Below is an example of the class being used as it is in the accompanying demo project:

GavDev.Image.Manipulate.ColorWashImage(_
    Server.MapPath("Images/Gav.jpg"),_
    Server.MapPath("Images/Gav." & txtColorWash.Text & ".jpg"),_
    txtColorWash.Text)

Fig. 2 - Demo project screenshot.

A better application of this functionality in your projects maybe to set the RGB value to use for a customer in Web.config and apply the color wash if the color washed files have not previously been generated (e.g. it's the first time the customer has run the web application). Obviously it's better to only create color washed images once rather than creating them every single time the web application is started.

Here's what the Web.config entry might look like:

<!--
Style sheet for customer�s look and feel.
-->
<add key="StyleSheet" value="GavDev.css"/>

<!--
The hex RGB colour value to apply to brand images.
-->
<add key="BrandImageRGB" value="447777"/>

And the code in Global.asax:

Sub Application_Start(ByVal sender As Object, _
                                ByVal e As EventArgs)
    Dim strImages As String() = _
      {"HelpDesk", "DeskBooking", "FacilityBooking",_
      "TopLeftCorner", "TopRightCorner", "BottomLeftCorner", _
      "BottomRightCorner"}
    Dim strHexRGB As String = _
        ConfigurationSettings.AppSettings("BrandImageRGB")
    Dim blnImageMissing As Boolean

    'Check all color washed images are present

    For Each str As String In strImages
        If Not System.IO.File.Exists(Server.MapPath("~") & _
               "\Images\" & str & "." & strHexRGB & ".jpg") Then
             blnImageMissing = True
             Exit For
        End If
    Next

    'Generate color washed images if they aren't present
    If blnImageMissing Then
        For Each str As String In strImageIdentifiers
          GavDev.Image.Manipulate.ColorWashImage(Server.MapPath("~") & _
             "\Images\" & str & ".jpg", Server.MapPath("~") & "\Images\" _
             & str & "." & strHexRGB & ".jpg", strHexRGB)
        Next
    End If

End Sub

Alternate usage scenario

Though this article and the demo code focuses on applying color washes to photo type images, another useful application is the coloration of minor images that are often used to provide presentation effects, e.g. applying rounded corners to boxes containing content on web sites. In such a scenario, you would make the dominant color of such minor images match the color we use as a base value when calculating the red, green and blue adjustment values. As mentioned previously, this is 127 for red, green and blue components (#7F7F7F in hexadecimal).

Observation

Though the method used is very effective, it would be more efficient to adjust the image's palette values rather than every individual pixel within the image. However I found no obvious method to achieve this.

Possible enhancement

Applying a color wash using the method described above doesn't always result in a satisfactory color washed image, sometimes the shade will be too light or two dark requiring some experimentation with the color wash value applied to achieve best results.

Instead of taking a mid point in the color range (#7F7F7F) to act as the reference point to apply the color wash to, I feel a better result would be achieved consistently if the whole source image was parsed at the beginning to determine what specific color is the most predominant in the image. The predominant color would then be used as the reference point to apply the unmodified color wash to and variations worked out from this image dependent reference point rather than the global midpoint (#7F7F7F).

Though I have not tried the theory out I do believe it would yield far more satisfactory results. When I have more time on my hands I may revisit my code and experiment to see if better results are achievable.

Bonus library functionality

Also present within the GavDev.Image.Manipulate class library, you'll find the ImposeUpperLimitOnDimensions function which is very useful for creating thumbnail images, though I won't cover it in detail here as there are already multiple articles out there detailing such functionality.

History

  • 04th Mar, 2005 - Original article and code.
  • 11th Mar, 2005 - Support added for images with indexed pixel formats.
  • 13th Mar, 2005 - Article updated to include highlights of code.
  • 13th Dec, 2005 - Possible enhancement suggested.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here