Calculating Color Contrast

26 Comments

Comments are ordered by helpfulness, as indicated by you. Help us pick out the gems and discourage asshattery by voting on notable comments.

Got something to add? You can leave a comment below.

  1. Eric Wieser

    Don’t be silly, this doesn’t work:

    function getContrast50(hexcolor){ return (parseInt(hexcolor, 16) > 0xffffff/2) ? ‘black’:‘white’; }

    That’ll say a background color of #010000 should have black text, and a background color of #7FFFFF should have
    white text. That’s ludicrous!

  2. Gary

    This is awesome! Thanks Brian. I’ll definitely be playing with the PHP/Javascript in order to better select when to use black or white text.

  3. Rory Parle

    The second method looks very useful, but I have reservations about the 50% method. Because the red portion of the color is the most significant two digits when you treat it as a number, that is effectively the only component that has any impact on the decision. It’s equivalent to using the other method but weighting red by a factor of 256 relative to green and 65,000 relative to blue.

  4. Eric Wieser

    Ok, it looks like I made a mistake reading that. #010000 does give the sensible choice of white.

    However, I still feel that treating the entire color as one number is inherently wrong. Provided I keep my red component below 0×80, the text color will ALWAYS be white. That seems like a bad choice.

    What I assume the author meant to do was to take the average of the components, and see if they were more than 50%. So:

    return (r + g + b) / 3 > 0.5 ? ‘black’ : ‘white’;

  5. Drew McLellan

    Eric – that sounds like a sensible way to improve that algorithm. That doesn’t make the original ludicrous, as you put it. It’s basic and has room for improvement – the article already explores that.

  6. Brian LePore

    This is brilliant.

    As a reminder to all (though I doubt 24ways readers need this reminder), always explicitly define your background color and don’t assume everyone uses white as a default. I can’t tell you the number of Company’s that I’ve seen this from. Sites like ebay, chase, Google, etc. are usually good about their home pages and certain other pages, but there are many other pages where this is forgotten about. Techniques like this in the article would fall apart if the user didn’t have the white background you assumed they’d just have.

  7. Kemal Delali?

    Great article Brian!

    I needed the knowledge from this article few years ago as there was a lot to explore and learn about colours before wrapping up the yiq technique.

    @Drew thanks for the demo page

  8. Robin Sonefors

    As someone who’s primarily a programmer, and not really a designer, I, like Eric, at first considered the first algorithm revolting. It is indeed flawed, and figuring out which colors would be pathological to the algorithm is not a particularly challenging endeavor.

    However, I realized it will probably work alright, and in case there are more readers like myself and Eric, I will provide an explanation that at least I find more satisfying than the one given in the article:

    Most video formats are still encoded in black and white, with added color channels. While this was originally done for backwards compatibility reasons, this also turns out to compress really well, since it turns out that the red, green and blue channels are very often quite similar: by separating the channels in a YIQ or a YUV picture, the luma channel will likely be the only one with a lot of information. By separating the channels in a RGB picture, all of the channels will appear to contain pretty much the same shapes. This must mean that most things are “somewhat greyish”: the channels in RGB are rarely that far apart.

    Exploiting this, as the 50% method appears to do, by assuming that the green and blue channel will be similar to the red will thus often give you somewhat sane results.

    This assumption does break sometimes, particularly for highly saturated colors such as #ff0000, but for those, either black or white text works.

    Sure, #7fffff and #800000 will give you really bad results, but other than those, it should work alright.

  9. Brian Suda

    J. RYAN STINNETT, you are right. It seems to be a typo that made it pretty far. I have updated the text so the function is 114 not 144. Thanks for that catch, with enough eyeballs all bugs are shallow.

  10. Michael Kaply

    Brian,

    What is your opinion of using luminance for this computation?

    let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    if luminance < 110 light, else dark (might be backwards)

    This is how Firefox determines whether a given textcolor in a Persona should have a light or dark text shadow.

  11. Mattijs Bliek

    This could come in handy sometime, nice article.

    I do however disagree with the ‘…neither of which is unreadable.’ remark regarding choosing between the 50% or YIQ method.

    As Drew’s test page clearly shows, white text on—for instance—#00FFFF hurts your eyes and isn’t readable at all. The YIQ method seems like the way to go in my opinion, it produces far more readable results in all the tests appearing in this article.

  12. Brian Suda

    Mike Kaply, there are a few equations for contrast with luminance, this is the W3C definition, which is very similar

    http://www.w3.org/TR/2006/WD-WCAG20-20060427/appendixA.html#luminosity-contrastdef

    They also have a more complex equation which I wanted to avoid in this quick article.

    http://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/G145.html

    Testing the contrast by luminance is yet another tool to have in your tool box. I don’t think it really matters which equation you choose, there will always be issues with edge-cases, it is more important that you are using something.

  13. Alan Hogan

    The 50% algorithm is incorrect and, as other commentors note, basically only considers red.

    The test colors are flawed, too; they are all completely saturated (or exactly grey). This means that even an incredibly naïve algorithm like the 50% one will always have contrast simply because both white and black always contrast against saturated colors — and because when r = g = b (grey) then it doesn’t matter if red is over-represented.

    I really feel the article needs updated to reflect this. It’s that big a deal, and I think 24 Ways commands enough respect that readers expect/deserve a correction.

  14. Matt L.

    Very helpful! Well done.

    Wanted to share that the parameters in the substring methods inside the getContrastYIQ function are incorrect.

    The indexes should be (0,2) (2,4) (4,6) not (0,2) (2,2) (4,2)

Impress us

Be friendly / use Textile