How to check if hex color is "too black"?

JavascriptColors

Javascript Problem Overview


I'm trying to evaluate the darkness of a color chosen by a color picker to see if it's "too black", and if so, set it to white. I thought I could use the first characters of the hex value to pull this off. It's working, but it's switching some legitimately "light" colors too.

I have code doing this:

		if (lightcolor.substring(0,3) == "#00"|| lightcolor.substring(0,3) == "#010"){
			lightcolor="#FFFFFF";
			color=lightcolor;
		}

There must be a more efficient way with hex math to know that a color has gone beyond a certain level of darkness? Like if lightcolor + "some hex value" <= "some hex value" then set it to white.

I have tinyColor added, which might be of use for this, but I don't know for sure.

Javascript Solutions


Solution 1 - Javascript

You have to extract the three RGB components individually, and then use a standard formula to convert the resulting RGB values into their perceived brightness.

Assuming a six character colour:

var c = c.substring(1);      // strip #
var rgb = parseInt(c, 16);   // convert rrggbb to decimal
var r = (rgb >> 16) & 0xff;  // extract red
var g = (rgb >>  8) & 0xff;  // extract green
var b = (rgb >>  0) & 0xff;  // extract blue

var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709

if (luma < 40) {
    // pick a different colour
}

EDIT

Since May 2014 tinycolor now has a getBrightness() function, albeit using the CCIR601 weighting factors instead of the ITU-R ones above.

EDIT

The resulting luma value range is 0..255, where 0 is the darkest and 255 is the lightest. Values greater than 128 are considered light by tinycolor. (shamelessly copied from the comments by @pau.moreno and @Alnitak)

Solution 2 - Javascript

I found this WooCommerce Wordpress PHP function (wc_hex_is_light) and I converted to JavaScript. Works fine!

function wc_hex_is_light(color) {
    const hex = color.replace('#', '');
    const c_r = parseInt(hex.substr(0, 2), 16);
    const c_g = parseInt(hex.substr(2, 2), 16);
    const c_b = parseInt(hex.substr(4, 2), 16);
    const brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
    return brightness > 155;
}

Solution 3 - Javascript

The TinyColor library (you've already mentioned it) provides several functions for inspecting and manipulating colors, among them:

  • getBrightness

    > Returns the perceived brightness of a color, from 0-255, as defined by Web Content Accessibility Guidelines (Version 1.0). > > tinycolor("#fff").getBrightness(); // 255

  • isLight

    > Return a boolean indicating whether the color's perceived brightness is light. > > tinycolor("#fff").isLight(); // true > tinycolor("#000").isLight(); // false

  • isDark

    > Return a boolean indicating whether the color's perceived brightness is dark. > > tinycolor("#fff").isDark(); // false > tinycolor("#000").isDark(); // true

  • getLuminance

    > Returns the perceived luminance of a color, from 0-1 as defined by Web Content Accessibility Guidelines (Version 2.0). > > tinycolor("#fff").getLuminance(); // 1

Solution 4 - Javascript

This work with hex e.g #fefefe

function isTooDark(hexcolor){
    var r = parseInt(hexcolor.substr(1,2),16);
    var g = parseInt(hexcolor.substr(3,2),16);
    var b = parseInt(hexcolor.substr(4,2),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    // Return new color if to dark, else return the original
    return (yiq < 40) ? '#2980b9' : hexcolor;
}

You can change it to return true or false by change

return (yiq < 40) ? '#2980b9' : hexcolor;

to

return (yiq < 40);

Solution 5 - Javascript

You can compute the luminance:

> Luminance is thus an indicator of how bright the surface will appear.

So it's great to choose if the text should be white or black.

var getRGB = function(b){
    var a;
    if(b&&b.constructor==Array&&b.length==3)return b;
    if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))return[parseInt(a[1]),parseInt(a[2]),parseInt(a[3])];
    if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];
    if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],
16)];
    if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];
    return (typeof (colors) != "undefined")?colors[jQuery.trim(b).toLowerCase()]:null
};

var luminance_get = function(color) {
    var rgb = getRGB(color);
    if (!rgb) return null;
        return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
}

The method above allows you to pass the color in different formats, but the algorithm is basically just in luminance_get.

When I used it, I was setting the color to black if the luminance was greater than 180, white otherwise.

Solution 6 - Javascript

There's an important distinction here between luminance and brightness. Luminance, at the end of the day, is a measure of how much energy travels through a certain area and completely ignores how our perceptual systems perceive that energy. Brightness, on the other hand, is a measure of how we perceive that energy and takes into the account the relationship between luminance and our perceptual system. (As a point of confusion, there is a term called relative luminance, which seems to be used synonymously with brightness terms. It tripped me up good).

To be precise, you are looking for "brightness" or "value" or "relatively luminance" as others have suggested. You can calculate this in several different way (such is to be human!) http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness

  1. Take the max of R, G, and B.
  2. Take the average of the max and the min from R, G, and B.
  3. Take the average of all three.
  4. Use some weighted average as others have suggested here.

Solution 7 - Javascript

I realize this conversation is a few years old, but it is still relevant. I wanted to add that my team was having the same issue in Java (SWT) and found this to be a bit more accurate:

private Color getFontColor(RGB bgColor) {
    Color COLOR_BLACK = new Color(Display.getDefault(), 0, 0, 0);
    Color COLOR_WHITE = new Color(Display.getDefault(), 255, 255, 255);

    double luminance = Math.sqrt(0.241 
       * Math.pow(bgColor.red, 2) + 0.691 * Math.pow(bgColor.green, 2) +  0.068 
       * Math.pow(bgColor.blue, 2));
    if (luminance >= 130) {
        return COLOR_BLACK;
    } else {
        return COLOR_WHITE;
    }
}

Solution 8 - Javascript

A possible solution would be to convert your color from RGB to HSB. HSB stands for hue, saturation, and brightness (also known as HSV, where V is for value). Then you have just one parameter to check: brightness.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDshizView Question on Stackoverflow
Solution 1 - JavascriptAlnitakView Answer on Stackoverflow
Solution 2 - JavascriptSergio CabralView Answer on Stackoverflow
Solution 3 - JavascriptskaleeView Answer on Stackoverflow
Solution 4 - JavascriptTheCrazyProfessorView Answer on Stackoverflow
Solution 5 - JavascriptRobinView Answer on Stackoverflow
Solution 6 - JavascriptDavid NguyenView Answer on Stackoverflow
Solution 7 - JavascriptChris ClarkView Answer on Stackoverflow
Solution 8 - JavascriptOhadView Answer on Stackoverflow