Color models

If someone knows only one color model, it's probably RGB. It's a great color space for computers to represent colors, but not intuitive or useful for humans. It's especially not useful when objectively determining whether a color is dark or light.

Annotated screenshot of Hue Tab showing the differing luma of colors with the same lightness.
Hue Tab showing how triadic HSL colors with the same lightness have different luma.

The screenshot above shows my Chrome extension Hue Tab. I had to use a luminance formula (different from RGB or HSL) to decide whether to use black or white text on the color sample.

HSL (Hue-Saturation-Lightness)

HSL is a more intuitive color model than RGB because it describes colors by their Hue (red, yellow, green, blue, purple or anywhere in between), Saturation (color intensity), and Lightness (how bright a color is).

In HSL, lightness is calculated as the average of max(r, g, b) and min(r, g, b).
In HSB, brightness is calculated as max(r, g, b)

The problem is that neither Lightness nor Brightness are a perfect representation of our sensitivity of luminance. Human eyes aren't as sensitive to colors in the purple or red regions as the greens and yellows. This is why blues seem darker than greens, even if they have the same mathematical L or B value.

Graph showing the relative brightness of various colors to the human eye.
Graph showing the relative brightness of various colors to the human eye.
By Skatebiker, vector by Adam Rędzikowski [CC BY-SA 3.0], via Wikimedia Commons

Luma coefficients

To approximate human perception of lightness, we need to use a weighted arithmetic mean. This table shows the percent at which each color is weighted at for the two most common luma coefficient standards.

Weighted MethodRGB
Rec. 601 (CCIR 601)29.9%58.7%11.4%
Rec. 70921.26%71.52%7.22%

Currently, the W3C recommends CCIR 601. Rec. 709 is much more commonly used in video editing.

Rec. 601 luminance function

This JavaScript function finds the luminance of a hex color using Rec. 601 coefficients.

I made the cutoff for a color to be dark at < 55% luminance because personally, I think white on a light color looks better than black on a dark color.

// uses CCIR 601 luma coefficients
// input in #RRGGBB
function isDark(hex) {
    // convert hexadecimal color into decimal
    var decimal = parseInt(hex.substring(1), 16);

    // extract red, green, and blue components separately
    var r = (decimal >> 16) & 255;
    var g = (decimal >> 8) & 255;
    var b = decimal & 255;

    // calculate CCIR 601 luma
    var lightness = (0.299*r + 0.587*g + 0.114*b) / 255;
    return (lightness < 0.55);
}

Demo

Here's a demo of the isDark function. Choose from the color picker, and it will tell you the luminance and change the text to either black or white to ensure maximum contrast.

Enable JavaScript for this demo to work.