How to decide font color in white or black depending on background color?

ColorsContrast

Colors Problem Overview


I want to show some images like this examplealt text

The fill color is decided by a field in the data base with the color in hex (ex:ClassX -> Color: #66FFFF). Now, I want to show data above a fill with the selected color (like in the image above) but i need to know if the color is dark or light so i know if the words should be in white or black. Is there a way? tks

Colors Solutions


Solution 1 - Colors

Building on my answer to a similar question.

You need to break the hex code into 3 pieces to get the individual red, green, and blue intensities. Each 2 digits of the code represent a value in hexadecimal (base-16) notation. I won't get into the details of the conversion here, they're easy to look up.

Once you have the intensities for the individual colors, you can determine the overall intensity of the color and choose the corresponding text.

if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff

The threshold of 186 is based on theory, but can be adjusted to taste. Based on the comments below a threshold of 150 may work better for you.


Edit: The above is simple and works reasonably well, and seems to have good acceptance here at StackOverflow. However, one of the comments below shows it can lead to non-compliance with W3C guidelines in some circumstances. Herewith I derive a modified form that always chooses the highest contrast based on the guidelines. If you don't need to conform to W3C rules then I'd stick with the simpler formula above.

The formula given for contrast in the W3C Recommendations is (L1 + 0.05) / (L2 + 0.05), where L1 is the luminance of the lightest color and L2 is the luminance of the darkest on a scale of 0.0-1.0. The luminance of black is 0.0 and white is 1.0, so substituting those values lets you determine the one with the highest contrast. If the contrast for black is greater than the contrast for white, use black, otherwise use white. Given the luminance of the color you're testing as L the test becomes:

if (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) use #000000 else use #ffffff

This simplifies down algebraically to:

if L > sqrt(1.05 * 0.05) - 0.05

Or approximately:

if L > 0.179 use #000000 else use #ffffff

The only thing left is to compute L. That formula is also given in the guidelines and it looks like the conversion from sRGB to linear RGB followed by the ITU-R recommendation BT.709 for luminance.

for each c in r,g,b:
    c = c / 255.0
    if c <= 0.03928 then c = c/12.92 else c = ((c+0.055)/1.055) ^ 2.4
L = 0.2126 * r + 0.7152 * g + 0.0722 * b

The threshold of 0.179 should not be changed since it is tied to the W3C guidelines. If you find the results not to your liking, try the simpler formula above.

Solution 2 - Colors

I take no credit for this code as it's not mine, but I leave it here for others to find quickly in the future:

Based on Mark Ransoms answer, here's a code snippet for the simple version:

function pickTextColorBasedOnBgColorSimple(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ?
    darkColor : lightColor;
}

and here's the code snippet for the advanced version:

function pickTextColorBasedOnBgColorAdvanced(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  var uicolors = [r / 255, g / 255, b / 255];
  var c = uicolors.map((col) => {
    if (col <= 0.03928) {
      return col / 12.92;
    }
    return Math.pow((col + 0.055) / 1.055, 2.4);
  });
  var L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
  return (L > 0.179) ? darkColor : lightColor;
}

To use them just call:

var color = '#EEACAE' // this can be any color
pickTextColorBasedOnBgColorSimple(color, '#FFFFFF', '#000000');

Also, thanks Alx and chetstone.

Solution 3 - Colors

How about this (JavaScript code)?

/**
 * Get color (black/white) depending on bgColor so it would be clearly seen.
 * @param bgColor
 * @returns {string}
 */
getColorByBgColor(bgColor) {
    if (!bgColor) { return ''; }
    return (parseInt(bgColor.replace('#', ''), 16) > 0xffffff / 2) ? '#000' : '#fff';
}

Solution 4 - Colors

Additionally to the arithmetic solutions, it's also possible to use an AI neural network. The advantage is that you can tailor it to your own taste and needs (ie. off-white text on bright saturated reds looks good and is just as readable as black).

Here's a neat Javascript demo that illustrates the concept. You can also generate your own JS formula right in the demo.

https://harthur.github.io/brain/

Below are some charts that helped me get my mind around the problem. In the first chart, lightness is a constant 128, while hue and saturation vary. In the second chart, saturation is a constant 255, while hue and lightness vary.

In the first chart, lightness is a constant 128, while hue and saturation vary:

Saturation is a constant 255, while hue and lightness vary:

Solution 5 - Colors

If like me you were looking for a RGBA version that takes alpha into account, here is one that works really well to have high contrast.

function getContrastColor(R, G, B, A) {
  const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A) * 255;

  return brightness > 186 ? "#000000" : "#FFFFFF";
}

