WPF sometimes requires us to convert one shade of a color to another. For example, we may want a button border to be a darker shade of the button background. In cases where the color value is bound to another property, one would typically calculate the darker shade as a mathematical percentage of the base color. For example, we might calculate the border color as 85% of the background color. I recently needed to perform this chore, and I was surprised to find very little on how to do the conversion without resorting to using the
System.Drawing is a GDI namespace, and I have a very strong preference for keeping it out of my WPF applications. To me, mixing GDI and WPF is a code smell, which should be avoided in all but exceptional circumstances. So, after a bit of research, I determined how to perform the conversion in pure-WPF.
This article explains how to perform the actual color conversion. Normally, the methods shown in the attached demos would be included in an
IValueConverter object that is included in the data binding of the color property. I don't cover the
IValueConverter aspect of the process in this article, since there are a number of good articles on how to create an
IValueConverter class. Instead, I focus on the conversion methods themselves. However, I have included a sample
IValueConverter class for each set of conversions.
WPF works with the
System.Windows.Media namespace, which includes a
Color object built around the familiar RGB color model. Actually, the WPF
Color object uses an ARGB color model, as it includes an alpha channel to control transparency. The RGB color model works very well for most purposes, but it isn't well-suited to the task of calculating different shades of a color. For that task, we can use one of two color models:
- HSB (Hue-Saturation-Brightness), also known as HSV (Hue-Saturation-Value); or
- HLS (Hue- Luminance-Saturation).
Many developers assume the two models are the same, but that's not the case. To see the difference, create a new WPF application in Expression Blend and set the window background to this solid color: #FFA7BCD7. Now, change the Blend color picker for the window background from RGB to HLS (by right-clicking on one of the R-G-B-A letters in the color picker); then, drag the L slider to reduce the Luminance value to zero. As you do so, you will see the color-space cursor jog to the right, and then move straight down:
Now, reset the color value to #FFA7BCD7, and change the color model from HLS to HSB. Then, drag the B slider to reduce the brightness to zero. This time, the color space cursor will move straight down, without the jog to the right:
As you can see, there are differences in the colors that result in some cases where B (in the HSB model) and L (in the HLS model) are set to the same value.
Which is the Correct Model?
Either color model will give good results; it really boils down to personal preference. I like the HLS model for doing percentage color adjustments, because I feel that the HSB model tends to de-saturate colors and move too quickly in the direction of dark gray. The jog to the right that the HLS model takes seems to me to produce richer shades. But naturally, there are those who disagree. If you are one of those folks, please feel free to leave a comment.
There are two demos attached to this article:
The demos are very simple console apps that perform RGB conversions to and from HLS and HSB color equivalents. Each demo contains the necessary color conversion methods and methods to construct WPF
Color objects from hex values.
Note that you will not need the hex conversion methods in an
IValueConverter, since WPF contains a built-in value converter that will recognize the hex passed in from XAML as a
SolidColorBrush. So, in a value converter, here is how you get an RGB
Color object from the value passed in to the value converter:
var brush = (SolidColorBrush) value;
var rgbColorIn = brush.Color;
You can see the complete code in the sample
IValueConverter class included with each demo.
I don't claim to be a color theorist, and unfortunately, I can't provide a line-by-line explanation of why the methods work. I have cited my sources for the algorithms, and the demos output sufficient information to allow you to verify the methods against the conversions performed by Blend. Most importantly, the demos verify that the conversion back to RGB from either HLS or HSB results in the original RGB value converted to the alternate color model.
As always, one of the reasons for posting this article is to solicit peer review from the CodeProject community. If you know of better algorithms for performing either set of conversions, your comments are welcome. Similarly, if you can flesh out the 'why this works' for either set of algorithms, your insights would be greatly appreciated. For the rest of us, hopefully, the demos will provide the C# code needed to perform a color adjustment the next time the need for one arises.