How to find out if a hexadecimal color is dark or light?

Asked

Viewed 3,408 times

45

I have a status listing that is displayed and each status has an assigned color. These colors are saved in my database in hexadecimal format.

Example:

#add555

I have a small problem: If the color is too dark, because it is used to background, the color of the text becomes unreadable.

So I was wondering if there’s any way to find out if the color is dark, so, when it’s dark, I determine that the color of the text will be white, and if it’s light, black.

You can do it?

Note: The solution can be both in Javascript and PHP.

  • 3

    tbm have this problem, so what I did was let the user choose the background color and color of the text, but yes, if he choose a dark color for the text and the background is unreadable, but the users have already been notified of it, so it was like, if it happens is their fault, I did what I did in the moment.

3 answers

55


Calculating luminosity

The solution comes from W3 itself:

https://www.w3.org/TR/AERT#color-Contrast

More specifically with this formula:

( R * 299 + G * 587 + B * 114) / 1000

It returns the luminosity in the range of 0 to 255.

As the human being has on average a different perception of each color, the formula compensates for this (blue needs to be much stronger for us to have the "feeling" that it shines as much as a certain red).

This is an average that doesn’t apply to color-blind people. Color-blind are people who just have problems with any of these color "channels", often failing to see partially or totally any of the three colors.


Treating the hexadecimal

A color in the format #add555 is nothing more than a hexadecimal combination of RGB values. In this case, we have these values:

R (red, vermelho ) 0xAD
G (green, verde )  0xD5
B (blue, azul )    0x55

As I mentioned in this other post:

How hexadecimal numbers work?

The hexa calculation is very simple. I won’t go into details, because both JS and PHP have their own functions for this, who want more details can read the post above.


Let’s go straight to the code examples:

PHP

  $hexa = '#add555';
  $r = hexdec(substr($hexa,1,2)); // Se for sem o #, mude para 0, 2
  $g = hexdec(substr($hexa,3,2)); // Se for sem o #, mude para 3, 2
  $b = hexdec(substr($hexa,5,2)); // Se for sem o #, mude para 5, 2
  $luminosidade = ( $r * 299 + $g * 587 + $b * 114) / 1000;

And to use the result (remembering that it goes from 0 to 255):

  if( $luminosidade > 128 ) {
     echo 'Cor clara';
  } else {
     echo 'Cor escura';
  }

See working on IDEONE.

JS

var hex = '#add555';
var r, g, b, lum;

hex = hex.replace('#', '');

r = parseInt(hex.substr(0, 2));
g = parseInt(hex.substr(2, 2));
b = parseInt(hex.substr(4, 2));

lum = (r * 299 + g * 587 + b * 114) / 1000;

See working on CODEPEN.


"Short" format and disregarding the #

Important to know which colors in "short" format, as #fc0 are simply a shorter way of writing #ffcc00.

To convert a short "format" color just that:

r = r_curto * 17
  • P: But where did this 17 come from?

  • A: In short format, 0xF amounts to 0xFF, 0x1 amounts to 0x11. In other words, a variation of 0 to 15 in short format is equivalent to an actual variation of 0 to 255.

    Simplifying: 255 / 15 = 17:)


In PHP can be used this way:

