37

Given a background color, how to get a foreground color that makes it readable on that background color?

I mean computing that foreground color automatically in a program.

Or simplify the problem, if the foreground color is chosen from white/black, how to do the choice in a program?

2

9 Answers 9

25

Here's one I did in both Java and Javascript. It's loosely based off this one in javascript. I took the Luminance formula from here. The sweet-spot of the threshold from my eye was about 140.

Java version:

public class Color {

    private float CalculateLuminance(ArrayList<Integer> rgb){
        return (float) (0.2126*rgb.get(0) + 0.7152*rgb.get(1) + 0.0722*rgb.get(2));
    }

    private ArrayList<Integer> HexToRBG(String colorStr) {
        ArrayList<Integer> rbg = new ArrayList<Integer>();
        rbg.add(Integer.valueOf( colorStr.substring( 1, 3 ), 16 ));
        rbg.add(Integer.valueOf( colorStr.substring( 3, 5 ), 16 ));
        rbg.add(Integer.valueOf( colorStr.substring( 5, 7 ), 16 ));
        return rbg;
    }
    public String getInverseBW(String hex_color) {
        float luminance = this.CalculateLuminance(this.HexToRBG(hex_color));
        String inverse = (luminance < 140) ? "#fff" : "#000";
        return inverse;
    }

}

enter image description here

Javascript version:

Here's the same thing in javascript for your front-end things. RGB conversion taken from here:

hex_to_rgb: function(hex) {
        let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? { 
                r: parseInt(result[1], 16),
                g: parseInt(result[2], 16),
                b: parseInt(result[3], 16) 
        } : null;
},
hex_inverse_bw: function(hex) {
        let rgb = this.hex_to_rgb(hex);
        let luminance = (0.2126*rgb["r"] + 0.7152*rgb["g"] + 0.0722*rgb["b"]);
        return (luminance < 140) ? "#ffffff": "#000000";
}
3
  • Thanks, exactly what I needed. The other solution in other questions and accepted answer for this one, with normalization of rgb before calculating luminosity was not working for me, but this is perfect.
    – jcubic
    Commented Jul 11, 2018 at 14:22
  • Thanks for providing a ready to use code! Appreciate it!
    – Santhosh
    Commented Nov 5, 2021 at 17:04
  • I came here to copy code, and I got code ready to go instead of having to build one. Much appreciated, thank you!
    – MrU
    Commented Jun 14, 2022 at 20:30
22

The safest bet is to conform with the World Wide Web Consortium’s (W3C) Web Content Accessibility Guidelines 2.0, which specify a brightness contrast ratio of 4.5:1 for regular text (12 pt or smaller), and 3.0:1 for large text. Contrast ratio is defined as:

[Y(b) + 0.05] / [Y(d) + 0.05]

Where Y(b) is the brightness (luminance) of the brighter color and Y(d) is the brightness of the darker color.

You calculate luminance Y by first converting each of the color’s RGB values to gamma adjusted normalize rgb values:

  • r = (R/255)^2.2
  • b = (B/255)^2.2
  • g = (G/255)^2.2

Then combine them using sRGB constants (rounded to 4 places):

Y = 0.2126*r + 0.7151*g + 0.0721*b

This gives white a Y of 1 and black a Y of 0, so the maximum possible contrast is (1.05/ 0.05) = 21 (within rounding error).

Or let JuicyStudio do the math for you.

This calculation assumes a standard-performing monitor in a relatively dimly lit room (or a room that the user can make dim if she or he has to). That makes it adequate for home or office use, but I don’t know if it’s adequate for mobile apps or other devices that are used outdoors.

6
  • Calculate the lightness (see HSL)
  • If the lightness is less than 50%, use white. Otherwise, use black.

Using colors as foreground color is difficult, because you have to take contrast and color blindness into account.

2
  • 3
    I have read some code snippet for rgb_to_hsl,and the lightness is computed with: MAX(r, g, b)/255, and it seems not sufficient for a choice.
    – peterwang
    Commented Jun 25, 2010 at 7:40
  • great. d3.rgb().hsl is straightforward
    – egidiocs
    Commented May 22, 2015 at 14:57
4

Here's some actual (ruby) code that'll actually do the lifting:

rgbval = "8A23C0".hex
r = rgbval >> 16
g = (rgbval & 65280) >> 8
b = rgbval & 255
brightness = r*0.299 + g*0.587 + b*0.114
return (brightness > 160) ? "#000" : "#fff"
2

Based on the answers above I came up with a C# implementation, you may want to tweak the logic surrounding the alpha based on your requirements.

public static class ColorExtension
{
    public static Color GetReadableColor(this Color color) => color.GetLuminance() > 140 || color.A != 255 ? Color.Black : Color.White;
    public static double GetLuminance(this Color color) => (0.2126 * color.R) + (0.7151 * color.G) + (0.0721 * color.B);
}
0

You could compute the inverse colour, but you run the risk of contrast diminishing "in the middle" of the colour space.

0

In case this could still be useful for someone, this is the Dart implementation based on the answers above

Color getInverseBW(Color color) {
    double luminance = (0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue);
    return (luminance < 140) ? Color(0xffffffff) : Color(0xff000000);
}
0

Pascal / Delphi version:

var
  red,green,blue : Integer;
  luminance : double;

// convert hexa-decimal values to RGB
red := (_BackgroundColor) and $FF;
green := (_BackgroundColor shr 8) and $FF;
blue := (_BackgroundColor shr 16) and $FF;

luminance := (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);

if luminance < 140 then
  Result := TColors.White
else
  Result := TColors.Black;
0

PyQt5 version of Michael Zuschlag`s answer:

import sys
from PyQt5.QtGui import QColor
class MostReadableColor():
    def getLuminance(self, color):
    """ get color luminance.
    
    Convert color RGB values to gamma adjusted normalized rgb values
    then combine them using sRGB constants (rounded to 4 places).
    """
        r, g, b, a = QColor(color).getRgb()
        l = ((r/255)**2.2)*0.2126 + ((g/255)**2.2)*0.7151 + \
            ((b/255)**2.2)*0.0721
        return(l)

    def getContrastRation(self, color1, color2):
        l1 = self.getLuminance(color1)
        l2 = self.getLuminance(color2)
        cr = (l1 + .05)/(l2+.05) if l1 > l2 else (l2+.05)/(l1 + .05)
        return(cr)
     
    def getMostReadable(self, color):
        cr = []
        for c in QColor.colorNames():
            if c == 'transparent':
                continue
            cr.append([self.getContrastRation(color, c), c])
        sorted_cr = sorted(cr, reverse=True)  
        return(sorted_cr[0][1])    
    
def main():
    if len(sys.argv) != 2:
        print("usage: MostReadableColor color_name (ex: 'red')")
    else:   
        mrc = MostReadableColor()
        best_contrast_color = mrc.getMostReadable(sys.argv[1])
        print(f"{best_contrast_color}")
    
if __name__ == "__main__":
    main()

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