Solution 6 - Colors

Here is my solution in Java for Android:

// Put this method in whichever class you deem appropriate
// static or non-static, up to you.
public static int getContrastColor(int colorIntValue) {
    int red = Color.red(colorIntValue);
    int green = Color.green(colorIntValue);
    int blue = Color.blue(colorIntValue);
    double lum = (((0.299 * red) + ((0.587 * green) + (0.114 * blue))));
    return lum > 186 ? 0xFF000000 : 0xFFFFFFFF;
}

// Usage
// If Color is represented as HEX code:
String colorHex = "#484588";
int color = Color.parseColor(colorHex);

// Or if color is Integer:
int color = 0xFF484588;

// Get White (0xFFFFFFFF) or Black (0xFF000000)
int contrastColor = WhateverClass.getContrastColor(color);

Solution 7 - Colors

Based on the answer of @MarkRansom, I created a PHP script you can find here:

function calcC($c) {
    if ($c <= 0.03928) {
        return $c / 12.92;
    }
    else {
        return pow(($c + 0.055) / 1.055, 2.4);
    }
}

function cutHex($h) {
    return ($h[0] == "#") ? substr($h, 1, 7) : $h;
}

function hexToR($h) {
    return hexdec(substr(cutHex($h), 0, 2));
}

function hexToG($h) {
    return hexdec(substr(cutHex($h), 2, 2)); // Edited
}

function hexToB($h) {
    return hexdec(substr(cutHex($h), 4, 2)); // Edited
}

function computeTextColor($color) {
    $r = hexToR($color);
    $g = hexToG($color);
    $b = hexToB($color);
    $uicolors = [$r / 255, $g / 255, $b / 255];


    $c = array_map("calcC", $uicolors);

    $l = 0.2126 * $c[0] + 0.7152 * $c[1] + 0.0722 * $c[2];
    return ($l > 0.179) ? '#000000' : '#ffffff';
}

Solution 8 - Colors

This is a swift version of Mark Ransom's answer as an extension of UIColor

extension UIColor {

// Get the rgba components in CGFloat
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
    var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
    
    getRed(&red, green: &green, blue: &blue, alpha: &alpha)
    
    return (red, green, blue, alpha)
}

/// Return the better contrasting color, white or black
func contrastColor() -> UIColor {
    let rgbArray = [rgba.red, rgba.green, rgba.blue]
    
    let luminanceArray = rgbArray.map({ value -> (CGFloat) in
        if value < 0.03928 {
            return (value / 12.92)
        } else {
            return (pow( (value + 0.55) / 1.055, 2.4) )
        }
    })

    let luminance = 0.2126 * luminanceArray[0] +
        0.7152 * luminanceArray[1] +
        0.0722 * luminanceArray[2]
    
    return luminance > 0.179 ? UIColor.black : UIColor.white
} }

Solution 9 - Colors

Here's my own method that I've been using and haven't had a problem with so far 

function hexToRgb(hex) {
    const hexCode = hex.charAt(0) === '#' 
                        ? hex.substr(1, 6)
                        : hex;

    const hexR = parseInt(hexCode.substr(0, 2), 16);
    const hexG = parseInt(hexCode.substr(2, 2), 16);
    const hexB = parseInt(hexCode.substr(4, 2), 16);
    // Gets the average value of the colors
    const contrastRatio = (hexR + hexG + hexB) / (255 * 3);

    return contrastRatio >= 0.5
        ? 'black'
        : 'white';
}

Solution 10 - Colors

This is just an example that will change the color of an SVG checkmark when clicking on an element. It will set the checkmark color to black or white based on the background-color of the clicked element.

