如何通过编程计算两种颜色之间的对比度比率?

111

非常简单,取黄色和白色:

back_color = {r:255,g:255,b:255}; //white
text_color = {r:255,g:255,b:0}; //yellow

在上帝地球的普遍恒量定律中,是什么导致黄色文本无法在白色背景上阅读,但蓝色文本可以?

为了我的可定制小部件,我尝试了所有我找到的颜色模型以及它们的转换函数;然而仅仅通过数值比较,我都无法确定绿色是否能够显示在白色背景上而黄色不能。

我查看了广告联盟 Adsense(由互联网之佛创造),你知道他们做了什么吗?他们创建了预设和颜色单元距离计算。而我无法这样做。我的用户有权选择最引起视网膜炎、不美观的组合,只要文本仍然可以阅读。


所以您的用户可以选择任意两种颜色,只要它们具有明显的对比度? - DanRedux
从技术角度来看,我觉得这很有趣。但更实际的是,如果您的用户有“权利”选择任何颜色,为什么您还要关心它是否可以被阅读?不是他们自己负责确保正确吗? - nnnnnn
@nnnnnn 我并不在意他们选择什么颜色,他们可以混合任何颜色,但我关心的是 (c) 2012 公司名称 的可读性。 - Silviu-Marian
1
如果有人错过了ShaoKahn的回复,这是链接 - Silviu-Marian
如果您不想自己计算,可以向WebAIM的API请求:您发送两种颜色,然后接收不同可访问级别的对比度比率和通过/失败。 - lars k.
显示剩余4条评论
7个回答

154
根据维基百科,将彩色图像转换为灰度图像时,“必须获取其红、绿和蓝的值”,并按以下比例混合:R:30% G:59% B:11%
因此,白色将具有100%的亮度,黄色将具有89%的亮度。同时,绿色的亮度仅为59%。11%几乎比41%低了四倍!
即使是酸橙色(#00ff00)也不适合阅读大量文本。
在我看来,为了良好的对比度,颜色的亮度应该至少相差50%。而这种亮度应该以转换为灰度后的形式进行测量。

更新:最近在网上找到了一个全面的工具,它使用了来自w3文档的公式。 阈值可以从#1.4中获取。 这里是更高级实现的代码。

const RED = 0.2126;
const GREEN = 0.7152;
const BLUE = 0.0722;

const GAMMA = 2.4;

function luminance(r, g, b) {
  var a = [r, g, b].map((v) => {
    v /= 255;
    return v <= 0.03928
      ? v / 12.92
      : Math.pow((v + 0.055) / 1.055, GAMMA);
  });
  return a[0] * RED + a[1] * GREEN + a[2] * BLUE;
}

function contrast(rgb1, rgb2) {
  var lum1 = luminance(...rgb1);
  var lum2 = luminance(...rgb2);
  var brightest = Math.max(lum1, lum2);
  var darkest = Math.min(lum1, lum2);
  return (brightest + 0.05) / (darkest + 0.05);
}

console.log(contrast([255, 255, 255], [255, 255, 0])); // 1.074 for yellow
console.log(contrast([255, 255, 255], [0, 0, 255])); // 8.592 for blue

// note: minimal recommended contrast ratio is 4.5, or 3 for larger font-sizes

如需了解更多信息,请查看 WCAG 2.0文档,了解如何计算此值。


4
没错。在实际生活中,将设计转换为灰度以检查可读性是必须的(特别是对于标志)。 - Roko C. Buljan
我会选择这个答案作为解决方案,因为它最全面,而且我不能选择两个解决方案;但如果可以选择两个解决方案的话,我会选择HondaSuzukiShaolinShaorma的答案,因为它提供了可直接使用的代码。 - Silviu-Marian
14
contrast([255, 255, 255], [0, 0, 255]) 的返回值为8.592,而将这些数字反过来 contrast([0, 0, 255], [255, 255, 255]) 的返回值为0.116,即1/8.592。如果要获得1到21之间的对比度比率,需要在输出小于1时将1除以输出。下面是相应的函数代码:function contrast(rgb1, rgb2) { var result = (luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05); if (result < 1) result = 1/result; return result; } - mattsoave
3
这里有一个小错误:return (luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05);。您需要用较大的值除以较小的值。更好的做法是:l1=luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05; l2=luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05; return (Math.max(l1,l2) / Math.min(l1,l2)); - zuluk
2
@zuluk,你说得完全正确。已编辑。https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-procedure - Bryan Rayner
显示剩余4条评论

34

有多种方法可以计算对比度,但常见的一种方式是使用以下公式:

brightness = (299*R + 587*G + 114*B) / 1000
你需要对两种颜色都这样做,然后取它们的差。显然,蓝色在白色背景上与黄色相比更具对比度。

4
仅供参考,显然这篇文章来自W3C。链接为:https://trendct.org/2016/01/22/how-to-choose-a-label-color-to-contrast-with-background/ 和 https://www.w3.org/TR/AERT#color-contrast - Cody Moniz
8
仅供参考,上面显示的系数(299R + 587G + 114B)与NTSC有关,在Web颜色中已经过时/不正确,此时应使用sRGB。适当的sRGB系数为0.2126R + 0.7152G + 0.0722B(请注意,对于sRGB,R G B分量必须首先线性化)。 - Myndex

5

const getLuminanace = (values) => {
  const rgb = values.map((v) => {
    const val = v / 255;
    return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
  });
  return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3));
};

