algorithm - Určete barvu písma na základě barvy pozadí

original title: "algorithm - Determine font color based on background color"


Translate

Given a system (a website for instance) that lets a user customize the background color for some section but not the font color (to keep number of options to a minimum), is there a way to programmatically determine if a "light" or "dark" font color is necessary?

I'm sure there is some algorithm, but I don't know enough about colors, luminosity, etc to figure it out on my own.



Vzhledem k systému (například webové stránce), který umožňuje uživateli přizpůsobit barvu pozadí pro určitou sekci, ale ne barvu písma (aby byl počet možností na minimum), existuje způsob, jak programovat ...

Toto je shrnutí po překladu. Pokud potřebujete zobrazit celý překlad, klikněte na ikonu „přeložit“


Všechny odpovědi
  • Translate

    I encountered similar problem. I had to find a good method of selecting contrastive font color to display text labels on colorscales/heatmaps. It had to be universal method and generated color had to be "good looking", which means that simple generating complementary color was not good solution - sometimes it generated strange, very intensive colors that were hard to watch and read.

    After long hours of testing and trying to solve this problem, I found out that the best solution is to select white font for "dark" colors, and black font for "bright" colors.

    Here's an example of function I am using in C#:

    Color ContrastColor(Color color)
    {
        int d = 0;
    
        // Counting the perceptive luminance - human eye favors green color... 
        double luminance = ( 0.299 * color.R + 0.587 * color.G + 0.114 * color.B)/255;
    
        if (luminance > 0.5)
           d = 0; // bright colors - black font
        else
           d = 255; // dark colors - white font
    
        return  Color.FromArgb(d, d, d);
    }
    

    This was tested for many various colorscales (rainbow, grayscale, heat, ice, and many others) and is the only "universal" method I found out.

    Edit
    Changed the formula of counting a to "perceptive luminance" - it really looks better! Already implemented it in my software, looks great.

    Edit 2 @WebSeed provided a great working example of this algorithm: http://codepen.io/WebSeed/full/pvgqEq/


  • Translate

    Just in case someone would like a shorter, maybe easier to understand version of GaceK's answer:

    public Color ContrastColor(Color iColor)
    {
       // Calculate the perceptive luminance (aka luma) - human eye favors green color... 
       double luma = ((0.299 * iColor.R) + (0.587 * iColor.G) + (0.114 * iColor.B)) / 255;
    
       // Return black for bright colors, white for dark colors
       return luma > 0.5 ? Color.Black : Color.White;
    }
    

    Note: I removed the inversion of the luma value (to make bright colors have a higher value, what seems more natural to me and is also the 'default' calculation method.

    I used the same constants as GaceK from here since they worked great for me.

    (You can also implement this as an Extension Method using the following signature:

    public static Color ContrastColor(this Color iColor)
    

    You can then call it via foregroundColor = background.ContrastColor().)


  • Translate

    Thank you @Gacek. Here's a version for Android:

    @ColorInt
    public static int getContrastColor(@ColorInt int color) {
        // Counting the perceptive luminance - human eye favors green color...
        double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
    
        int d;
        if (a < 0.5) {
            d = 0; // bright colors - black font
        } else {
            d = 255; // dark colors - white font
        }
    
        return Color.rgb(d, d, d);
    }
    

    And an improved (shorter) version:

    @ColorInt
    public static int getContrastColor(@ColorInt int color) {
        // Counting the perceptive luminance - human eye favors green color...
        double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
        return a < 0.5 ? Color.BLACK : Color.WHITE;
    }
    

  • Translate

    My Swift implementation of Gacek's answer:

    func contrastColor(color: UIColor) -> UIColor {
        var d = CGFloat(0)
    
        var r = CGFloat(0)
        var g = CGFloat(0)
        var b = CGFloat(0)
        var a = CGFloat(0)
    
        color.getRed(&r, green: &g, blue: &b, alpha: &a)
    
        // Counting the perceptive luminance - human eye favors green color...
        let luminance = 1 - ((0.299 * r) + (0.587 * g) + (0.114 * b))
    
        if luminance < 0.5 {
            d = CGFloat(0) // bright colors - black font
        } else {
            d = CGFloat(1) // dark colors - white font
        }
    
        return UIColor( red: d, green: d, blue: d, alpha: a)
    }
    

  • Translate

    Javascript [ES2015]

    const hexToLuma = (colour) => {
        const hex   = colour.replace(/#/, '');
        const r     = parseInt(hex.substr(0, 2), 16);
        const g     = parseInt(hex.substr(2, 2), 16);
        const b     = parseInt(hex.substr(4, 2), 16);
    
        return [
            0.299 * r,
            0.587 * g,
            0.114 * b
        ].reduce((a, b) => a + b) / 255;
    };
    

  • Translate

    This is such a helpful answer. Thanks for it!

    I'd like to share an SCSS version:

    @function is-color-light( $color ) {
    
      // Get the components of the specified color
      $red: red( $color );
      $green: green( $color );
      $blue: blue( $color );
    
      // Compute the perceptive luminance, keeping
      // in mind that the human eye favors green.
      $l: 1 - ( 0.299 * $red + 0.587 * $green + 0.114 * $blue ) / 255;
      @return ( $l < 0.5 );
    
    }
    

    Now figuring out how to use the algorithm to auto-create hover colors for menu links. Light headers get a darker hover, and vice-versa.


  • Translate

    Thanks for this post.

    For whoever might be interested, here's an example of that function in Delphi:

    function GetContrastColor(ABGColor: TColor): TColor;
    var
      ADouble: Double;
      R, G, B: Byte;
    begin
      if ABGColor <= 0 then
      begin
        Result := clWhite;
        Exit; // *** EXIT RIGHT HERE ***
      end;
    
      if ABGColor = clWhite then
      begin
        Result := clBlack;
        Exit; // *** EXIT RIGHT HERE ***
      end;
    
      // Get RGB from Color
      R := GetRValue(ABGColor);
      G := GetGValue(ABGColor);
      B := GetBValue(ABGColor);
    
      // Counting the perceptive luminance - human eye favors green color...
      ADouble := 1 - (0.299 * R + 0.587 * G + 0.114 * B) / 255;
    
      if (ADouble < 0.5) then
        Result := clBlack  // bright colors - black font
      else
        Result := clWhite;  // dark colors - white font
    end;
    

  • Translate

    Ugly Python if you don't feel like writing it :)

    '''
    Input a string without hash sign of RGB hex digits to compute
    complementary contrasting color such as for fonts
    '''
    def contrasting_text_color(hex_str):
        (r, g, b) = (hex_str[:2], hex_str[2:4], hex_str[4:])
        return '000' if 1 - (int(r, 16) * 0.299 + int(g, 16) * 0.587 + int(b, 16) * 0.114) / 255 < 0.5 else 'fff'
    

  • Translate

    I had the same problem but i had to develop it in PHP. I used @Garek's solution and i also used this answer: Convert hex color to RGB values in PHP to convert HEX color code to RGB.

    So i'm sharing it.

    I wanted to use this function with given Background HEX color, but not always starting from '#'.

    //So it can be used like this way:
    $color = calculateColor('#804040');
    echo $color;
    
    //or even this way:
    $color = calculateColor('D79C44');
    echo '<br/>'.$color;
    
    function calculateColor($bgColor){
        //ensure that the color code will not have # in the beginning
        $bgColor = str_replace('#','',$bgColor);
        //now just add it
        $hex = '#'.$bgColor;
        list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
        $color = 1 - ( 0.299 * $r + 0.587 * $g + 0.114 * $b)/255;
    
        if ($color < 0.5)
            $color = '#000000'; // bright colors - black font
        else
            $color = '#ffffff'; // dark colors - white font
    
        return $color;
    }
    

  • Translate

    An implementation for objective-c

    + (UIColor*) getContrastColor:(UIColor*) color {
        CGFloat red, green, blue, alpha;
        [color getRed:&red green:&green blue:&blue alpha:&alpha];
        double a = ( 0.299 * red + 0.587 * green + 0.114 * blue);
        return (a > 0.5) ? [[UIColor alloc]initWithRed:0 green:0 blue:0 alpha:1] : [[UIColor alloc]initWithRed:255 green:255 blue:255 alpha:1];
    }
    

  • Translate

    iOS Swift 3.0 (UIColor extension):

    func isLight() -> Bool
    {
        if let components = self.cgColor.components, let firstComponentValue = components[0], let secondComponentValue = components[1], let thirdComponentValue = components[2] {
            let firstComponent = (firstComponentValue * 299)
            let secondComponent = (secondComponentValue * 587)
            let thirdComponent = (thirdComponentValue * 114)
            let brightness = (firstComponent + secondComponent + thirdComponent) / 1000
    
            if brightness < 0.5
            {
                return false
            }else{
                return true
            }
        }  
    
        print("Unable to grab components and determine brightness")
        return nil
    }
    

  • Translate

    Swift 4 Example:

    extension UIColor {
    
        var isLight: Bool {
            let components = cgColor.components
    
            let firstComponent = ((components?[0]) ?? 0) * 299
            let secondComponent = ((components?[1]) ?? 0) * 587
            let thirdComponent = ((components?[2]) ?? 0) * 114
            let brightness = (firstComponent + secondComponent + thirdComponent) / 1000
    
            return !(brightness < 0.6)
        }
    
    }
    

    UPDATE - Found that 0.6 was a better test bed for the query


  • Translate

    Flutter implementation

    Color contrastColor(Color color) {
      if (color == Colors.transparent || color.alpha < 50) {
        return Colors.black;
      }
      double luminance = (0.299 * color.red + 0.587 * color.green + 0.114 * color.blue) / 255;
      return luminance > 0.5 ? Colors.black : Colors.white;
    }
    

  • Translate

    If you're manipulating color spaces for visual effect it's generally easier to work in HSL (Hue, Saturation and Lightness) than RGB. Moving colours in RGB to give naturally pleasing effects tends to be quite conceptually difficult, whereas converting into HSL, manipulating there, then converting back out again is more intuitive in concept and invariably gives better looking results.

    Wikipedia has a good introduction to HSL and the closely related HSV. And there's free code around the net to do the conversion (for example here is a javascript implementation)

    What precise transformation you use is a matter of taste, but personally I'd have thought reversing the Hue and Lightness components would be certain to generate a good high contrast colour as a first approximation, but you can easily go for more subtle effects.


  • Translate

    You can have any hue text on any hue background and ensure that it is legible. I do it all the time. There's a formula for this in Javascript on Readable Text in Colour – STW* As it says on that link, the formula is a variation on the inverse-gamma adjustment calculation, though a bit more manageable IMHO. The menus on the right-hand side of that link and its associated pages use randomly-generated colours for text and background, always legible. So yes, clearly it can be done, no problem.


  • Translate

    As Kotlin / Android extension:

    fun Int.getContrastColor(): Int {
        // Counting the perceptive luminance - human eye favors green color...
        val a = 1 - (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255
        return if (a < 0.5) Color.BLACK else Color.WHITE
    }
    

  • Translate

    An Android variation that captures the alpha as well.

    (thanks @thomas-vos)

    /**
     * Returns a colour best suited to contrast with the input colour.
     *
     * @param colour
     * @return
     */
    @ColorInt
    public static int contrastingColour(@ColorInt int colour) {
        // XXX https://stackoverflow.com/questions/1855884/determine-font-color-based-on-background-color
    
        // Counting the perceptive luminance - human eye favors green color...
        double a = 1 - (0.299 * Color.red(colour) + 0.587 * Color.green(colour) + 0.114 * Color.blue(colour)) / 255;
        int alpha = Color.alpha(colour);
    
        int d = 0; // bright colours - black font;
        if (a >= 0.5) {
            d = 255; // dark colours - white font
        }
    
        return Color.argb(alpha, d, d, d);
    }