checkmarkColor: function(el) {
    var self = el;
    var contrast = function checkContrast(rgb) {
        // @TODO check for HEX value

        // Get RGB value between parenthesis, and remove any whitespace
        rgb = rgb.split(/\(([^)]+)\)/)[1].replace(/ /g, '');

        // map RGB values to variables
        var r = parseInt(rgb.split(',')[0], 10),
            g = parseInt(rgb.split(',')[1], 10),
            b = parseInt(rgb.split(',')[2], 10),
            a;

        // if RGBA, map alpha to variable (not currently in use)
        if (rgb.split(',')[3] !== null) {
            a = parseInt(rgb.split(',')[3], 10);
        }

        // calculate contrast of color (standard grayscale algorithmic formula)
        var contrast = (Math.round(r * 299) + Math.round(g * 587) + Math.round(b * 114)) / 1000;

        return (contrast >= 128) ? 'black' : 'white';
    };

    $('#steps .step.color .color-item .icon-ui-checkmark-shadow svg').css({
        'fill': contrast($(self).css('background-color'))
    });
}

onClickExtColor: function(evt) {
    var self = this;

    self.checkmarkColor(evt.currentTarget);
}

https://gist.github.com/dcondrey/183971f17808e9277572

Solution 11 - Colors

I use this JavaScript function to convert rgb/rgba to 'white' or 'black'.

function getTextColor(rgba) {
  	rgba = rgba.match(/\d+/g);
   	if ((rgba[0] * 0.299) + (rgba[1] * 0.587) + (rgba[2] * 0.114) > 186) {
   		return 'black';
   	} else {
   		return 'white';
   	}
}

You can enter any of these formats and it will output 'black' or 'white'

  • rgb(255,255,255)
  • rgba(255,255,255,0.1)
  • color:rgba(255,255,255,0.1)
  • 255,255,255,0.1

Solution 12 - Colors

Mark's detailed answer works great. Here is an implementation in javascript:

function lum(rgb) {
    var lrgb = [];
    rgb.forEach(function(c) {
        c = c / 255.0;
        if (c <= 0.03928) {
            c = c / 12.92;
        } else {
            c = Math.pow((c + 0.055) / 1.055, 2.4);
        }
        lrgb.push(c);
    });
    var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
    return (lum > 0.179) ? '#000000' : '#ffffff';
}

Then can call this function lum([111, 22, 255]) to get white or black.

Solution 13 - Colors

This is an R version of Mark Ransom's answer, using base R only.

hex_bw <- function(hex_code) {
  
  myrgb <- as.integer(col2rgb(hex_code))
  
  rgb_conv <- lapply(myrgb, function(x) {
    i <- x / 255
    if (i <= 0.03928) {
      i <- i / 12.92
    } else {
      i <- ((i + 0.055) / 1.055) ^ 2.4
    }
    return(i)
  })

 rgb_calc <- (0.2126*rgb_conv[[1]]) + (0.7152*rgb_conv[[2]]) + (0.0722*rgb_conv[[3]])
 
 if (rgb_calc > 0.179) return("#000000") else return("#ffffff")
   
}

> hex_bw("#8FBC8F")
[1] "#000000"
> hex_bw("#7fa5e3")
[1] "#000000"
> hex_bw("#0054de")
[1] "#ffffff"
> hex_bw("#2064d4")
[1] "#ffffff"
> hex_bw("#5387db")
[1] "#000000"

Solution 14 - Colors

I'm using the tinyColor library which can also do the job.

import { TinyColor } from '@ctrl/tinycolor'

// ...

getColorContrast(color = '#66FFFF'): string {
  if(new TinyColor(color).getLuminance() > 0.179) { // 0.179 -> Mark Ransom answer
     return '#000'
  } else {
     return '#fff'
  }
}

This method would also accept rgb color like rgb(102,255,255)

Solution 15 - Colors

I've never done anything like this, but what about writing a function to check the values of each of the colors against the median color of Hex 7F (FF / 2). If two of the three colors are greater than 7F, then you're working with a darker color.

Solution 16 - Colors

LESS has a nice contrast() function that worked nicely for me, see http://lesscss.org/functions/#color-operations-contrast

"Choose which of two colors provides the greatest contrast with another. This is useful for ensuring that a color is readable against a background, which is also useful for accessibility compliance. This function works the same way as the contrast function in Compass for SASS. In accordance with WCAG 2.0, colors are compared using their gamma-corrected luma value, not their lightness."

Example:

p {
    a: contrast(#bbbbbb);
    b: contrast(#222222, #101010);
    c: contrast(#222222, #101010, #dddddd);
    d: contrast(hsl(90, 100%, 50%), #000000, #ffffff, 30%);
    e: contrast(hsl(90, 100%, 50%), #000000, #ffffff, 80%);
}

Output:

p {
    a: #000000 // black
    b: #ffffff // white
    c: #dddddd
    d: #000000 // black
    e: #ffffff // white
}

Solution 17 - Colors

From hex to black or white:

function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result
    ? [
        parseInt(result[1], 16),
        parseInt(result[2], 16),
        parseInt(result[3], 16)
      ]
    : [0, 0, 0];
}

function lum(hex) {
  var rgb = hexToRgb(hex)
  var lrgb = [];
  rgb.forEach(function(c) {
    c = c / 255.0;
    if (c <= 0.03928) {
      c = c / 12.92;
    } else {
      c = Math.pow((c + 0.055) / 1.055, 2.4);
    }
    lrgb.push(c);
  });
  var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
  return lum > 0.179 ? "#000000" : "#ffffff";
}

Solution 18 - Colors

Based on different inputs from the link https://stackoverflow.com/questions/2241447/make-foregroundcolor-black-or-white-depending-on-background and this thread, I made an extension class for Color that gives you required contrast colors.

The code goes as below:

 public static class ColorExtension
{       
    public static int PerceivedBrightness(this Color c)
    {
        return (int)Math.Sqrt(
        c.R * c.R * .299 +
        c.G * c.G * .587 +
        c.B * c.B * .114);
    }
    public static Color ContrastColor(this Color iColor, Color darkColor,Color lightColor)
    {
        //  Counting the perceptive luminance (aka luma) - human eye favors green color... 
        double luma = (iColor.PerceivedBrightness() / 255);

        // Return black for bright colors, white for dark colors
        return luma > 0.5 ? darkColor : lightColor;
    }
    public static Color ContrastColor(this Color iColor) => iColor.ContrastColor(Color.Black);
    public static Color ContrastColor(this Color iColor, Color darkColor) => iColor.ContrastColor(darkColor, Color.White);
    // Converts a given Color to gray
    public static Color ToGray(this Color input)
    {
        int g = (int)(input.R * .299) + (int)(input.G * .587) + (int)(input.B * .114);
        return Color.FromArgb(input.A, g, g, g);
    }
}

Solution 19 - Colors

I did a function based on the advanced one proposed by @SudoPlz which takes also account of light and dark colors:

function getTextColor (bgColor, lightColor = '#FFFFFF', darkColor = '#000000') {

  const getLuminance = function (hexColor) {
    var color = (hexColor.charAt(0) === '#') ? hexColor.substring(1, 7) : hexColor
    var r = parseInt(color.substring(0, 2), 16) // hexToR
    var g = parseInt(color.substring(2, 4), 16) // hexToG
    var b = parseInt(color.substring(4, 6), 16) // hexToB
    var uicolors = [r / 255, g / 255, b / 255]
    var c = uicolors.map(col => col <= 0.03928 ? col / 12.92 : ((col + 0.055) / 1.055) ** 2.4)

    return (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
  }

  var L = getLuminance(bgColor)
  var L1 = getLuminance(lightColor)
  var L2 = getLuminance(darkColor)

  return (L > Math.sqrt((L1 + 0.05) * (L2 + 0.05)) - 0.05) ? darkColor : lightColor;
}

Therefore if the dark text is not black but maroon, the recommended text color on a grey background becomes white:

getTextColor('#808080')
"#000000"
getTextColor('#808080', '#FFFFFF', '#800000')
"#FFFFFF"

Solution 20 - Colors

@SoBiT, I was looking at your answer, which looks good, but there is a small mistake in it. Your function hexToG, and hextoB need a minor edit. The last number in substr is the length of the string, and so in this case itshould be "2", rather than 4 or 6.

function hexToR($h) {
    return hexdec(substr(cutHex($h), 0, 2));
}
function hexToG($h) {
    return hexdec(substr(cutHex($h), 2, 2));
}
function hexToB($h) {
    return hexdec(substr(cutHex($h), 4, 2));
}

Solution 21 - Colors

Objective-c version code for iOS based on Mark's answer:

- (UIColor *)contrastForegroundColor {
CGFloat red = 0, green = 0, blue = 0, alpha = 0;
[self getRed:&red green:&green blue:&blue alpha:&alpha];
NSArray<NSNumber *> *rgbArray = @[@(red), @(green), @(blue)];
NSMutableArray<NSNumber *> *parsedRGBArray = [NSMutableArray arrayWithCapacity:rgbArray.count];
for (NSNumber *item in rgbArray) {
    if (item.doubleValue <= 0.03928) {
        [parsedRGBArray addObject:@(item.doubleValue / 12.92)];
    } else {
        double newValue = pow((item.doubleValue + 0.055) / 1.055, 2.4);
        [parsedRGBArray addObject:@(newValue)];
    }
}

double luminance = 0.2126 * parsedRGBArray[0].doubleValue + 0.7152 * parsedRGBArray[1].doubleValue + 0.0722 * parsedRGBArray[2].doubleValue;

return luminance > 0.179 ? UIColor.blackColor : UIColor.whiteColor;
}

Solution 22 - Colors

What about testing with all 24 bits colors ?

Be aware that YIQ method will return a minimum contrast ratio of 1.9:1, that doesn't pass AA and AAA WCAG2.0 tests, assuming a 128 threshold.

For W3C method, it will return a minimum contrast ratio of 4.58:1, that pass AA and AAA tests for large text, and AA test for small text, it won't pass AAA test for small text for every colors.

Solution 23 - Colors

Here is my code for Java Swing based on Mark's amazing answer:

public static Color getColorBasedOnBackground(Color background, Color darkColor, Color lightColor) {
    // Calculate foreground color based on background (based on https://stackoverflow.com/a/3943023/)
    Color color;
    double[] cL = new double[3];
    double[] colorRGB = new double[] {background.getRed(), background.getGreen(), background.getBlue()};

    for (int i = 0; i < colorRGB.length; i++)
        cL[i] = (colorRGB[i] / 255.0 <= 0.03928) ? colorRGB[i] / 255.0 / 12.92 :
                Math.pow(((colorRGB[i] / 255.0 + 0.055) / 1.055), 2.4);

    double L = 0.2126 * cL[0] + 0.7152 * cL[1] + 0.0722 * cL[2];
    color = (L > Math.sqrt(1.05 * 0.05) - 0.05) ? darkColor : lightColor;

    return color;
}

Solution 24 - Colors

Accepted answer somehow never worked on Android when using androidx.compose.ui.graphics.Color. Then i found there is actually built in luminance() function in Android Jetpack Compose which returns luminance value between [0,1]. Documentation says "Based on the formula for relative luminance defined in WCAG 2.0, W3C Recommendation 11 December 2008.".

Here is the official source code.

Example usage:

fun Color.generateOnColor()
        : Color {
    return if (luminance() > 0.5f) {
        Color.Black.copy(alpha = .8f)
    } else {
        Color.White
    }
}

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
QuestionDJPBView Question on Stackoverflow
Solution 1 - ColorsMark RansomView Answer on Stackoverflow
Solution 2 - ColorsSudoPlzView Answer on Stackoverflow
Solution 3 - ColorsGeorge KaganView Answer on Stackoverflow
Solution 4 - ColorsFélix MénardView Answer on Stackoverflow
Solution 5 - ColorsGermainView Answer on Stackoverflow
Solution 6 - ColorsmwieczorekView Answer on Stackoverflow
Solution 7 - ColorsSoBiTView Answer on Stackoverflow
Solution 8 - ColorsRydellView Answer on Stackoverflow
Solution 9 - ColorsGodstime OsaroboView Answer on Stackoverflow
Solution 10 - ColorsdavidcondreyView Answer on Stackoverflow
Solution 11 - Colorsd-_-bView Answer on Stackoverflow
Solution 12 - ColorsfireantView Answer on Stackoverflow
Solution 13 - ColorsNauticaView Answer on Stackoverflow
Solution 14 - ColorsRaphaël BaletView Answer on Stackoverflow
Solution 15 - ColorsDJ QuimbyView Answer on Stackoverflow
Solution 16 - ColorsPeter T.View Answer on Stackoverflow
Solution 17 - ColorsDorianView Answer on Stackoverflow
Solution 18 - ColorsKasim HusainiView Answer on Stackoverflow
Solution 19 - ColorsJean PaulView Answer on Stackoverflow
Solution 20 - ColorsPatchView Answer on Stackoverflow
Solution 21 - ColorsSimon WangView Answer on Stackoverflow
Solution 22 - ColorsPhiphouView Answer on Stackoverflow
Solution 23 - ColorsAyush GargView Answer on Stackoverflow
Solution 24 - ColorsJemshit IskenderovView Answer on Stackoverflow