根据背景颜色确定字体颜色

Jos*_*gle 229 language-agnostic algorithm colors

给定一个系统(例如网站),允许用户自定义某些部分的背景颜色而不是字体颜色(以保持选项数量最少),有没有办法以编程方式确定"光"或"黑暗的"字体颜色是必要的?

我确定有一些算法,但我对颜色,光度等知之甚少,无法自己解决.

Gac*_*cek 429

我遇到过类似的问题.我必须找到一种选择对比字体颜色的好方法,以在colorscales/heatmaps上显示文本标签.它必须是通用的方法,生成的颜色必须是"好看的",这意味着简单的生成互补色不是好的解决方案 - 有时它产生奇怪的,非常密集的颜色,难以观看和阅读.

经过长时间的测试并试图解决这个问题,我发现最好的解决方案是选择白色字体为"深色",黑色字体为"亮"色.

这是我在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);
}
Run Code Online (Sandbox Code Playgroud)

这是为许多不同的颜色(彩虹,灰度,热,冰和许多其他颜色)测试的,并且是我发现的唯一"通用"方法.

编辑
将计数公式更改a为"感知亮度" - 它看起来确实更好!已经在我的软件中实现它,看起来很棒.

编辑2 @WebSeed提供了一个很好的算法实例:http://codepen.io/WebSeed/full/pvgqEq/

  • CodePen上述算法的例子:http://codepen.io/WebSeed/full/pvgqEq/ (20认同)
  • 它可能并不重要,但你可能想要一个更好的功能来计算亮度http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color (14认同)

Mar*_*orf 17

万一有人想要更简短,更容易理解的GaceK答案版本:

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;
}
Run Code Online (Sandbox Code Playgroud)