function Luminosidade($hexa) {
   $hexa = trim($hexa, ' #');
   $longo = strlen($hexa) > 3;

   $r = $longo ? hexdec(substr($hexa, 0, 2)) : hexdec(substr($hexa, 0, 1)) * 17;
   $g = $longo ? hexdec(substr($hexa, 3, 2)) : hexdec(substr($hexa, 1, 1)) * 17;
   $b = $longo ? hexdec(substr($hexa, 5, 2)) : hexdec(substr($hexa, 2, 1)) * 17;

   return ( $r * 299 + $g * 587 + $b * 114) / 1000;

And in JS:

function Luminosidade(hex) {
   var r, g, b, longo;
   hex = hex.replace( '#', '' );
   longo = hex.length > 3;

   r = longo ? parseInt(hex.substr(0, 2), 16) : parseInt(hex.substr(0, 1), 16) * 17;
   g = longo ? parseInt(hex.substr(2, 2), 16) : parseInt(hex.substr(1, 1), 16) * 17;
   b = longo ? parseInt(hex.substr(4, 2), 16) : parseInt(hex.substr(2, 1), 16) * 17;

   return ( r * 299 + g * 587 + b * 114) / 1000;
}


It goes beyond what was asked, but if you need a more "universal" code that recognizes colors in many formats, it has a parser well completed ready here:

http://www.phpied.com/rgb-color-parser-in-javascript/

  • 1

    Does it follow the same rule for colorblind?

  • 6

    @Acklay certainly not, because the color-blind has a limitation in some of the "channels". Since you mentioned, I will make an observation.

13

The library Tinycolor provides various functions to inspect and manipulate colors, including:

  • isLight

    Returns a Boolean indicating if the perceived brightness for the color is clear.

    tinycolor("#fff").isLight(); // true
    tinycolor("#000").isLight(); // false
    
  • isDark

    Returns a Boolean indicating if the perceived brightness for the color is dark.

    tinycolor("#fff").isDark(); // false
    tinycolor("#000").isDark(); // true
    

Extracted and translated from https://stackoverflow.com/a/32442062/5230740.

11

I told you that in this other answer of mine:

Color composition

It is important to keep in mind that although white is the result of the sum of red, green and blue, it does not mean that each of these three colors represents a third of white. This is not true, and can be easily perceived empirically by noting that pure green is bright, while pure red is matte and pure blue is dark.

In fact, the exact proportion of white light composition depends on the disposition of the different receptor cells in the retina of the observer’s eye, health conditions, weariness, age and stress of the observer, lighting conditions, brightness and contrast of the screen, the angle and direction between the screen plane and the observer’s line of sight, the screen type (reflective or reflective Anti-reflective, CRT, LED, plasma, LCD, overhead projector, Kindle, etc.), among many other variables, may even vary from one eye to another in the same person with normal and healthy vision.

But disregarding these variables that are out of the programmer’s control and assuming that the user has a healthy view and is using a good quality screen in an environment with adequate lighting, there’s a formula I saw in a book once a few years ago that gave the following ratio:

Branco = 0,290 * vermelho + 0,599 * verde + 0,111 * azul

It’s a shame I don’t remember the title, but the bfavaretto gave three references to it in the comments: 1, 2 and 3, although there are small variations in the exact factors.

Keeping these brightness composition factors in mind is important in case you want to make an algorithm for anti-aliasing considering that subpixels have different colors.

This same formula given above for the white color, can be used to measure the brightness of a certain color from its red, green and blue components. According to this page, the formula recommended by W3C (similar to the one above) is:

Brilho = 0,299 * vermelho + 0,587 * verde + 0,114 * azul

However, this same page says that this formula may still fail. For example, the color (240, 0, 30) is slightly brighter than (80, 80, 80), and by this formula of the W3C, the first would have a brightness of 75.18 while the second would have 80 (vermelho e cinza). The reason for this is that the brightness is actually the distance that a color has in relation to black, and not just the weighted sum of the values of its shades.

If we consider all colors arranged as different internal points in a parallelepiped where one of the vertices is black, the opposite vertex is white, the vertices adjacent to black are red, green and blue and the vertices opposite to these are cyan, magenta and yellow (in this order), one of the dimensions would correspond to the value of the red component, the other of the green component and the other of the blue component. If we define the size of each of the dimensions of this parallelepiped as the intensity of the corresponding color component, then we could use the Euclidean distance from the point occupied by any color within that parallelepiped to the vertex of the black color as a measure of brightness. Thus, to calculate the intensity of a color, just use the Pythagorean theorem. If we use the values of W3C, we would arrive at this formula:

Brilho = sqrt(0,299 * (vermelho)² + 0,587 * (verde)² + 0,114 * (azul)²)

In this formula, the brightness of the above colors would be 131,62 and 80.

So, a Javascript solution considering that a light color would be closer to white while a dark one is closer to black, would be this:

/**
 * Função que define se uma cor, dado os valores dos componentes vermelho, verde e azul em uma escala de 0 a 1, é clara ou escura.
 * @param {int} r - Valor do componente vermelho em uma escala de 0 a 1.
 * @param {int} g - Valor do componente verde em uma escala de 0 a 1.
 * @param {int} b - Valor do componente azul em uma escala de 0 a 1.
 */
function corClaraRgb(r, g, b) {
    return Math.sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114) >= 0.5;
}

