In my last entry, I provided some code to convert from the HSB color space to the RGB color space.

Note that we are actually referring to the HSL color space, not the HSB color space. I am using the name HSB because this is the convention used by the .NET Framework and its supporting documentation.

Now, the time has come to make this code come to life, rather than being interesting only to those who are interested in math or color spaces!

To explore some of the possibilities of using the HSL color space (which maps more intuitively to the human perception of color), I created a class deriving from Panel, and used the background color to generate 4 different colors with the same hue, but different values for saturation and brightness (luminance), which I then used to creating an interesting looking gradient.

Custom Gradient Panel Based On a Single Color's Hue

This highlights some of the interesting possibilities of mapping color to something that humans feel more comfortable manipulating. Starting with a single color, I can now generate a gradient, and I can generate it for all possible colors. This might be useful if we wanted to create an interesting looking toolbar (shown at the top), or interesting looking headings (shown in the body) without having to specify specific colors for each endpoint of the gradient.

The top panel (the one emulating a toolbar) is based on a System Color - Active Caption. Using this technique, we are now generating a 4 color gradient based on the active caption color - in this case blue. If I switch the color scheme to the olive green one, now my color is based on olive green. Handy! However, we'll come back to this one in a second. The header panel I have labelled Group 1 is based on the color Red. The header panel I have labeled Group 2 is based on the system color Control.

Wait a minute ... isn't the Control system color grey, and not in any way electric purple? That is one thing to watch out for if you attempt to base the colors on external colors that are outside of your control. Note that we are modifying both saturation and brightness (luminance) to come up with our gradient. In the HSL color space, there is no hue for grey at all - grey is simply a color (any color you want) with a saturation of 0. Hues of 0 through 360 with a saturation of 0 and a brightness (luminance) indicating the shade of grey are all exactly identical. So, when we take a shade of grey, we can never be entirely sure which specific hue will have been used, since it does not matter in the least. As such, it is probably best to avoid using this technique and tying it to a color over which you have no control, because shades of grey break the model in this color space. You will notice the same thing in the panel I am using as a toolbar if you change your system colors to the silver color scheme: instead of a grey gradient, you end up with a red gradient!

Of course, we could take this into account and more intelligently modify saturation and lightness, rather than just hard coding explicit values as we have done here. This is left as an exercise for the reader.

With that said, let's take a look at the code:

class FourColorPanel : Panel {

    private Color topTopColor;
    private Color topBottomColor;
    private Color bottomTopColor;
    private Color bottomBottomColor;

    public FourColorPanel() : base() {
        SetBackgroundColors(BackColor.GetHue());
    }

    public override System.Drawing.Color BackColor {
        get {
            return base.BackColor;
        }
        set {
            base.BackColor = value;
            SetBackgroundColors(value.GetHue());
        }
    }

    private void SetBackgroundColors(float baseHue) {
        topTopColor = ColorConversions.ColorFromAhsb(255, baseHue,
            0.2958f, 0.7292f);
        topBottomColor = ColorConversions.ColorFromAhsb(255, baseHue,
            0.5875f, 0.35f);
        bottomTopColor = ColorConversions.ColorFromAhsb(255, baseHue,
            0.7458f, 0.2f);
        bottomBottomColor = ColorConversions.ColorFromAhsb(255, baseHue,
            0.6f, 0.4042f);
    }

    protected override void OnPaint(PaintEventArgs e) {
        Rectangle topRect = new Rectangle(0, 0, ClientRectangle.Width,
            ClientRectangle.Height / 2);
        Rectangle bottomRect = new Rectangle(0, topRect.Height,
            ClientRectangle.Width,
            ClientRectangle.Height - topRect.Height - 1);
        using (Brush topBrush = new LinearGradientBrush(topRect,
            topTopColor, topBottomColor, LinearGradientMode.Vertical)) {
            e.Graphics.FillRectangle(topBrush, topRect);
        }
        using (Brush bottomBrush = new LinearGradientBrush(bottomRect,
            bottomTopColor, bottomBottomColor,
            LinearGradientMode.Vertical)) {
            e.Graphics.FillRectangle(bottomBrush, bottomRect);
        }
    }

}

Hopefully, this technique can open up new doors for creating interesting color combinations based on a single hue, rather than requiring that every single color be explicitly specified.