How to Calculate a Complementary Colour (Inc. RGB / HSL Conversion)


Related pages:

Colour Scheme Calculator

Hex / HSL to RGB / RGB to HSL Calculator

When I wrote the programs for the colour calculator, I had some difficulty finding all the information I needed for the formulae etc., so here it is.

If you Google around, you can see there's a popular belief that you can calculate a hex colour's complement by subtracting each hex pair from FF, but if you check the results of that on a colour wheel you can see that it doesn't work, or only works for certain colours.

Complementary colours are opposite one another on the colour wheel, so the easiest way to calculate them is to convert to a colour notation system that's based on that wheel, e.g., HSL. If you're already familiar with HSL, skip to the calculation section below.

About HSL


HSL expresses colours in terms of their Hue, Saturation and Lightness, giving a number for each of these three attributes of the colour.

The Hue is the colour's position on the colour wheel, expressed in degrees from 0° to 359°, representing the 360° of the wheel; 0° being red, 180° being red's opposite colour cyan, and so on.

Saturation is the intensity of the colour, how dull or bright it is. The lower the saturation, the duller (greyer) the colour looks. This is expressed as a percentage, 100% being full saturation, the brightest, and 0% being no saturation, grey.

Lightness is how light the colour is. Slightly different to saturation. The more white in the colour the higher its Lightness value, the more black, the lower its Lightness. So 100% Lightness turns the colour white, 0% Lightness turns the colour black, and the "pure" colour would be 50% Lightness.

It's easier to see the difference between Saturation and Lightness than to explain it. If you want to clarify, try viewing the Lightness and Saturation variations on the colour calculator page, choosing quite a bright colour as your starter colour.

So HSL notation looks like this, giving the Hue, Saturation and Lightness values in that order:

Red: 0° 100% 50%
Pale pink: 0° 100% 90%
Cyan: 180° 100% 50%

Calculation Steps


1. Convert your colour to HSL.

2. Change the Hue value to that of the Hue opposite (e.g., if your Hue is 50°, the opposite one will be at 230° on the wheel — 180° further around).

3. Leave the Saturation and Lightness values as they were.

4. Convert this new HSL value back to your original colour notation (RGB or whatever).

So now we just need formulae to convert everything to and from HSL, which the people at EasyRGB.com kindly give here, in "generic" code so you can adapt it to the language of your choice.

PHP Example


Here is my example, using PHP to calculate a complementary colour from a hex colour code. You can make the PHP more succinct and efficient in your own program, but this example breaks the process down into simple steps to make it easier to follow the theory.

Our formula to convert RGB to HSL takes three decimal fractions of 1 as its input. E.g., an RGB value of 255 255 255 would be input as 1 1 1, and the RGB value 153 051 255 would be input as 0.6, 0.2, 1.

So first of all, get the six hex digits into this input format:

    // $hexcode is the six digit hex colour code we want to convert

    $redhex  = substr($hexcode,0,2);
    $greenhex = substr($hexcode,2,2);
    $bluehex = substr($hexcode,4,2);

    // $var_r, $var_g and $var_b are the three decimal fractions to be input to our RGB-to-HSL conversion routine

    $var_r = (hexdec($redhex)) / 255;
    $var_g = (hexdec($greenhex)) / 255;
    $var_b = (hexdec($bluehex)) / 255;

Now plug these values into the rgb2hsl routine. Below is my PHP version of EasyRGB.com's generic code for that conversion:

    // Input is $var_r, $var_g and $var_b from above
    // Output is HSL equivalent as $h, $s and $l — these are again expressed as fractions of 1, like the input values

    $var_min = min($var_r,$var_g,$var_b);
    $var_max = max($var_r,$var_g,$var_b);
    $del_max = $var_max - $var_min;

    $l = ($var_max + $var_min) / 2;

    if ($del_max == 0)
    {
            $h = 0;
            $s = 0;
    }
    else
    {
            if ($l < 0.5)
            {
                    $s = $del_max / ($var_max + $var_min);
            }
            else
            {
                    $s = $del_max / (2 - $var_max - $var_min);
            };

            $del_r = ((($var_max - $var_r) / 6) + ($del_max / 2)) / $del_max;
            $del_g = ((($var_max - $var_g) / 6) + ($del_max / 2)) / $del_max;
            $del_b = ((($var_max - $var_b) / 6) + ($del_max / 2)) / $del_max;

            if ($var_r == $var_max)
            {
                    $h = $del_b - $del_g;
            }
            elseif ($var_g == $var_max)
            {
                    $h = (1 / 3) + $del_r - $del_b;
            }
            elseif ($var_b == $var_max)
            {
                    $h = (2 / 3) + $del_g - $del_r;
            };

            if ($h < 0)
            {
                    $h += 1;
            };

            if ($h > 1)
            {
                    $h -= 1;
            };
    };

So now we have the colour as an HSL value, in the variables $h, $s and $l. These three output variables are again held as fractions of 1 at this stage, rather than as degrees and percentages. So e.g., cyan (180° 100% 50%) would come out as $h = 0.5, $s = 1, and $l =  0.5.

Next find the value of the opposite Hue, i.e., the one that's 180°, or 0.5, away (I'm sure the mathematicians have a more elegant way of doing this, but):

                // Calculate the opposite hue, $h2

                $h2 = $h + 0.5;

                if ($h2 > 1)
                        {
                                $h2 -= 1;
                        };

The HSL value of the complementary colour is now in $h2, $s, $l. So we're ready to convert this back to RGB (again, my PHP version of the EasyRGB.com formula). Note the input and output formats are different this time, see my comments at the top of the code:

       // Input is HSL value of complementary colour, held in $h2, $s, $l as fractions of 1
       // Output is RGB in normal 255 255 255 format, held in $r, $g, $b
       // Hue is converted using function hue_2_rgb, shown at the end of this code

        if ($s == 0)
        {
                $r = $l * 255;
                $g = $l * 255;
                $b = $l * 255;
        }
        else
        {
                if ($l < 0.5)
                {
                        $var_2 = $l * (1 + $s);
                }
                else
                {
                        $var_2 = ($l + $s) - ($s * $l);
                };

                $var_1 = 2 * $l - $var_2;
                $r = 255 * hue_2_rgb($var_1,$var_2,$h2 + (1 / 3));
                $g = 255 * hue_2_rgb($var_1,$var_2,$h2);
                $b = 255 * hue_2_rgb($var_1,$var_2,$h2 - (1 / 3));
        };

       // Function to convert hue to RGB, called from above

        function hue_2_rgb($v1,$v2,$vh)
        {
                if ($vh < 0)
                {
                        $vh += 1;
                };

                if ($vh > 1)
                {
                        $vh -= 1;
                };

                if ((6 * $vh) < 1)
                {
                        return ($v1 + ($v2 - $v1) * 6 * $vh);
                };

                if ((2 * $vh) < 1)
                {
                        return ($v2);
                };

                if ((3 * $vh) < 2)
                {
                        return ($v1 + ($v2 - $v1) * ((2 / 3 - $vh) * 6));
                };

                return ($v1);
        };

And after that routine, we finally have $r, $g and $b in 255 255 255 (RGB) format, which we can convert to six digits of hex:

        $rhex = sprintf("%02X",round($r));
        $ghex = sprintf("%02X",round($g));
        $bhex = sprintf("%02X",round($b));

        $rgbhex = $rhex.$ghex.$bhex;

$rgbhex is our answer — the complementary colour in hex.



'; http://serennu.com