根据对比度调整颜色

Adjust color based on contrast

对于我正在开发的 mod,我想合并播放器的主题颜色并使用它们生成 UI 元素。但是,我 运行 遇到了一个问题,即并非所有颜色主题都具有提供 good contrast ratio as outlined in 1.4.3 Contrast (Minimum) of Web Content Accessibility Guidelines (WCAG) 2.1.

的颜色

我目前可以使用以下方法检查对比度:

float RelativeLuminance(Color color)
{
    float ColorPartValue(float part)
    {
        return part <= 0.03928f ? part / 12.92f : Mathf.Pow((part + 0.055f) / 1.055f, 2.4f);
    }
    var r = ColorPartValue(color.r);
    var g = ColorPartValue(color.g);
    var b = ColorPartValue(color.b);

    var l = 0.2126f * r + 0.7152f * g + 0.0722f * b;
    return l;
}

private float ColorContrast(Color a, Color b)
{
    float result = 0f;
    var La = RelativeLuminance(a) + 0.05f;
    var Lb = RelativeLuminance(b) + 0.05f;

    result = Mathf.Max(La, Lb) / Mathf.Min(La, Lb);

    return result;
}

我使用找到的颜色对比度来确定初始文本颜色是否足够好。

public Color GetContrastingColors(Color backgroundColor, Color textColor)
{
    Color contrastColor;

    // See if we have good enough contrast already
    if (!(ColorContrast(backgroundColor, textColor) < 4.5f))
    {
        return textColor;
    }

    Color.RGBToHSV(textColor, out var textH, out var textS, out var textV);
    Color.RGBToHSV(backgroundColor, out var bgH, out var bgS, out var bgV);

    // Modify textV by some value to provide enough contrast.

    contrastColor = Color.HSVToRGB(textH, textS, textV);

    return contrastColor;
}

但是,我不确定如何调整颜色以使文本颜色变亮(或变暗)到足以达到 4.5:1 的对比度。最初,我正在考虑将亮度和对比度方程的代数计算到 sRGB 值乘以某个值 X 的点。不过我记得 HSV,调整颜色的亮度似乎更简单我。问题是,我不确定如何比较 2 种 HSV 颜色的对比度,更不用说使用它们的值来操纵颜色的亮度以达到所需的对比度。

我目前的想法是做这样的蠢事:

float targetL;
bool brighter = false;
var backL = RelativeLuminance(backgroundColor);
var textL = RelativeLuminance(textColor);
var ratio = 4.5f;

// Try to go in the direction of brightness originally.
if (textL > backL)
{
    targetL = ((backL + 0.05f) * ratio) - 0.05f;
    brighter = true;

    if (targetL > 1f)
    {
        targetL = ((backL + 0.05f) / ratio) - 0.05f;
        brighter = false;
    }
}
else
{
    targetL = ((backL + 0.05f) / ratio) - 0.05f;
    if (targetL > 0f)
    {
        targetL = ((backL + 0.05f) * ratio) - 0.05f;
        brighter = true;
    }
}

Color adjustedColor = textColor;

while ((!brighter && textL > targetL) || (brighter && textL < targetL))
{
    Color.RGBToHSV(adjustedColor, out var textH, out var textS, out var textV);

    textV += brighter ? 0.01f : -0.01f;

    adjustedColor = Color.HSVToRGB(textH, textS, textV);
    textL = RelativeLuminance(adjustedColor);
}

contrastColor = adjustedColor;

但这并不是很有效,那么我怎样才能操纵文本颜色以使其“保持相同”但提供足够的对比度?

编辑:

为了让我尝试做的事情有更多背景,假设我有以下 4 种颜色作为玩家的主题。

根据 HTML 代码,即:

#32263d #3d1c70
#7347b6 #320d68

我想在为他们创建 UI 时从他们的主题中加入其中 2 种颜色。然而,并不是所有的都容易区分,你可以在这里看到这种情况下的各种对比:

现在每个主题都包含较深和较浅的颜色,就像本例中的中间 2 行一样,但也像本例一样,最终用户可能无法始终阅读它们的对比度。继续这个例子,在这种情况下,我们将使用 #32263d#7347b6 来构建我们的 UI.

虽然我可以尝试随机创建一个相似的紫色阴影,但我想让它尽可能接近原始颜色并使其变亮。我们可以在这里看到它在各种光线下的样子:

如果我们将 #7347b6 设置为 #a163ff 处的最大亮度,我们现在得到以下对:

虽然比以前好,但这仍然只是 3.88 : 1 的对比度。所以现在我想按比例缩小 #32263d 的亮度。如果我们将它减少到 #251B2D,那么我们最终会得到:

这两种新颜色的颜色对比度为 4.51 : 1。

现在,我可以手动浏览每个主题,但考虑到它们的数量,我更愿意编写一个算法来动态生成更新的颜色。

查看我对

的回答

你可以跳过我谈论对比度公式的部分,因为你已经知道了,但我谈论的是如何调整颜色以获得更好的对比度。

如果我真的根据之前的答案编写我的建议,我会更有效率,而不是从每个 RGB 分量中加或减 1 并重新计算亮度,我可能会 add/subtract 10 并重新计算.如果反差不够,再做10次。一旦我获得足够的对比度,我就可以重新调整值,也许每次调整 2,在相反的方向,直到我接近 4.5 而不会下降。

毕竟我的代码最终使用了一个循环。虽然 的答案接近我想要的,但我发现将所有 RGB 分量调整相同的量并不是我想要的,因为它实际上影响了色调,所以我最终改用了 HSV。

public Color[] GetContrastingColors(Color backgroundColor, Color textColor, float ratio)
{
    Color[] colors = new Color[2];

    var backL = RelativeLuminance(backgroundColor);
    var textL = RelativeLuminance(textColor);

    if (textL > backL)
    {
        colors[0] = textColor;
        colors[1] = backgroundColor;
    }
    else
    {
        colors[1] = textColor;
        colors[0] = backgroundColor;
    }

    // See if we have good enough contrast already
    if (!(ColorContrast(backgroundColor, textColor) < ratio))
    {
        return colors;
    }

    Color.RGBToHSV(colors[0], out var lightH, out var lightS, out var lightV);
    Color.RGBToHSV(colors[1], out var darkH, out var darkS, out var darkV);

    // If the darkest color can be darkened enough to have enough contrast after brightening the color.
    if (ColorContrast(Color.HSVToRGB(darkH, darkS, 0f), Color.HSVToRGB(lightH, lightS, 1f)) >= ratio)
    {
        var lightDiff = 1f - lightV;
        var darkDiff = darkV;

        var steps = new float[] { 0.12f, 0.1f, 0.08f, 0.05f, 0.04f, 0.03f, 0.02f, 0.01f, 0.005f };
        var step = 0;

        var lightRatio = (lightDiff / (lightDiff + darkDiff));
        var darkRatio = (darkDiff / (lightDiff + darkDiff));

        while (ColorContrast(Color.HSVToRGB(lightH, lightS, lightV), Color.HSVToRGB(darkH, darkS, darkV)) < ratio)
        {
            while (ColorContrast(Color.HSVToRGB(lightH, lightS, lightV + lightRatio * steps[step]), Color.HSVToRGB(darkH, darkS, darkV - darkRatio * steps[step])) > ratio && step < steps.Length - 1)
            {
                step++;
            }
            lightV += lightRatio * steps[step];
            darkV -= darkRatio * steps[step];
        }

        colors[0] = Color.HSVToRGB(lightH, lightS, lightV);
        colors[1] = Color.HSVToRGB(darkH, darkS, darkV);
    }
    // Fall back to using white.
    else
    {
        colors[0] = Color.white;

        while (ColorContrast(Color.white, Color.HSVToRGB(darkH, darkS, darkV)) < ratio)
        {
            darkV -= 0.01f;
        }

        colors[1] = Color.HSVToRGB(darkH, darkS, darkV);
    }

    return colors;
}