const getContrastRatio = (colorA, colorB) => {
  const lumA = getLuminanace(colorA);
  const lumB = getLuminanace(colorB);

  return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
};

// usage:
const back_color = [255,255,255]; //white
const text_color = [255,255,0]; //yellow

getContrastRatio(back_color, text_color); // 1.0736196319018405


2

根据Kirilloid的答案:

Angular服务,通过传入十六进制值来计算对比度和亮度:

.service('ColorContrast', [function() {
var self = this;

/**
 * Return iluminance value (base for getting the contrast)
 */
self.calculateIlluminance = function(hexColor) {
    return calculateIluminance(hexColor);
};

/**
 * Calculate contrast value to white
 */
self.contrastToWhite = function(hexColor){
    var whiteIlluminance = 1;
    var illuminance = calculateIlluminance(hexColor);
    return whiteIlluminance / illuminance;
};

/**
* Bool if there is enough contrast to white
*/
self.isContrastOkToWhite = function(hexColor){
    return self.contrastToWhite(hexColor) > 4.5;
};

/**
 * Convert HEX color to RGB
 */
var hex2Rgb = function(hex) {
    var 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;
};

/**
 * Calculate iluminance
 */
var calculateIlluminance = function(hexColor) {
    var rgbColor = hex2Rgb(hexColor);
    var r = rgbColor.r, g = rgbColor.g, b = rgbColor.b;
    var a = [r, g, b].map(function(v) {
        v /= 255;
        return (v <= 0.03928) ?
            v / 12.92 :
            Math.pow(((v + 0.055) / 1.055), 2.4);
    });
    return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
};

}]);

2
最近,我在这个页面上看到了一个答案,并使用该代码制作了一个Adobe Illustrator脚本来计算对比度比率。
这里是结果:http://screencast.com/t/utT481Ut
该脚本中的某些速记符号对我来说很困惑,并且在Adobe Extend Script中无法工作。因此,我认为分享我对kirilloid分享的代码的改进/解释会很好。
function luminance(r, g, b) {
    var colorArray = [r, g, b];
    var colorFactor;
    var i;
    for (i = 0; i < colorArray.length; i++) {
        colorFactor = colorArray[i] / 255;
        if (colorFactor <= 0.03928) {
            colorFactor = colorFactor / 12.92;
        } else {
            colorFactor = Math.pow(((colorFactor + 0.055) / 1.055), 2.4);
        }
        colorArray[i] = colorFactor;
    }
    return (colorArray[0] * 0.2126 + colorArray[1] * 0.7152 + colorArray[2] * 0.0722) + 0.05;
}

当然,您需要调用此函数。在for循环中,我从我的illustrator对象获取所有颜色。
//just a snippet here to demonstrate the notation
var selection = app.activeDocument.selection;
for (i = 0; i < selection.length; i++) {
   red[i] = selection[i].fillColor.red;
   //I left out the rest,because it would become to long
}

//this can then be used to calculate the contrast ratio.
var foreGround = luminance(red[0], green[0], blue[0]);
var background = luminance(red[1], green[1], blue[1]);
luminanceValue = foreGround / background;
luminanceValue = round(luminanceValue, 2);

//for rounding the numbers I use this function:
function round(number, decimals) {
   return +(Math.round(number + "e+" + decimals) + "e-" + decimals);
}

对比度比率的更多信息:http://webaim.org/resources/contrastchecker/


0
module.exports = function colorcontrast (hex) {
    var color = {};

    color.contrast = function(rgb) {
        // check if we are receiving an element or element background-color
        if (rgb instanceof jQuery) {
            // get element background-color
            rgb = rgb.css('background-color');
        } else if (typeof rgb !== 'string') {
            return;
        }

        // Strip everything except the integers eg. "rgb(" and ")" and " "
        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';
    };

    // Return public interface
    return color;

};

这看起来不错,但为什么你的 colorcontrast 方法接收一个 hex 参数,但是这个参数并没有被使用? - ctlockey

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接