Code: Determine the contrast of a colour

Useful when you need to place text over an image or a solid block of colour and still need the text readable.

Posted by Malcolm Hollingsworth on

You will often have times where you need to place text over an image and make sure it has the best chance of being readable.

There are many ways to achieve this; common ways include:

  • Layering the text over a solid bar which is then placed over the image.
  • Display the text in a contrasting colour to the image underneath it.
  • Using a "scrim", essentially a faint gradient that is layered between the image and the text that bleeds out to transparent some distance from where the text stops.

We are going to focus on the second method. This is best suited where the image below the position you will be placing your text; has a uniformatity to the colour palette. If your text is being placed over a solid or near solid block of colour then this is usually the method to use.

The image used at the top of this article is a good example of this problem. Despite the use of many greens behind the text - all the text is still readable. However if you see the same image but the text shown in black you will see how much harder it is to read the text.

The most important requirement when using the contrast state method is knowing the colour of the image it will be layered over. If the image is more complicated then you use the average colour of the area the text will be layered on top of.

This is a simple JavaScript function that will return the contrast state when provided with a 6 digit hexadecimal colour.

function getContrastState(color) {
    var r = parseInt(color.substr(0,2),16);
    var g = parseInt(color.substr(2,2),16);
    var b = parseInt(color.substr(4,2),16);
    var yiq = (r*0.299)+(g*0.587)+(b*0.114);
    return (yiq >= 128) ? 1 : 0;
}
// based on the colour you provided it; it will
// return 0 for colour = dark contrast
// return 1 for colour = light contrast

If you are well versed in JavaScript (or many similar languages) then the basic logic here is obvious. But for other people; then first three var lines are converting the red, green and blue hex values into base 10 values.

The yiq variable is set using the combination of red, green and blue weighted ratios. The reason each colour has different weighted values is how each colour appears to the human eye - not all colours are perceived equally.

You may wish to learn more about converting colours from red, green and blue into greyscale, although this is not required to understand the code presented in this article. Wikipedia › Grayscale - Luma Coding in Video Systems.

To use the function you only have to provide the colour using the standard six character hexadecimal representation.

Usage Example

getContrastState("000000"); // returns 0
getContrastState("c60000"); // returns 0
getContrastState("ffffff"); // returns 1

Once you have the answer of zero or one for the source colour, you can choose to set your text colour appropriately. Commonly a source colour resulting in zero would have the text set to white and set to black when the result is one.

I said commonly; as often designers will prefer not to define black or white as a their solid variants, instead choosing close values such as #444 for black and #f0f0f0 for white. If the designer has redefined black or white, choose their versions. This way it becomes their job to determine if this use-case requires a new design rule.

A future post will discuss the scrim method; this is better suited to images that have a variety of colours in the area where the text will be overlaid.