385

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

1

27 Answers 27

587

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. For an interesting look into the problems with this see Contrast Ratio Math and Related Visual Issues.

The formula given for contrast in the W3C Recommendations (WCAG 2.0) 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.04045 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.

30
  • 3
    Tks Mark. Tryed a few changes: calculated red green and blue by the first digit only (less precise but is the digit with more weight) and instead of 186 used 9. works a little bit better for me, specially with greens.
    – DJPB
    Commented Oct 15, 2010 at 16:26
  • 4
    This formula is wrong, by a lot. To take one example, it gives the yellow #D6B508 a value of 171, hence a white contrast. The contrast should be black, however (confirmed here: webaim.org/resources/contrastchecker)
    – McGarnagle
    Commented Jul 17, 2014 at 20:36
  • 3
    Here's another demo comparing the two formulas given in this answer. Using the color picker (in recent Firefox or Chrome) you can check the contrast against any color.
    – chetstone
    Commented May 13, 2016 at 5:58
  • 7
    After playing with @chetstone's demo for a bit, I think the Simple formula works best for me, except that I disagree with the threshold recommendation. Based on the results of pure green, I tried setting the threshold to 149 and that works a lot better in my opinion. I made a really dumb fiddle to demonstrate this; you can try changing the threshold at the top back to see the original suggestion.
    – Miral
    Commented Oct 20, 2016 at 6:23
  • 4
    for me 150 instead of 186 gave a good contrast between background color and text color
    – Oshada
    Commented Oct 22, 2018 at 5:30
112

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.

4
  • 2
    I used the simple function, and stripped it down a bit more: dropped the 2 last parameters and renamed is simply isDark(bgColor) My usage is then just 'color': isDark(color)?'white':'black'
    – diynevala
    Commented Jun 21, 2017 at 11:05
  • 1
    Worked like a charm for me. Thank you very much! Did it in php though simply converting the syntax and functions. Commented Mar 23, 2018 at 17:24
  • 6
    Note that Mark's calculation of L>0.179 assumes we are choosing among white and black as the text color. Other color choices would require re-calculating this threshold, so it doesn't quite make sense to pass in arbitrary colors as lightColor and darkColor. Commented Sep 23, 2020 at 19:41
  • 1
    I keep getting NaN with the advanced example.
    – coler-j
    Commented Dec 16, 2021 at 19:43
29

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:

4
  • 3
    This is an excellent way to visually display the options
    – Zac
    Commented May 18, 2021 at 16:02
  • 1
    I've republished my article on Medium as my old blog doesn't exist any more. medium.com/@think_ui/… Unfortunately I can't edit the link in this answer as the suggested edit queue is too long. Commented Nov 21, 2021 at 21:42
  • Hi @RogerAttrill I updated the link to you medium article in the post...
    – Myndex
    Commented Dec 30, 2021 at 5:55
  • The code provided under harthur.github.io/brain didn't work initially for me because the bgColor input was not documented. This is the format ` var string = ' #f876a9'; var htmlHexa = string.trim(); var rgbHexa = htmlHexa.charAt(0) === '#' ? htmlHexa.substring(1, 7) : htmlHexa; var bgColor = { r: parseInt(rgbHexa.substring(0, 2), 16) / 255, g: parseInt(rgbHexa.substring(2, 4), 16) / 255, b: parseInt(rgbHexa.substring(4, 6), 16) / 255, }; var textColor = function (bgColor) { ... ` Commented Apr 20, 2023 at 8:45
25

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';
}
2
  • 2
    This works most of the time but there are cases like i.imgur.com/3pOUDe5.jpg that look weird, background color is actually rgb(6, 247, 241);
    – madprops
    Commented Dec 30, 2016 at 11:54
  • I appreciate this is a relatively cheap way to perform a contrast calculation in cases where you haven't already converted the colour to rgb values (not that that is too difficult, just extra math steps)
    – frumbert
    Commented Mar 27, 2017 at 5:03