// Mapeia nomes comuns de cores. Fonte: http://www.w3schools.com/cssref/css_colors.asp
var colorMap = {"ALICEBLUE": "#F0F8FF", "ANTIQUEWHITE": "#FAEBD7", "AQUA": "#00FFFF", "AQUAMARINE": "#7FFFD4", "AZURE": "#F0FFFF", "BEIGE": "#F5F5DC", "BISQUE": "#FFE4C4", "BLACK": "#000000", "BLANCHEDALMOND": "#FFEBCD", "BLUE": "#0000FF", "BLUEVIOLET": "#8A2BE2", "BROWN": "#A52A2A", "BURLYWOOD": "#DEB887", "CADETBLUE": "#5F9EA0", "CHARTREUSE": "#7FFF00", "CHOCOLATE": "#D2691E", "CORAL": "#FF7F50", "CORNFLOWERBLUE": "#6495ED", "CORNSILK": "#FFF8DC", "CRIMSON": "#DC143C", "CYAN": "#00FFFF", "DARKBLUE": "#00008B", "DARKCYAN": "#008B8B", "DARKGOLDENROD": "#B8860B", "DARKGRAY": "#A9A9A9", "DARKGREY": "#A9A9A9", "DARKGREEN": "#006400", "DARKKHAKI": "#BDB76B", "DARKMAGENTA": "#8B008B", "DARKOLIVEGREEN": "#556B2F", "DARKORANGE": "#FF8C00", "DARKORCHID": "#9932CC", "DARKRED": "#8B0000", "DARKSALMON": "#E9967A", "DARKSEAGREEN": "#8FBC8F", "DARKSLATEBLUE": "#483D8B", "DARKSLATEGRAY": "#2F4F4F", "DARKSLATEGREY": "#2F4F4F", "DARKTURQUOISE": "#00CED1", "DARKVIOLET": "#9400D3", "DEEPPINK": "#FF1493", "DEEPSKYBLUE": "#00BFFF", "DIMGRAY": "#696969", "DIMGREY": "#696969", "DODGERBLUE": "#1E90FF", "FIREBRICK": "#B22222", "FLORALWHITE": "#FFFAF0", "FORESTGREEN": "#228B22", "FUCHSIA": "#FF00FF", "GAINSBORO": "#DCDCDC", "GHOSTWHITE": "#F8F8FF", "GOLD": "#FFD700", "GOLDENROD": "#DAA520", "GRAY": "#808080", "GREY": "#808080", "GREEN": "#008000", "GREENYELLOW": "#ADFF2F", "HONEYDEW": "#F0FFF0", "HOTPINK": "#FF69B4", "INDIANRED ": "#CD5C5C", "INDIGO ": "#4B0082", "IVORY": "#FFFFF0", "KHAKI": "#F0E68C", "LAVENDER": "#E6E6FA", "LAVENDERBLUSH": "#FFF0F5", "LAWNGREEN": "#7CFC00", "LEMONCHIFFON": "#FFFACD", "LIGHTBLUE": "#ADD8E6", "LIGHTCORAL": "#F08080", "LIGHTCYAN": "#E0FFFF", "LIGHTGOLDENRODYELLOW": "#FAFAD2", "LIGHTGRAY": "#D3D3D3", "LIGHTGREY": "#D3D3D3", "LIGHTGREEN": "#90EE90", "LIGHTPINK": "#FFB6C1", "LIGHTSALMON": "#FFA07A", "LIGHTSEAGREEN": "#20B2AA", "LIGHTSKYBLUE": "#87CEFA", "LIGHTSLATEGRAY": "#778899", "LIGHTSLATEGREY": "#778899", "LIGHTSTEELBLUE": "#B0C4DE", "LIGHTYELLOW": "#FFFFE0", "LIME": "#00FF00", "LIMEGREEN": "#32CD32", "LINEN": "#FAF0E6", "MAGENTA": "#FF00FF", "MAROON": "#800000", "MEDIUMAQUAMARINE": "#66CDAA", "MEDIUMBLUE": "#0000CD", "MEDIUMORCHID": "#BA55D3", "MEDIUMPURPLE": "#9370DB", "MEDIUMSEAGREEN": "#3CB371", "MEDIUMSLATEBLUE": "#7B68EE", "MEDIUMSPRINGGREEN": "#00FA9A", "MEDIUMTURQUOISE": "#48D1CC", "MEDIUMVIOLETRED": "#C71585", "MIDNIGHTBLUE": "#191970", "MINTCREAM": "#F5FFFA", "MISTYROSE": "#FFE4E1", "MOCCASIN": "#FFE4B5", "NAVAJOWHITE": "#FFDEAD", "NAVY": "#000080", "OLDLACE": "#FDF5E6", "OLIVE": "#808000", "OLIVEDRAB": "#6B8E23", "ORANGE": "#FFA500", "ORANGERED": "#FF4500", "ORCHID": "#DA70D6", "PALEGOLDENROD": "#EEE8AA", "PALEGREEN": "#98FB98", "PALETURQUOISE": "#AFEEEE", "PALEVIOLETRED": "#DB7093", "PAPAYAWHIP": "#FFEFD5", "PEACHPUFF": "#FFDAB9", "PERU": "#CD853F", "PINK": "#FFC0CB", "PLUM": "#DDA0DD", "POWDERBLUE": "#B0E0E6", "PURPLE": "#800080", "REBECCAPURPLE": "#663399", "RED": "#FF0000", "ROSYBROWN": "#BC8F8F", "ROYALBLUE": "#4169E1", "SADDLEBROWN": "#8B4513", "SALMON": "#FA8072", "SANDYBROWN": "#F4A460", "SEAGREEN": "#2E8B57", "SEASHELL": "#FFF5EE", "SIENNA": "#A0522D", "SILVER": "#C0C0C0", "SKYBLUE": "#87CEEB", "SLATEBLUE": "#6A5ACD", "SLATEGRAY": "#708090", "SLATEGREY": "#708090", "SNOW": "#FFFAFA", "SPRINGGREEN": "#00FF7F", "STEELBLUE": "#4682B4", "TAN": "#D2B48C", "TEAL": "#008080", "THISTLE": "#D8BFD8", "TOMATO": "#FF6347", "TURQUOISE": "#40E0D0", "VIOLET": "#EE82EE", "WHEAT": "#F5DEB3", "WHITE": "#FFFFFF", "WHITESMOKE": "#F5F5F5", "YELLOW": "#FFFF00", "YELLOWGREEN": "#9ACD32"};

