根据对比度调整颜色

问题描述 投票:0回答:3

对于我正在开发的 mod,我想合并玩家的主题颜色并使用它们来生成 UI 元素。但是,我遇到了一个问题,即并非所有颜色主题都具有提供 良好对比度 的颜色,如 Web 内容可访问性指南 (WCAG) 2.11.4.3 对比度(最小值) 中所述。 .

我目前可以通过以下方式检查对比度:

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 种颜色作为玩家的主题。

4 different shades of purple.

用HTML代码来说,那就是:

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

在为他们创建 UI 时,我想合并他们主题中的 2 种颜色。然而,并非所有这些都很容易区分,您可以在这里看到本例中的各种对比:

4 different shades of purple in rows with different numbers across each row in each of the other colors, indicating that colors contrast with it.

现在每个主题都包含较深和较浅的颜色,就像本示例中的中心两行一样,但也像本示例一样,它们的对比度可能并不总是可供最终用户阅读。按照示例进行操作,在本例中,我们将使用

#32263d
#7347b6
来构建我们的 UI。

The two colors side by side.

虽然我可以尝试随机创建类似的紫色阴影,但我想使其尽可能接近原始颜色并使其变亮。我们可以在这里看到它在不同光线水平下的外观:

A gradient showing the various levels of lighting for #7347b6.

如果我们将

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

#32263d and #a163ff side by side.

虽然比以前好,但对比度仍然只有 3.88 : 1。所以现在我想缩小

#32263d
的亮度。如果我们将其减少到
#251B2D
,我们最终会得到这样的结果:

#251B2D and #a163ff side by side.

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

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

c# unity-game-engine colors accessibility
3个回答
0
投票

查看我的答案 调整给定的颜色对以遵守 ePub 的 W3C 可访问性标准

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

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


0
投票

我最终在我的代码中使用了循环。虽然 slugolicious 的答案很接近我想要的,但我发现将所有 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;
}

0
投票

我也不喜欢使用HSV,所以我将willuwontu的算法变成了通用算法,并添加了保留backgroundColor和textColor的功能。我还固定了步长:

static double stepsize = 0.01;
public static Color[] GetContrastingColors<ColorModel>(Color backgroundColor, Color textColor, double ratio, ChangingMode changingMode) where ColorModel : IContrastAdjustable<ColorModel>
 {
     Color[] colors = new Color[2];
     colors[0] = backgroundColor;
     colors[1] = textColor;

     Color[] lightAndDarkAsColors = new Color[2];

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

     bool preferChangingLight = false;
     bool darkFixed, lightFixed;
     bool textIsLight = textL > backL;

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

     ColorModel verydark, verylight;

     //decide which colors we change
     {
         double rw, rb;
         if (changingMode == ChangingMode.ForegroundFixed || changingMode == ChangingMode.BackgroundFixed)
         {  
             //if one color is fixed, high contrast can be achieve by increasing or by decreasing the Luminosity of the other Color Jochla
             //so we test if we can achieve a good result while keeping the dark color dark and the light color light 

             lightAndDarkAsColors[0] = textIsLight ? textColor : backgroundColor;
             lightAndDarkAsColors[1] = textIsLight ? backgroundColor : textColor;

             Color colorFixed = changingMode == ChangingMode.ForegroundFixed ? textColor : backgroundColor;
             Color colorChangeable = changingMode == ChangingMode.ForegroundFixed ? backgroundColor : textColor; 
             rw = ColorContrast(Color.White, colorFixed);
             rb = ColorContrast(colorFixed, Color.Black);
             preferChangingLight = rw > rb;

             if ((!(preferChangingLight ^ (changingMode == ChangingMode.ForegroundFixed))) == textIsLight)
             {
                

                 lightFixed = !(changingMode == ChangingMode.ForegroundFixed ^ textIsLight); //^Xor
                 darkFixed = !(changingMode == ChangingMode.BackgroundFixed ^ textIsLight); //^Xor


                 light = ColorModel.FromColor(lightAndDarkAsColors[0]);
                 dark = ColorModel.FromColor(lightAndDarkAsColors[1]);

                 verydark = dark.Clone();
                 verylight = light.Clone();
                 if (!lightFixed)
                 {
                     verylight.Luminosity = ColorModel.MaxLuminosity;
                 }
                 if (!darkFixed)
                 {
                     verydark.Luminosity = ColorModel.MinLuminosity;
                 }
                 if (ColorContrast(ColorModel.ToColor(verydark), ColorModel.ToColor(verylight)) >= ratio)
                 {
                     var lightDiff = 1 - light.Luminosity;
                     var darkDiff = dark.Luminosity;

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

                     while (ColorContrast(ColorModel.ToColor(light), ColorModel.ToColor(dark)) < ratio)
                     {
                         if (!lightFixed)
                         {
                             light.Luminosity += lightRatio * stepsize;
                         }
                         if (!darkFixed)
                         {
                             dark.Luminosity -= darkRatio * stepsize;
                         }
                     }
                     lightAndDarkAsColors[0] = ColorModel.ToColor(light);
                     lightAndDarkAsColors[1] = ColorModel.ToColor(dark);
                     colors[0] = textIsLight ? lightAndDarkAsColors[1] : lightAndDarkAsColors[0];
                     colors[1] = textIsLight ? lightAndDarkAsColors[0] : lightAndDarkAsColors[1];
                     return colors;
                 }
             }
             textIsLight = changingMode == ChangingMode.BackgroundFixed ? preferChangingLight : !preferChangingLight;
             lightAndDarkAsColors[0] = preferChangingLight ? colorChangeable : colorFixed;
             lightAndDarkAsColors[1] = preferChangingLight ? colorFixed : colorChangeable; 
             darkFixed = preferChangingLight;
             lightFixed = !preferChangingLight;

         }else
         {
             lightAndDarkAsColors[0] = textIsLight ? textColor : backgroundColor;
             lightAndDarkAsColors[1] = textIsLight ? backgroundColor : textColor;
             darkFixed = false;
             lightFixed = false;
         }
     }

     light = ColorModel.FromColor(lightAndDarkAsColors[0]);
     dark = ColorModel.FromColor(lightAndDarkAsColors[1]);

     verydark = dark.Clone();
     verylight = light.Clone();

     if (!lightFixed)
     {
         verylight.Luminosity = ColorModel.MaxLuminosity;
     }
     if (!darkFixed)
     {
         verydark.Luminosity = ColorModel.MinLuminosity;
     }
     // If the darkest and the lightest we can achieve is enough
     if (ColorContrast(ColorModel.ToColor(verydark), ColorModel.ToColor(verylight)) >= ratio)
     {
         var lightDiff = 1 - light.Luminosity;
         var darkDiff = dark.Luminosity;

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

         while (ColorContrast(ColorModel.ToColor(light), ColorModel.ToColor(dark)) < ratio)
         {
             if (!lightFixed)
             {
                 light.Luminosity += lightRatio * stepsize;
             }
             if (!darkFixed)
             {
                 dark.Luminosity -= darkRatio * stepsize;
             }
         }
         lightAndDarkAsColors[0] = ColorModel.ToColor(light);
         lightAndDarkAsColors[1] = ColorModel.ToColor(dark);
     }
     else
     {
         double rw, rb;
         switch (changingMode)
         {
             //if we prefer white we use white l
             case ChangingMode.PreferWhite:
                 lightAndDarkAsColors[0] = Color.White;

                 //Can we achieve any better than black and white?
                 if (ColorContrast(Color.White, ColorModel.ToColor(verydark)) < ratio)
                 {
                     lightAndDarkAsColors[1] = Color.Black; 
                 }else
                 {
                     while (ColorContrast(Color.White, ColorModel.ToColor(dark)) < ratio)
                     {
                         dark.Luminosity -= 0.01;
                     }

                     lightAndDarkAsColors[1] = ColorModel.ToColor(dark);
                 }
                 break;
             //if we prefer black we use black 
             case ChangingMode.PreferBlack:
                 lightAndDarkAsColors[1] = Color.Black;

                 //Can we achieve any better than black and white?
                 if (ColorContrast(ColorModel.ToColor(verylight), Color.Black) < ratio)
                 {
                     lightAndDarkAsColors[0] = Color.White;
                 }else
                 {
                     while (ColorContrast(ColorModel.ToColor(light), Color.Black) < ratio)
                     {
                         light.Luminosity += 0.01;
                     }

                     lightAndDarkAsColors[0] = ColorModel.ToColor(light);
                 }
                 break;
             case ChangingMode.ForegroundFixed:
                 colors[0] = preferChangingLight ? Color.White : Color.Black;
                 colors[1] = textColor;
                 return colors;

             case ChangingMode.BackgroundFixed:
                 colors[0] = backgroundColor;
                 colors[1] = preferChangingLight ? Color.White : Color.Black;
                 return colors;
         }
     }
     colors[0] = textIsLight ? lightAndDarkAsColors[1] : lightAndDarkAsColors[0];
     colors[1] = textIsLight ? lightAndDarkAsColors[0] : lightAndDarkAsColors[1];
     return colors;
 }

    public enum ChangingMode
    {
        PreferWhite,
        PreferBlack,
        BackgroundFixed,
        ForegroundFixed
    }
© www.soinside.com 2019 - 2024. All rights reserved.