21

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";
}
2
  • 1
    logged in just to give you an upvote, couldnt find anyone taking tihs into account
    – robertjuh
    Commented Aug 26, 2020 at 10:19
  • @robertjuh I rarely post on stackoverflow as well, so thanks for the effort :)
    – Germain
    Commented Aug 27, 2020 at 14:57
12

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';
}
8

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);
3
  • 2
    Is it really 'perfect'? Try pure green background, #00FF00. Commented Jan 2, 2018 at 21:55
  • It's true this is not tested for all colors.... but who would use a pure green background for anything that wasn't designed to annoy users?
    – mwieczorek
    Commented Jan 3, 2018 at 11:00
  • 6
    @mwieczorek people who rely on user-generated content or randomly selected colors do. Commented Sep 15, 2018 at 12:54
6

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';
}
6

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
} }
4

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"
3

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"
3

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)

2

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

2

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
2
  • Now try this with a pure green background: #00FF00. Commented Jan 2, 2018 at 22:15
  • Thank you for this! Translated into swift and used it in my ios app!
    – Lucas P.
    Commented Jun 14, 2018 at 10:43
2

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.

1

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.

1
  • This does not work, espacially on most shades of green (the green component contributes the most to lightness). Take "lime" (#00FF00), your solution would use a light white text; take an even lighter shade of green (#7FFF7F), your solution still gives white text and it clearly not contrastive enough: in both cases the text should be black. Your solution works only for gray backgrounds (where all RGB components contribute equally to the final lightness of the gray background): if you use median gray (#7F7F7F or #808080) it's dark and contrasts bests white text (the threshold is > hex 7F)!
    – verdy_p
    Commented Jun 26, 2023 at 17:07
1

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
}
1

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";
}
0
1

Based on different inputs from the link 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);
    }
}
1

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
    }
}
0

@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));
}
0

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;
}
0

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.

0

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;
}
0

Just in case anybody cares for a SCSS version of Mark Ransom's answer:

@use 'sass:color' as *;
@use 'sass:math' as *;

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

@function pickTextColorBasedOnBgColorAdvanced(
  $bgColor,
  $lightColor,
  $darkColor
) {
  $r: red($bgColor);
  $g: green($bgColor);
  $b: blue($bgColor);
  $ui_r: $r / 255;
  $ui_g: $g / 255;
  $ui_b: $b / 255;

  $ui_r_c: col_r($ui_r);
  $ui_g_c: col_r($ui_g);
  $ui_b_c: col_r($ui_b);

  $L: (0.2126 * $ui_r_c) + (0.7152 * $ui_g_c) + (0.0722 * $ui_b_c);
  @if ($L > 0.179) {
    @return $darkColor;
  } @else {
    @return $lightColor;
  }
}
0
0

Ruby solution based on @SudoPlz's answer

def get_contrast_color(bg_color, light_color = '#FFFFFF', dark_color = '#000000')
    color = bg_color.gsub('#', '')
    r = color&.slice(0, 2).to_i(16)
    g = color&.slice(2, 2).to_i(16)
    b = color&.slice(4, 2).to_i(16)
    intensity = (r * 0.299) + (g * 0.587) + (b * 0.114)
    intensity > 186 ? dark_color : light_color
end
get_contrast_color('#222222') #=> '#FFFFFF'
0

For anyone searching for a Python3 implementation of SudoPlz's advanced function:

def get_text_color_based_on_background_color(bgColor, lightColor, darkColor):
   color = bgColor.lstrip("#").rstrip(";")
   r, g, b = hex_to_rgb(color)
   uicolors = [r/255, g/255, b/255]
   adjusted = []
   for col in uicolors:
      col2 = col
      if col <= 0.03928:
         col2 = col/12.92
      col2 = pow((col2 + 0.055)/1.055,2.4)
      adjusted.append(col2)
   L = (0.2126 * adjusted[0] + 0.7152 * adjusted[1] + (0.072 * adjusted[2]))
   return darkColor if L > 0.179 else lightColor

Which depends on hex_to_rgb from this venerable question.

def hex_to_rgb(value):
   value = value.lstrip('#').rstrip(";")
   lv = len(value)
   return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))

Not the answer you're looking for? Browse other questions tagged or ask your own question.