BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Graphics
.NET 1.1+

Converting Between RGB and HSL Colour Models

The RGB colour model is ideal for producing images on display devices but counter-intuitive for people who wish to adjust colours. The HSL model is intuitive but not fully supported by .NET. This article explains how to convert between the two models.

Colour Conversion

In this article we will create a set of classes that allow colours to be converted between the RGB and HSL colour models. The classes will work with the standard Color structure, which is RGB-based. This will allow you to convert an RGB colour that you can use with many .NET framework classes to HSL, make a modification to any or all of the three HSL components and convert back to a Color value that can be applied to a control.

As we will be working with the Color structure, ensure that your project includes a reference to the System.Drawing assembly and that all of the classes that refer to Color variables include the following using directive:

using System.Drawing;

NB: The Color structure does include three methods that obtain the hue, saturation and lightness values. These are named GetHue, GetSaturation and GetBrightness. The naming of GetBrightness is unfortunate as is implies that these methods return values for the HSV colour scheme, sometimes known as HSB, which operates in a subtly different manner to HSL. We will still implement the conversion from RGB to HSL to allow for greater accuracy, fewer rounding errors and to allow understanding of the algorithm.

Creating the Colour Model Classes

Whilst performing the conversion between RGB and HSL, we will hold both types of colour in new classes. Even though the Color structure permits individual access to the three colour component values, it is useful to work with ranges of zero to one, rather than zero to two hundred and fifty-five. To create the RGB class, add the following code, noting the method that allows conversion from RGB to Color.

public class RGB
{
    public decimal R { get; set; }
    public decimal G { get; set; }
    public decimal B { get; set; }

    public Color ToColor()
    {
        int r = (int)Math.Round(R * 255M);
        int g = (int)Math.Round(G * 255M);
        int b = (int)Math.Round(B * 255M);
        return Color.FromArgb(255, r, g, b);
    }
}

The HSL class is similar except that no conversion method is required:

public class HSL
{
    public decimal H { get; set; }
    public decimal S { get; set; }
    public decimal L { get; set; }
}

Converting from System.Color to HSL

The first conversion we will implement is from RGB to HSL. We will create the algorithm in stages with a brief explanation of the process for each step. The algorithm is explained in more detail in the HSL and HSV article at wikipedia.com. To begin, create new class named, "ColorToHSLConverter".

We can now create the signature for the Convert method, which accepts a Color value using its sole parameter. We can also create a new HSL object to populate by adding the following code to the class:

public HSL Convert(Color color)
{
    HSL hsl = new HSL();
}

As the algorithm works with RGB values in the range of zero to one, we must first convert the Color value into an instance of the RGB class that we created earlier. The GetRGBFromColor method achieves this by simply dividing each component value by 255.

private RGB GetRGBFromColor(Color color)
{
    RGB rgb = new RGB();
    rgb.R = color.R / 255M;
    rgb.G = color.G / 255M;
    rgb.B = color.B / 255M;
    return rgb;
}

To perform the Color to RGB conversion, add the following to the main Convert method.

RGB rgb = GetRGBFromColor(color);

The conversion from RGB to HSL uses three special values repeatedly. Two of these are the maximum and minimum values of the red, green and blue components. The third is the chroma value, which is the difference between the maximum and minimum brightnesses. To calculate these values, add the following code to the main conversion method:

decimal max = Math.Max(Math.Max(rgb.R, rgb.G), rgb.B);
decimal min = Math.Min(Math.Min(rgb.R, rgb.G), rgb.B);
decimal chroma = max - min;

We now have enough information to calculate the lightness value for the HSL colour. Using the bi-hexcone method described in the Wikipedia article, the lightness is simply the mean average of the maximum and minimum RGB values. We'll perform the calculation in a private method, as follows:

private decimal GetL(decimal max, decimal min)
{
    return (max + min) / 2M;
}

We can apply the result of this method directly to the L property of the HSL variable by adding the following to the Convert method:

hsl.L = GetL(max, min);

After calculating the lightness we need to check for a special case. If all three of the RGB values are identical, the colour produced is grey, so has a saturation of zero and an undefined hue. If the RGB values do match, the maximum and minimum values will be the same and, therefore, the chroma value will be zero. We can check for this situation and simply return the HSL value with default values of zero for hue and saturation using the following code:

if (chroma != 0)
{
}
return hsl;

If the chroma value is not zero we need to determine the saturation and hue values from the information that we already have. We will create a method to obtain each, so to call those methods add the following two lines within the code block of the previously defined if statement:

hsl.H = GetH(rgb, max, chroma);
hsl.S = GetS(hsl.L, chroma);

Next we will create the method that generates the hue value. This performs one of three calculations, selected according to which of the RGB elements has the highest value. The other two components of the RGB colour are then used to move within the HSL "cylinder" and find the correct hue, which is returned as a value between zero and 360.

private decimal GetH(RGB rgb, decimal max, decimal chroma)
{
    decimal h;
    if (rgb.R == max)
        h = ((rgb.G - rgb.B) / chroma);
    else if (rgb.G == max)
        h = ((rgb.B - rgb.R) / chroma) + 2M;
    else
        h = ((rgb.R - rgb.G) / chroma) + 4M;
    return 60M * ((h + 6M) % 6M);
}

Finally, we can calculate the saturation. One of two calculations is used depending upon whether the lightness is greater than 50% or not. Using the conditional operator allows these calculations to be performed with a single line of code:

private decimal GetS(decimal l, decimal chroma)
{
    return l <= 0.5M ? chroma / (l * 2M) : chroma / (2M - 2M * l);
}

The conversion from RGB to HSL is now complete.

17 November 2010