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

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 for background, The Color of the text becomes unreadable.

So I wanted to know if there is any way to find out if the color is dark, so, when it is dark, I determine that the color of the text it will be white, and if it is clear, black.

How do you do that?

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

Author: Wallace Maxters, 2017-02-13

3 answers

Calculating luminosity

The solution already 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 brightness in the range from 0 to 255.

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

This is an average that does not apply to the colorblind. colorblind people are people who have problems with any of these "channels" of color, often failing to see partially or completely any of the three colors.


treating or hexadecimal

A color in the format #add555 is nothing more than the combination of RGB values in hexadecimal. In the 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 do hexadecimal numbers work?

The calculation of hexa is quite simple. I will not go into details, because both JS and PHP have their own functions for this, who wants 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 that colors in "short" format such as #fc0 are simply a shorter way of writing #ffcc00.

To convert a color from the short "format" just this:

r = r_curto * 17
  • Q: but where did this 17 come from?

  • R: in short form, 0xF equals 0xFF, 0x1 equals 0x11. In other words, a variation from 0 to 15 in decimal in short form equals an actual variation from 0 to 255.

    Simplifying: 255 / 15 = 17:)


in PHP it 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, you have a complete parser ready here:

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

 55
Author: Bacco, 2020-06-11 14:45:34

The TinyColor library provides several functions for inspecting and manipulating colors, including:

  • IsLight

    Returns a boolean indicating whether the perceived brightness for the color is clear.

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

    Returns a boolean indicating whether 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 .

 13
Author: Murillo Goulart, 2020-06-11 14:45:34

I already talked about it 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, this 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.

Actually, the exact proportion of the composition of white light, it depends on the willingness of the various receptor cells in the retina of the eye of the beholder, improved health, tiredness, old age, and the stress of the observer, the light conditions, the brightness and contrast of the screen from the angle and the direction between the plane of the screen, and the line-of-sight from the observer to the top of the screen (reflective or non-reflective, CRT, LED, plasma, LCD, projector, games, etc.), and many other factors, including it may even vary for an 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 vision and is using a good quality screen in an environment with adequate lighting, there is a formula that I saw in a book once a few years ago that gave the following ratio:

White = 0.290 * red + 0.599 * green + 0.111 * blue

It's a pity that I do not remember the title, but the bfavaretto gave three references to this in the comments: 1, 2 e 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 of anti-aliasing that considers that the subpixels have different colors.

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

Brightness = 0.299 * red + 0.587 * green + 0.114 * blue

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

If we consider all the colors arranged as different internal points on 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), we would have one of the dimensions correspond to the value of the red, 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 component of the corresponding color, then we could use the Euclidean distance from the point occupied by any color within this parallelepiped to the vertex of the Black color as a measure of brightness. Thus, to calculate the intensity of a color, it is enough to use the Pythagorean theorem. If we use the values of W3C, we would get to this Formula :

Brightness = sqrt ( 0.299 * (Red) 2 + 0.587 * (Green) 2 + 0.114 * (Blue)2)

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

Therefore, a solution in Javascript considering that a light color would be the one closest to white while a dark one is the one closest 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);
 11
Author: Victor Stafusa, 2017-05-23 12:37:31