英文:
I'm consistently getting the incorrect value for a contrast ratio between two colours in a contrast finder in C#
问题
我正在尝试编写一个类库,该类库可以用于接受任何格式的输入颜色,并返回符合 W3 可读性标准的两种颜色中的一种。根据特定情况的字体粗细,我只需要满足 4.5:1 的对比度比例。
为了计算对比度,我正在使用公式 (l1 + 0.0005) / (l2 + 0.0005)
来计算对比度比率。为此,我需要计算 RGB 颜色的亮度,我使用的方法最初基于这个方法,因为它是我编写的 HSL 颜色空间的扩展。
这是用于计算要返回的文本颜色的方法。它的设计是默认颜色为 #121212 和 #EAEAEA,用于浅色或深色背景的文本颜色。然而,如果这两者都没有足够的对比度,那么会再次尝试 #000000 和 #FFFFFF。如果这两者都不起作用,它会默认为黑色。
以下是 FindTextColor
方法的代码:
public static (int R, int G, int B) FindTextColor(int R, G, int B, int darkR = 0x12, int darkG = 0x12, int darkB = 0x12, int lightR = 0xEA, int lightG = 0xEA, int lightB = 0xEA)
{
double bgLuminance = GetLuminanceOfRGB(R, G, B);
double lightTextLuminance = GetLuminanceOfRGB(lightR, lightG, lightB);
double lightContrastRatio = (Math.Max(lightTextLuminance, bgLuminance) + 0.0005) / (Math.Min(lightTextLuminance, bgLuminance) + 0.0005);
double darkTextLuminance = GetLuminanceOfRGB(darkR, darkG, darkB);
double darkContrastRatio = (Math.Max(darkTextLuminance, bgLuminance) + 0.0005) / (Math.Min(darkTextLuminance, bgLuminance) + 0.0005);
if (lightContrastRatio >= 4.5)
{
return (lightR, lightG, lightB);
}
else if (darkContrastRatio >= 4.5)
{
return (darkR, darkG, darkB);
}
else
{
if (darkR != 0x00 && darkG != 0x00 && darkB != 0x00 && lightR != 0xFF && lightG != 0xFF && lightB != 0xFF)
{
return FindTextColor(R, G, B, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF);
}
else
{
return (0, 0, 0);
}
}
}
接下来是 GetLuminanceOfRGB
方法,它使用先前提到的链接中的转换的第一个阶段:
public static double GetLuminanceOfRGB(int R, G, int B)
{
//1,将 1-255 的 RGB 值转换为 0-1 的值
double red = R / (double)255;
double green = G / (double)255;
double blue = B / (double)255;
//2,找到 R、G 和 B 中的最小值和最大值
double minimum = Math.Min(Math.Min(red, green), Math.Min(green, blue));
double maximum = Math.Max(Math.Max(red, green), Math.Max(green, blue));
//3,从最大和最小值计算亮度。
return (double)Math.Ceiling((maximum + minimum) / 2.0 * 100) / 100;
}
这产生了亮度值,形式为 0 到 1 之间的双精度,表示百分比,其中 0 是黑色,1 是白色。
大多数情况下,这种方法对于颜色如 #FFFF00 或 #FF0000 等颜色效果良好。问题出现在蓝色通道具有优先权的情况下。
您遇到问题的颜色是 #3041FF(一种蓝色),其亮度为 59%。虽然这超过了 50%,但与黄色相比,蓝色的阴影要暗得多,尽管理论上它很明亮。
目前,该方法告诉我 #3041FF 与 #EAEAEA 之间的对比度比为大约 1.53,而 此对比度查找器 告诉我对比度比为 5.25。
如果需要,我可以提供 RGB-HSL 转换方法的完整文本。
到目前为止,我已尝试了几种方法,包括更改亮度计算为 L = 0.2126 * R + 0.7152 * G + 0.0722 * B
,但这并未解决问题,只是使浅色的对比度比大约为 11.4,而深色为 11.8,这并未解决问题。
我还尝试使用相对亮度计算方法:
public static double GetRelativeLuminance(double L) {
if (L < 0 || L > 1) {
throw new ArgumentOutOfRangeException("Luminance may not be greater than 1 or less than 0.");
}
double lr = 0;
if (L <= 0.04045)
{
lr = (L + 0.05) / 0.255;
}
else
{
lr = Math.Pow(((L + 0.05) / 1.055), 2.4);
}
return lr;
}
这也没有解决问题,不过回顾一下,我可能在 if 语句中的比例上弄错了。
我还尝试了从原始十六进制输入创建 HSL 颜色并使用这些颜色的
英文:
I am attempting to write a class library that can be used to take an input colour in any format and returns one of two colours that subscribes to the W3 standards for good readability.
Due to the font weight for the specific case, I only need to meet the 4.5:1 ratio for this version.
In order to calculate the contrast I am using the formula (l1 + 0.0005) / (l2 + 0.0005)
to work out the contrast ratio. To do this I need to calculate the luminance of the RGB colour, the method I am using is based originally on this method because it is an expansion to an HSL colour space I have written.
This is the method used to calculate which text colour to return. It's designed to have default colours of #121212 and #EAEAEA as the text colours for light or dark backgrounds respectively. However, if neither of these is good enough contrast, then it tries again with #000000 and #FFFFFF. If neither of these work, it defaults out black.
public static (int R, int G, int B) FindTextColor(int R, int G, int B, int darkR = 0x12, int darkG = 0x12, int darkB = 0x12, int lightR = 0xEA, int lightG = 0xEA, int lightB = 0xEA)
{
double bgLuminance = GetLuminanceOfRGB(R, G, B);
double lightTextLuminance = GetLuminanceOfRGB(lightR, lightG, lightB);
double lightContrastRatio = (Math.Max(lightTextLuminance, bgLuminance) + 0.0005) / (Math.Min(lightTextLuminance, bgLuminance) + 0.0005);
double darkTextLuminance = GetLuminanceOfRGB(darkR, darkG, darkB);
double darkContrastRatio = (Math.Max(darkTextLuminance, bgLuminance) + 0.0005) / (Math.Min(darkTextLuminance, bgLuminance) + 0.0005);
if (lightContrastRatio >= 4.5)
{
return (lightR, lightG, lightB);
}
else if (darkContrastRatio >= 4.5)
{
return (darkR, darkG, darkB);
}
else
{
if (darkR != 0x00 && darkG != 0x00 && darkB != 0x00 && lightR != 0xFF && lightG != 0xFF && lightB != 0xFF)
{
return FindTextColor(R, G, B, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF);
}
else
{
return (0, 0, 0);
}
}
}
The following is the method GetLuminanceOfRGB(), which uses the first stage of the conversion in the previously mentioned link.
public static double GetLuminanceOfRGB(int R, int G, int B)
{
//1, Convert the 1-255 RGB values into 0-1 values
double red = R / (double)255;
double green = G / (double)255;
double blue = B / (double)255;
//2, Find the minimum and maximum values out of R, G and B
double minimum = Math.Min(Math.Min(red, green), Math.Min(green, blue));
double maximum = Math.Max(Math.Max(red, green), Math.Max(green, blue));
//3, Calculate the luminance from the max and minimum.
return (double)Math.Ceiling((maximum + minimum) / 2.0 * 100) / 100;
}
This produces values for the luminance in the form of a double between 0 and 1 which represents a percentage where 0 is black and 1 is white.
Most of the time, this method works fine for colours like #FFFF00 or #FF0000. The issue arises when the blue channel has precidence.
The colour that I am having particular trouble with is #3041FF (A sort of blue) that has a luminance of 59%. While this is above 50%, the blue shades are much darker in appearance than a yellow would be, despite it being theoretically bright.
Currently, the method tells me that the contrast ratio between #3041FF and #EAEAEA is 1.53 approximately, whereas this contrast finder tells me the contrast ratio is 5.25.
I can supply the full text for the RGB-HSL conversion method if requested.
So far I have attempted several things, including changing my luminance to this calculation: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
, which didn't solve the issue, it just made the contrast ratios for light around 11.4 and dark around 11.8, which does not resolve the issue.
I also attempted using relative luminance using this method:
public static double GetRelativeLuminance(double L) {
if (L < 0 || L > 1) {
throw new ArgumentOutOfRangeException("Luminance may not be greater than 1 or less than 0.");
}
double lr = 0;
if (L <= 0.04045)
{
lr = (L + 0.05) / 0.255;
}
else
{
lr = Math.Pow(((L + 0.05) / 1.055), 2.4);
}
return lr;
}
Which also did not resolve the issue, however looking back on it I may have gotten the scale wrong with the if statement.
I have also tried just creating HSL colours from the original hex input and using the luminance of those, just in case I had written the new method wrong in some fashion.
All of this has led to the same result of the code giving me the #121212 font colour, and I'm not sure why any more.
答案1
得分: 0
我最终解决了这个问题。以下是我的结果,希望能帮助其他人。
第一个方法,FindTextColor(),保持不变,不过我会重新发布下面的信息。
该方法计算是否在前景上显示浅色或深色文本颜色,如果没有提供或默认颜色有效,它将使用黑色和白色调用自身。如果因某种原因仍然失败,它将默认返回黑色。它应该永远不需要使用黑色或白色,但这是一个良好的安全措施。
public static (int R, int G, int B) FindTextColor(int R, int G, int B, int darkR = 0x12, int darkG = 0x12, int darkB = 0x12, int lightR = 0xEA, int lightG = 0xEA, int lightB = 0xEA)
{
double bgLuminance = GetLuminanceOfRGB(R, G, B);
double lightTextLuminance = GetLuminanceOfRGB(lightR, lightG, lightB);
double lightContrastRatio = (Math.Max(lightTextLuminance, bgLuminance) + 0.05) / (Math.Min(lightTextLuminance, bgLuminance) + 0.05);
double darkTextLuminance = GetLuminanceOfRGB(darkR, darkG, darkB);
double darkContrastRatio = (Math.Max(darkTextLuminance, bgLuminance) + 0.05) / (Math.Min(darkTextLuminance, bgLuminance) + 0.05);
if (lightContrastRatio >= 4.5)
{
return (lightR, lightG, lightB);
}
else if (darkContrastRatio >= 4.5)
{
return (darkR, darkG, darkB);
}
else
{
if (darkR != 0x00 && darkG != 0x00 && darkB != 0x00 && lightR != 0xFF && lightG != 0xFF && lightB != 0xFF)
{
return FindTextColor(R, G, B, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF);
}
else
{
return (0, 0, 0);
}
}
}
它使用 Math.Max()
和 Math.Min()
以确保我们始终将浅色放在深色上。
最大的变化发生在我对 GetLuminanceOfRGB()
的实现上,导致问题的主要原因是我使用了伽马编码的sRGB,但我需要使用线性RGB。
public static double GetLuminanceOfRGB(int R, int G, B)
{
double red = LineariseChannel(R);
double green = LineariseChannel(G);
double blue = LineariseChannel(B);
return 0.2125 * red + 0.7154 * green + 0.0721 * blue;
}
在这个方法中,我们需要使用线性通道,我最初只在底部的公式中使用sRGB值。
private static double LineariseChannel(int channel)
{
double dbl = channel / 255.0;
if (dbl <= 0.03928)
{
return dbl / 12.92;
}
else
{
return Math.Pow(((dbl + 0.055) / 1.055), 2.4);
}
}
这个方法使用了 Wikipedia 上的公式。这是我需要改变的重要区别。
这三个方法允许计算两种颜色之间的对比度比率。
英文:
I managed to solve the issue eventually. Posting my results below so it may help others.
The first method, FindTextColor() has remained unchanged, however I will repost the information below.
This method calculates whether to display the light or dark text colour on the front, if neither supplied or default colour works, it calls itself with black and white. If this still fails for some reason, it will default to returning black.<br/>
It should never need to use black or white, but it's a good safety net.
public static (int R, int G, int B) FindTextColor(int R, int G, int B, int darkR = 0x12, int darkG = 0x12, int darkB = 0x12, int lightR = 0xEA, int lightG = 0xEA, int lightB = 0xEA)
{
double bgLuminance = GetLuminanceOfRGB(R, G, B);
double lightTextLuminance = GetLuminanceOfRGB(lightR, lightG, lightB);
double lightContrastRatio = (Math.Max(lightTextLuminance, bgLuminance) + 0.05) / (Math.Min(lightTextLuminance, bgLuminance) + 0.05);
double darkTextLuminance = GetLuminanceOfRGB(darkR, darkG, darkB);
double darkContrastRatio = (Math.Max(darkTextLuminance, bgLuminance) + 0.05) / (Math.Min(darkTextLuminance, bgLuminance) + 0.05);
if (lightContrastRatio >= 4.5)
{
return (lightR, lightG, lightB);
}
else if (darkContrastRatio >= 4.5)
{
return (darkR, darkG, darkB);
}
else
{
if (darkR != 0x00 && darkG != 0x00 && darkB != 0x00 && lightR != 0xFF && lightG != 0xFF && lightB != 0xFF)
{
return FindTextColor(R, G, B, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF);
}
else
{
return (0, 0, 0);
}
}
}
It uses Math.Max()
and Math.Min()
in order to ensure that we always put the light colour over the dark.
The biggest change came to my implementation of GetLuminanceOfRGB()
, the main reason for my issues was that I was using gamma encoded sRGB, but I needed to use Linear RGB.
public static double GetLuminanceOfRGB(int R, int G, int B)
{
double red = LineariseChannel(R);
double green = LineariseChannel(G);
double blue = LineariseChannel(B);
return 0.2125 * red + 0.7154 * green + 0.0721 * blue;
}
In this method, we need to use the linear channels, I was originally using just the sRGB values within the formula at the bottom.
private static double LineariseChannel(int channel)
{
double dbl = channel / 255.0;
if (dbl <= 0.03928)
{
return dbl / 12.92;
}
else
{
return Math.Pow(((dbl + 0.055) / 1.055), 2.4);
}
}
This method makes use of the formula available on Wikipedia here. This is the important distinction I needed to change.
These three methods allow the calculation of the contrast ratio between two colours.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论