注意:我删除了亮度值的反转(使明亮的颜色具有更高的值,对我来说似乎更自然,也是'默认'计算方法.

我从这里使用与GaceK相同的常量,因为它们对我很有用.

(您也可以使用以下签名将其实现为扩展方法:

public static Color ContrastColor(this Color iColor)
Run Code Online (Sandbox Code Playgroud)

然后你可以通过它调用它foregroundColor = background.ContrastColor().)


Myn*_*dex 13

简短回答:

\n

计算给定颜色的亮度 (Y),并根据预先确定的中间对比度数字将文本翻转为黑色或白色。对于典型的 sRGB 显示屏,当 Y < 0.36(即 36%)时翻转为白色

\n

编辑添加:这个问题经常被问到,我在https://github.com/Myndex/max-contrast设置了一个带有现成 JS 的存储库

\n

是这样的:

\n
    // Simple: send it sRGB values 0-255.\n   // If Ys is > 0.342 it returns \'black\' otherwise returns \'white\'\n  // See  https://github.com/Myndex/max-contrast \n\n\nfunction maxContrast (Rs = 164, Gs = 164, Bs = 164) {\n  \n  const flipYs = 0.342; // based on APCA\xe2\x84\xa2 0.98G middle contrast BG\n\n  const trc = 2.4, Rco = 0.2126729, Gco = 0.7151522, Bco = 0.0721750; // 0.98G\n\n  let Ys = (Rs/255.0)**trc*Rco + (Gs/255.0)**trc*Gco + (Bs/255.0)**trc*Bco; \n\n  return Ys < flipYs ? \'white\' : \'black\'\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更长的答案

\n

毫不奇怪,这里几乎每个答案都存在一些误解,和/或引用了不正确的系数。唯一真正接近的答案是Seirios的答案,尽管它依赖于 WCAG 2 对比度,而众所周知,该对比度本身是不正确的。

\n

如果我说“不足为奇”,部分原因是互联网上关于这个特定主题的大量错误信息。事实上,这个领域仍然是一个活跃的研究主题,而且科学尚未确定,这增加了乐趣。我得出这个结论是过去几年对一种新的可读性对比度预测方法的研究结果。

\n

视觉感知领域是密集而抽象的,而且是不断发展的,因此存在误解是很常见的。例如,HSV 和 HSL 与感知准确度相差甚远。为此,您需要一个感知统一的模型,例如 CIELAB 或 CIELUV 或 CIECAM02 等。

\n

一些误解甚至已经进入了标准,例如 WCAG 2 (1.4.3) 的对比部分,它已被证明在其大部分范围内是不正确的。

\n

第一个修复:

\n

这里许多答案中显示的系数是 (.299, .587, .114) 并且是错误的,因为它们属于一个长期过时的系统,称为 NTSC YIQ,这是几十年前北美的模拟广播系统。虽然它们仍可能在某些 YCC 编码规范中用于向后兼容,但它们不应该在 sRGB 上下文中使用

\n

sRGB 和 Rec.709 (HDTV) 的系数为:

\n
    \n
  • 红色:0.2126
  • \n
  • 绿色:0.7152
  • \n
  • 蓝色:0.0722
  • \n
\n

其他色彩空间(例如 Rec2020 或 Adob​​eRGB)使用不同的系数,对于给定色彩空间使用正确的系数非常重要。

\n

这些系数不能直接应用于 8 位 sRGB 编码图像或颜色数据。编码数据必须首先被线性化,然后应用系数来查找给定像素或颜色的亮度(光值)。

\n

对于 sRGB,存在分段变换,但由于我们只对感知的亮度对比度感兴趣,以找到将文本从黑色“翻转”为白色的点,因此我们可以通过简单的伽玛方法采取捷径。

\n

安迪的亮度和亮度捷径

\n

将每种 sRGB 颜色除以 255.0,然后计算 2.2 次方,然后乘以系数并求和以找到估计亮度。

\n
 let cieY = Math.pow(sR/255.0,2.2) * 0.2126 +\n          Math.pow(sG/255.0,2.2) * 0.7152 +\n          Math.pow(sB/255.0,2.2) * 0.0722; // Andy\'s Easy Standard Luminance for sRGB. For Rec709 HDTV change the 2.2 to 2.4\n
Run Code Online (Sandbox Code Playgroud)\n

此处,Y是sRGB 显示器的相对亮度,范围为 0.0 到 1.0。但这与感知无关,我们需要进一步的转换以适应人类对相对亮度和感知对比度的视觉感知。

\n

36% 的翻转

\n

但在我们到达那里之前,如果您只是寻找将文本从黑色翻转为白色或反之亦然的基本点,则作弊方法是使用我们刚刚导出的Y,并将翻转点设为Y = 0.36;。因此,对于高于 0.36 Y 的颜色,将文本设置为黑色#000,对于比 0.36 Y 更深的颜色,将文本设置为白色#fff

\n
  let textColor = (cieY < 0.36) ? "#fff" : "#000"; // Low budget down and dirty text flipper.\n
Run Code Online (Sandbox Code Playgroud)\n

为什么是 36% 而不是 50%?我们人类对明/暗和对比度的感知不是线性的。对于自发光显示器,在大多数典型条件下,0.36 Y 恰好约为中等对比度。

\n

是的,它有所不同,是的,这过于简单化了。但如果您要将文本翻转为黑色或白色,那么简单的答案是有用的。(最佳值可能在 ~34% 和 ~42% 之间变化,具体取决于环境条件和刺激空间特征)。

\n

感性奖金轮

\n

预测对给定颜色和亮度的感知仍然是一个活跃的研究主题,而不是完全确定的科学。CIELAB 或 LUV 的 L* (Lstar) 已被用来预测感知亮度,甚至预测感知对比度。然而,L* 对于非常定义/受控环境中的表面颜色效果很好,而对于自发光显示器则效果不佳。

\n

虽然这不仅取决于显示器类型和校准,还取决于您的环境和整个页面内容,但如果您从上面获取 Y,并将其提高大约 ^0.66 到 ^0.7,您会发现 0.5 是大约中间点将文本从白色翻转为黑色。

\n
  let textColor = (Math.pow(cieY,0.678) < 0.5) ? "#fff" : "#000"; // perceptual text flipper.\n
Run Code Online (Sandbox Code Playgroud)\n

使用指数 0.6 将使文本颜色交换为较深的颜色,使用指数 0.8 将使文本颜色交换为较浅的颜色。

\n

空间频率双倍奖金回合

\n

值得注意的是,对比度不仅仅是两种颜色之间的距离。空间频率,即字体粗细和大小,也是不可忽视的关键因素。

\n

也就是说,您可能会发现当颜色处于中间范围时,您需要增加字体的大小和/或粗细。

\n
  let textSize = "16px";\n  let textWeight = "normal"; \n  let Ls = Math.pow(cieY,0.678);\n\n  if (Ls > 0.33 && Ls < 0.66) {\n      textSize = "18px";\n      textWeight = "bold";\n      }  // scale up fonts for the lower contrast mid luminances.\n
Run Code Online (Sandbox Code Playgroud)\n

顺化茹

\n

深入研究超出了本文的范围,但上面我们忽略了色调和色度。色调和色度确实有影响,例如亥姆霍兹科尔劳施(Helmholtz Kohlrausch),并且上面更简单的亮度计算并不总是能预测由于饱和色调而导致的强度。

\n

为了预测感知的这些更微妙的方面,需要一个完整的外观模型。如果你想深入了解人类视觉感知的兔子洞,R. Hunt、M. Fairshild、E. Burns 等几位值得研究的作者……

\n

出于这个狭隘的目的,我们可以稍微重新加权系数,因为知道绿色构成了亮度的大部分,而纯蓝色和纯红色应该始终是两种颜色中最暗的。使用标准系数往往会发生的情况是,具有大量蓝色或红色的中间颜色可能会在低于理想亮度的情况下翻转为黑色,而具有高绿色分量的颜色可能会发生相反的情况。

\n

也就是说,我发现最好通过增加中间颜色的字体大小和粗细来解决这个问题。

\n

把它们放在一起

\n

因此,我们假设您将向此函数发送一个十六进制字符串,并且它将返回一个可以发送到特定 HTML 元素的样式字符串。

\n

查看受 Seirios 所做的启发的 CODEPEN。Codepen 代码所做的事情之一是增加较低对比度中频的文本大小。

\n

更新:新的花式字体翻转GitHub 存储库

\n

更完整的演示器。

\n

样品

\n

这是一个翻转点为 cieY 42 的示例,该值太高了:\n翻转点太高的精美字体翻转器示例

\n

这是翻转点为 cieY 36 的示例:\n带翻转点 Ys 36 的精美字体翻转器示例

\n

如果您想尝试一些底层概念,请参阅SAPC 开发站点,单击“研究模式”提供交互式实验来演示这些概念。

\n

启蒙条款

\n
    \n
  • 亮度: Y(相对)或L(绝对cd/m 2)是光谱加权但线性的光测量值。不要与“亮度”混淆。

    \n
  • \n
  • 光度:随时间变化的光,在天文学中有用。

    \n
  • \n
  • 亮度: CIE 定义的L * (Lstar) 感知亮度。某些型号具有相关的亮度 J *

    \n
  • \n
\n


Tho*_*Vos 11

谢谢@Gacek.这是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);
}
Run Code Online (Sandbox Code Playgroud)

一个改进的(更短)版本:

@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;
}
Run Code Online (Sandbox Code Playgroud)


Vit*_*eca 9

我的Swift实施Gacek的答案:

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)
}
Run Code Online (Sandbox Code Playgroud)


mat*_*fin 8

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;
};
Run Code Online (Sandbox Code Playgroud)


Jos*_*oco 7

如果你不想写它,那么丑陋的 Python :)

'''
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'
Run Code Online (Sandbox Code Playgroud)


Ric*_* D. 6

谢谢你的这篇文章.

对于可能感兴趣的人,这是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;
Run Code Online (Sandbox Code Playgroud)


小智 5

这是一个非常有用的答案.谢谢!

我想分享一个SCSS版本:

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

}
Run Code Online (Sandbox Code Playgroud)

现在弄清楚如何使用该算法自动创建菜单链接的悬停颜色.灯头变得更暗,反之亦然.