/**
 * Função que define se uma cor, dado o nome dela, é clara ou escura.
 * @param {string} cor - Nome da cor, pode ter um dos seguintes formatos: '#abcdef', '#abc', 'rgb(a,b,c)', 'rgba(a,b,c)'  ou ser um nome comum de cor, tal como 'red' ou 'yellow'.
 */
function corClara(cor) {
    var r, g, b;
    try {
        var rgb = cor.trim().toUpperCase();
        if (colorMap[rgb]) rgb = colorMap[rgb];
        if (rgb.startsWith("#") && rgb.length === 7) {
            r = parseInt(rgb.substring(1, 3), 16) / 255;
            g = parseInt(rgb.substring(3, 5), 16) / 255;
            b = parseInt(rgb.substring(5, 7), 16) / 255;
        } else if (rgb.startsWith("#") && rgb.length === 4) {
            r = parseInt(rgb.substring(1, 2), 16) * 17 / 255;
            g = parseInt(rgb.substring(2, 3), 16) * 17 / 255;
            b = parseInt(rgb.substring(3, 4), 16) * 17 / 255;
        } else if (rgb.startsWith("RGB") && rgb.endsWith(")")) {
            rgb = rgb.substring(3);
            var a = rgb.charAt(0) === 'A';
            if (a) rgb = rgb.substring(1);
            rgb = rgb.trim();
            if (!rgb.startsWith("(")) throw new Error();
            rgb = rgb.substring(1, rgb.length - 1);
            var x = rgb.split(',');
            if (x.length !== (a ? 4 : 3)) throw new Error();
            r = parseInt(x[0].trim());
            g = parseInt(x[1].trim());
            b = parseInt(x[2].trim());
            aa = a ? parseFloat(x[3].trim()) : 0.0;
            if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a) || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || aa < 0.0 || aa > 1.0) throw new Error();
            r /= 255;
            g /= 255;
            b /= 255;
        } else {
            throw new Error();
        }
        return corClaraRgb(r, g, b);
    } catch (x) {
        throw new Error("A cor '" + cor + "' não foi reconhecida.");
    }
}

