假设有一个系统(例如网站),它允许用户自定义某些部分的背景颜色,但不允许自定义字体颜色(为了将选项数量最小化),是否有一种编程方式可以确定是否需要“浅色”或“深色”字体颜色?
我相信存在某些算法,但我不了解足够的颜色、亮度等知识来独立解决此问题。
假设有一个系统(例如网站),它允许用户自定义某些部分的背景颜色,但不允许自定义字体颜色(为了将选项数量最小化),是否有一种编程方式可以确定是否需要“浅色”或“深色”字体颜色?
我相信存在某些算法,但我不了解足够的颜色、亮度等知识来独立解决此问题。
我遇到了类似的问题。我需要找到一种选择对比字体颜色以显示颜色标度/热力图上的文本标签的好方法。它必须是通用的方法,并且生成的颜色必须是“好看的”,这意味着简单地生成补色不是一个好的解决方案 - 有时会生成奇怪、非常强烈的颜色,很难观看和阅读。
经过长时间的测试和尝试解决这个问题,我发现最好的解决方案是为“深色”颜色选择白色字体,为“浅色”颜色选择黑色字体。
这里是我在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);
}
这经过了对多种不同的配色方案进行测试(彩虹、灰度、热、冰等),是我发现的唯一“通用”的方法。
编辑
把计算a
的公式改为“感知亮度” - 看起来更好!已在我的软件中实现,效果很棒。
编辑2 @WebSeed提供了一个这个算法的工作示例:http://codepen.io/WebSeed/full/pvgqEq/
基于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;
}
注: 我删除了亮度值的倒置,使得明亮的颜色具有更高的值,这对我来说更自然,并且也是“默认”的计算方法。(参见此链接)。
(编辑:原回答中已采用此方法。)
我使用了与此处的Gacek相同的常量,因为它们对我非常有效。
你还可以使用以下签名将其实现为扩展方法:
public static Color ContrastColor(this Color iColor)
然后,您可以通过以下方式轻松调用它
foregroundColor = backgroundColor.ContrastColor()
。
谢谢@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);
}
并且有一个改进后(更短)的版本:
@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;
}
1-...
部分并将a
重命名为luminance
),那么它将变得更短,更易于阅读。 - Ridcully我对 Gacek 回答的 Swift 实现:
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)
}
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;
};
计算给定颜色的亮度(Y),并根据预先确定的中等对比度数值将文本翻转为黑色或白色。对于典型的sRGB显示器,当Y < 0.36(即36%)时翻转为白色。
编辑添加:这个问题经常被问到,我在https://github.com/Myndex/max-contrast上设置了一个准备好的JS库。
就是这样:
// Simple: send it sRGB values 0-255.
// If Ys is > 0.342 it returns 'black' otherwise returns 'white'
// See https://github.com/Myndex/max-contrast
function maxContrast (Rs = 164, Gs = 164, Bs = 164) {
const flipYs = 0.342; // based on APCA™ 0.98G middle contrast BG
const trc = 2.4, Rco = 0.2126729, Gco = 0.7151522, Bco = 0.0721750; // 0.98G
let Ys = (Rs/255.0)**trc*Rco + (Gs/255.0)**trc*Gco + (Bs/255.0)**trc*Bco;
return Ys < flipYs ? 'white' : 'black'
}
毫不奇怪,几乎每个回答都存在一些误解,和/或引用了错误的系数。唯一一个接近正确的答案是Seirios的回答,尽管它依赖于已知不正确的WCAG 2对比度。
如果我说“毫不奇怪”,部分原因是因为互联网上关于这个特定主题的大量错误信息。事实上,这个领域仍然是活跃研究和未解决科学问题的主题,这增加了乐趣。我得出这个结论是基于过去几年对可读性的新对比度预测方法的研究结果。
视觉感知领域既密集又抽象,同时也在发展中,所以误解是常见的。例如,HSV和HSL甚至不接近感知准确。为了达到这一点,你需要一个感知均匀的模型,如CIELAB或CIELUV或CIECAM02等。
一些误解甚至已经进入了标准,比如WCAG 2(1.4.3)的对比度部分,在其大部分范围内已被证明是不正确的。
将每个sRGB颜色除以255.0,然后乘以2.2的幂,再乘以系数并求和,以找到估计的亮度。
let Ys = Math.pow(sR/255.0,2.2) * 0.2126 +
Math.pow(sG/255.0,2.2) * 0.7152 +
Math.pow(sB/255.0,2.2) * 0.0722; // Andy's Easy Luminance for sRGB. For Rec709 HDTV change the 2.2 to 2.4
但在我们继续之前,如果你只想要一个基本的将文本从黑色翻转为白色或反之的方法,那么诀窍就是使用我们刚刚得出的Y,并将翻转点设置为Y = 0.36;
。所以对于大于0.36 Y的颜色,将文本设置为黑色#000
,对于比0.36 Y更暗的颜色,将文本设置为白色#fff
。
let textColor = (Ys < 0.36) ? "#fff" : "#000"; // Low budget down and dirty text flipper.
let textColor = (Math.pow(Ys,0.678) < 0.5) ? "#fff" : "#000"; // perceptual text flipper.
let textSize = "16px";
let textWeight = "normal";
let Ls = Math.pow(Ys,0.678);
if (Ls > 0.33 && Ls < 0.66) {
textSize = "18px";
textWeight = "bold";
} // scale up fonts for the lower contrast mid luminances.
在这篇文章中,我们不会深入探讨色调和色度,因为这超出了本文的范围。然而,色调和色度确实会产生影响,比如Helmholtz Kohlrausch,而上面简单的亮度计算并不能总是准确预测饱和色调的强度。
要预测这些更微妙的感知方面,需要一个完整的外观模型。R. Hunt, M. Fairshild, E. Burns是一些值得研究的作者,如果你想深入了解人类视觉感知的话...
对于这个狭窄的目的,我们可以稍微重新调整系数的权重,知道绿色占据了大部分亮度,纯蓝色和纯红色应该始终是两种颜色中最暗的。使用标准系数时,中间带有大量蓝色或红色的颜色可能会在较低的亮度下变成黑色,而具有高绿色成分的颜色可能会相反。
话虽如此,我发现最好的解决方法是增加中间颜色的字体大小和粗细。
所以我们假设你会向这个函数发送一个十六进制字符串,它将返回一个可以发送到特定HTML元素的样式字符串。
看看这个受Seirios启发的CODEPEN。Codepen的代码之一是为低对比度中间范围增加文本大小。
一个更完整的演示。
如果你想玩弄一些基本概念,可以查看SAPC development网站,点击“研究模式”可以进行交互式实验,以演示这些概念。
亮度: Y(相对)或 L(绝对cd/m2)是光的光谱加权但线性度量。不要与“亮度”混淆。
亮度:随时间变化的光,对天文学有用。
明度: L*(Lstar)是由CIE定义的感知明度。一些模型有一个相关的明度J*。
如果你不想写Python,可以使用丑陋的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'
感谢这篇文章。
对于可能感兴趣的读者,以下是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;
这是非常有帮助的答案。谢谢!
我想分享一个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 );
}
Flutter 实现
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;
}