Automatically change text color to assure readabil

2019-01-16 14:30发布

问题:

Users can set the background-color of a button through a textbox that accept RGB hexadecimal notation: ff00ff, ccaa22, etc. So I need to set the text color to the opposite. Not sure about the terminology (opposite color) but the idea is assure readability.

回答1:

You can invert the background color and use it as foreground color. The following algorithm produces results identical to the "Image > Adjustments > Invert" color command in Photoshop:

function invertColor(hexTripletColor) {
    var color = hexTripletColor;
    color = color.substring(1);           // remove #
    color = parseInt(color, 16);          // convert to integer
    color = 0xFFFFFF ^ color;             // invert three bytes
    color = color.toString(16);           // convert to hex
    color = ("000000" + color).slice(-6); // pad with leading zeros
    color = "#" + color;                  // prepend #
    return color;
}
/*
 * Demonstration
 */
function randomColor() {
    var color;
    color = Math.floor(Math.random() * 0x1000000); // integer between 0x0 and 0xFFFFFF
    color = color.toString(16);                    // convert to hex
    color = ("000000" + color).slice(-6);          // pad with leading zeros
    color = "#" + color;                           // prepend #
    return color;
}
$(function() {
    $(".demolist li").each(function() {
        var c1 = randomColor();
        var c2 = invertColor(c1);
        $(this).text(c1 + " " + c2).css({
            "color": c1,
            "background-color": c2
        });
    });
});
body { font: bold medium monospace; }
.demolist { margin: 0; padding: 0; list-style-type: none; overflow: hidden; }
.demolist li { float: left; width: 5em; height: 5em; text-align: center; }
<ul class="demolist">
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Note that this is not a bullet-proof solution. Colors that are close to 50% brightness and/or saturation will not produce sufficient contrast.

Demo on jsFiddle



回答2:

Salaman's code is good but sometimes his inversion is not readable enough. I use YCbCr and just change gray scale.

function invertColor(rgb) {
    var yuv = rgb2yuv(rgb);
    var factor = 180;
    var threshold = 100;
    yuv.y = clamp(yuv.y + (yuv.y > threshold ? -factor : factor));
    return yuv2rgb(yuv);
}

jsfiddle demo



回答3:

I linked another Question to this topic in the comments.
JS function to calculate complementary colour?

As Tejasva said you need to need to convert RGB to HSL, complement the Hue and convert it back.

I implemented the linked answer as a sample. Please upvote the original poster if this was helpful for you, because they provided the solution in fact.

Sample
http://jsfiddle.net/pLZ89/2/



回答4:

I had the same issue once, and gathered information all over the internet also using Salman A answer, I came up with this function, it supports hex, rgb and rgba

var invertColor = function (color) {
            var hex   = '#';
            if(color.indexOf(hex) > -1){
                color = color.substring(1);           
                color = parseInt(color, 16);         
                color = 0xFFFFFF ^ color;            
                color = color.toString(16);           
                color = ("000000" + color).slice(-6); 
                color = "#" + color; 
            }else{
                color = Array.prototype.join.call(arguments).match(/(-?[0-9\.]+)/g);
                for (var i = 0; i < color.length; i++) {
                    color[i] = (i === 3 ? 1 : 255) - color[i];
                }
                if(color.length === 4){
                    color = "rgba("+color[0]+","+color[1]+","+color[2]+","+color[3]+")";
                }else{
                    color = "rgb("+color[0]+","+color[1]+","+color[2]+")";
                }
            }         
            return color;
        }

but I don't think this is what you need, I found something more interesting, the below function will return white or black, it will decide witch one is more readable on the given color.

var getContrastYIQ = function (color){
            var hex   = '#';
            var r,g,b;
            if(color.indexOf(hex) > -1){
                r = parseInt(color.substr(0,2),16);
                g = parseInt(color.substr(2,2),16);
                b = parseInt(color.substr(4,2),16);
            }else{
                color = color.match(/\d+/g);
                r = color[0];
                g = color[1];
                b = color[2];
            }

            var yiq = ((r*299)+(g*587)+(b*114))/1000;
            return (yiq >= 128) ? 'black' : 'white';
        }

I don't take credit for any of this, I just got inspired and modified to my needs.

Sources: YIQ function explained



回答5:

A bit late to the party, but IMO all text should either be light or dark. Colored text is for links.

Here's a coffeescript function I've written to decide which to use:

is_light = (hex_color) ->
  c = hex_color.replace(/^#/,'')
  sum = parseInt(c[0]+c[1], 16)
  sum += parseInt(c[2]+c[3], 16)
  sum += parseInt(c[4]+c[5], 16)  
  log "sum is #{sum}"
  sum > 382.6


回答6:

var getContrastYIQ = function(color) {
  var hex = '#';
  var r, g, b;
  if (color.indexOf(hex) > -1) {
    r = parseInt(color.substr(1, 2), 16);
    g = parseInt(color.substr(3, 2), 16);
    b = parseInt(color.substr(5, 2), 16);
  } else {
    color = color.match(/\d+/g);
    r = color[0];
    g = color[1];
    b = color[2];
  }

  var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
  return (yiq >= 128) ? 'black' : 'white';
}

Thanks for the post ZetCoby! Had to adjust your "color.substr(" array position to account for the initial '#'; then it worked great! you could also use a color.replace('#',''); within the if block...



回答7:

You could use this simple schema to achieve that goal. Just convert the color from RGB to HSV form. You can use this link . Then use this pseudo code;

rr = (color>>16)&0xFF;
gg = (color>>8)&0xFF;
bb = color & 0xFF;

someBrightColor = 0xFFFFFF;
someDarkColor = 0x000000;

hsvColor = rgbToHsv( rr, gg, bb );
//
//hsv is array: [h,s,v]...all values in [0,1]
//
//color is from dark range, if hsv < 0.5, so we need bright color to draw text, because    in dark color bright color 'will be more visible'.  
if( hsvColor[2] <= 0.5 )
  textColor = someBrightColor ;
//this is opposite case , when in more bright color, the dark color 'will be more visible'
else
  textColor = someDarkColor ;

Also you could divide [0,1] range into more parts. And instead of defining 2 colors (someBrightColor,someDarkColor) , define more colors. My suggested method is 'how bright is background color , thas text color must be dark, and vice versa'.



回答8:

Here is a very small way to complement the hex value //hex value like "aa00cc"

function complementHex(hexValue){
    var reqHex = "";
    for(var i=0;i<6;i++){
        reqHex = reqHex + (15-parseInt(hexValue[i],16)).toString(16);
    }
    return reqHex;
}