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:
- Load the image to be color washed.
- 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).
- 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:
Const cintAdjustAgainstBase As Integer = 127
...
Dim intAdjustR As Integer = Red - cintAdjustAgainstBase
Dim intAdjustG As Integer = Green - cintAdjustAgainstBase
Dim intAdjustB As Integer = Blue - cintAdjustAgainstBase
- Loop through every pixel within the image adjusting red, green and blue components to produce the desired color wash.
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
- 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:
<!---->
<add key="StyleSheet" value="GavDev.css"/>
<!---->
<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
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.