// Teste
function teste(x) {
    try {
        document.write("A cor '" + x + "' é uma cor " + (corClara(x) ? "clara" : "escura") + ".<br>");
    } catch (e) {
        document.write(e.message + "<br>");
    }
}

document.write("<h2>Essas cores devem ser todas válidas</h2>");
teste("#102030");
teste("#0000ff");
teste("#00f");
teste("#00ff00");
teste("#FF00FF");
teste("#808080");
teste("#888");
teste("#ffffff");
teste("#fff");
teste("#000000");
teste("#000");
teste("#a000A0");
teste("#a0A");
teste("yellow");
teste("BLUE");
teste("PowderBlue");
teste("rgb(20, 40, 60)");
teste("rgb(100, 180, 250)");
teste(" rgb (100 , 180 , 250 )");
teste("  rgb (255 , 255 , 255 )  ");
teste("rgba(20, 40, 60, 0.4)");
teste("rgba(100, 180, 250, 0.7)");
teste(" rgba (100 , 180 , 250 , 0.9)");
teste("  rgba (255 , 255 , 255 , 0.33 )  ");

document.write("<h2>Essas cores devem ser todas inválidas</h2>");
teste("hahaha");
teste("cor de burro quando foge");
teste("rgba(255, 255, 255)");
teste("rgb(255, 255, 255, 255)");
teste("rgb(255, 255)");
teste("rgba(255, 255, 255, 0, 0)");
teste("rgb(256, 0, 0)");
teste("rgb(-1, 0, 0)");
teste("rgba(256, 0, 0, 0.0)");
teste("rgba(-1, 0, 0, 0.0)");
teste("rgba(255, 0, 0, -1.0)");
teste("rgba(255, 0, 0, 1.1)");
teste({});
teste(1245);
teste([]);
teste(undefined);
teste(null);

  • I’m seeing "rgb" in function. In the case of hexadecimal, how would it look?

  • @Wallace, in the case of a color #abcdef, the ab would be the red, the cd the green and the ef blue. I will edit the answer to address this.

  • I figured it was that, since Bacco had told me to "split it three ways"

  • @Edited Wallacemaxters. Still want to make some changes to address specified colors in other formats.

  • @Wallacemaxters Edited Again. Tell me what you think of this.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.