如何实现这种“色差”算法?

时间:2014-07-31 20:19:14

标签: c# algorithm colors

作为这个问题的后续问题:(How can I draw legible text on a bitmap (Winforms)?),我通过计算"平均值"来在位图上绘制清晰但小的文字。文本下方的颜色,并为文本选择适当的对比色。

我从https://stackoverflow.com/a/6185448/3784949窃取了Till的代码,用于计算"平均值" bmp颜色。现在我正在查看"色差" http://www.w3.org/TR/AERT#color-contrast建议的算法。

这表明我需要使我的色彩亮度至少为125"单位"更大,我的色差至少要大500个单位,亮度和差异计算如下:

颜色亮度由以下公式确定:

((红色值X 299)+(绿色值X 587)+(蓝色值X 114))/ 1000

色差由以下公式确定:

(最大值(红色值1,红色值2) - 最小值(红色值1,红色值2))+(最大值(绿色值1,绿色值2) - 最小值(绿色值1,绿色值2)) +(最大值(蓝色值1,蓝色值2) - 最小值(蓝色值1,蓝色值2))

我该如何实现?我可以通过ARGB设置我的颜色(我相信,它是标签前景色);但是我如何计算改变每个单独的价值以实现这里所需的差异?我不熟悉打破“差异”所需的数学知识。单位到他们的组成部分。

例如,我的平均水平"对于一个位图是:颜色[A = 255,R = 152,G = 138,B = 129]。我如何"添加"足以让每个部分实现这两个差异?

编辑:具体来说,我的困惑在于:

  1. 看起来我需要添加三个单独的值(R,G,B)来实现两个不同的目标(新RGB加起来加上原来加125,新RGB加起来原来加500

  2. 看起来我可能需要"体重"我添加的亮度值可以比G增加更多的G而不是B.

  3. 我不知道如何解决#1问题。而且我并不乐观,我对#2是正确的。

    编辑:提议的解决方案

    我目前正在尝试这个:

    private Color GetContrastingFontColor(Color AverageColorOfBitmap,
                                          List<Color> FavoriteColors)
    {
        IEnumerable<Color> AcceptableColors =
            (IEnumerable<Color>)FavoriteColors.Where(clr =>
            (GetColorDifferenceAboveTarget(AverageColorOfBitmap, clr, (float)200) > 0)
            && (GetBrightnessAboveTarget(AverageColorOfBitmap, clr, (float).125) > 0))
            .OrderBy(clr => GetColorDifferenceAboveTarget(
                                AverageColorOfBitmap, clr, (float)200));
        return AcceptableColors.DefaultIfEmpty(Color.Aqua).First();
    }
    

    这是一个很好的框架,但我需要努力选择最好的&#34;列表中的候选人。现在它只是返回&#34;具有最大色差的合格色,符合亮度标准&#34;。但是,这允许我修改浮点值(W3&#39; s&#34; 500色差需要&#34;完全废话,零KnownColors合格)和实验。

    支持代码:

    private float GetBrightnessAboveTarget(Color AverageColorOfBitmap, 
                                           Color proposed, float desiredDifference)
    {
        float result = proposed.GetBrightness() - AverageColorOfBitmap.GetBrightness();
        return result - desiredDifference;
    }
    
    private float GetColorDifferenceAboveTarget(Color avg, Color proposed,
                                                float desiredDifference)
    {
        float r1 = Convert.ToSingle(MaxByte(Color.Red, avg, proposed));
        float r2 = Convert.ToSingle(MinByte(Color.Red, avg, proposed));
        float r3 = Convert.ToSingle(MaxByte(Color.Green, avg, proposed));
        float r4 = Convert.ToSingle(MinByte(Color.Green, avg, proposed));
        float r5 = Convert.ToSingle(MaxByte(Color.Blue, avg, proposed));
        float r6 = Convert.ToSingle(MinByte(Color.Blue, avg, proposed));
    
        float result = (r1 - r2) + (r3 - r4) + (r5 - r6);
        return result - desiredDifference;
    }
    
    private byte MaxByte(Color rgb, Color x, Color y)
    {
        if (rgb == Color.Red) return (x.R >= y.R) ? x.R : y.R;
        if (rgb == Color.Green) return (x.G >= y.G) ? x.G : y.G;
        if (rgb == Color.Blue) return (x.B >= y.B) ? x.B : y.B;
        return byte.MinValue;
    }
    
    private byte MinByte(Color rgb, Color x, Color y)
    {
        if (rgb == Color.Red) return (x.R <= y.R) ? x.R : y.R;
        if (rgb == Color.Green) return (x.G <= y.G) ? x.G : y.G;
        if (rgb == Color.Blue) return (x.B <= y.B) ? x.B : y.B;
        return byte.MinValue;
    }
    

2 个答案:

答案 0 :(得分:0)

这是对原始问题的更多回答。我称之为 homemeade outline

使用透明度加上你可以获得的最大和最小亮度(白色和黑色),它可以产生良好的对比度,至少它在我的屏幕上看起来很不错。

它是阴影和透明的混合物。我从红色组件中减去一点来得到你想到的浅绿色......

通过向左打印1个像素和向右打印1个像素,首先创建一个较暗的背景版本。最后,它打印出一个明亮的版本。请注意,它并不是真的使用黑色和白色,因为它的半透明像素的色调实际上是每个背景像素的色调。

对于实际的打印输出,您必须进行实验,尤其是字体,还有两个透明胶片!

此外,您应该在黑色阴影上的白色和白色突出显示的黑色之间切换,具体取决于您打印的点的亮度。但是有了这个主题大纲它真的可以在黑暗和明亮的背景下工作,它在明亮的背景上看起来不那么优雅。

using (Graphics G = Graphics.FromImage(pictureBox1.Image) )
{
  Font F = new Font("Arial", 8);
  SolidBrush brush0 = new SolidBrush(Color.FromArgb(150, 0, 0, 0))
  SolidBrush brush1 = new SolidBrush(Color.FromArgb(200, 255, 255, 222))

  G.DrawString(textBox1.Text, F, brush0 , new Point(x-1, y-1));
  G.DrawString(textBox1.Text, F, brush0 , new Point(x+1, y+1));
  G.DrawString(textBox1.Text, F, brush1, new Point(x, y));
}

编辑:这是通过单击按钮调用的,但实际上应该在paint事件中。 Graphics对象及其使用块G将被简单的e.Graphics事件参数替换。

我注意到您使用'透明'标签来显示数据,以避免Graphics.DrawString和Paint事件的细节。

那可以做到,结果看起来很相似:

string theText ="123 - The quick brown fox..";
Label L1, L2, L3;

pictureBox1.Controls.Add(new trLabel());
L1 = (trLabel)pictureBox1.Controls[pictureBox1.Controls.Count - 1];
L1.Text = theText;
L1.ForeColor = Color.FromArgb(150, 0, 0, 0);
L1.Location = new Point(231, 31);  // <- position in the image, change!

L1.Controls.Add(new trLabel());
L2 = (trLabel)L1.Controls[pictureBox1.Controls.Count - 1];
L2.Text = theText;
L2.ForeColor = Color.FromArgb(150, 0, 0, 0);
L2.Location = new Point(2, 2);  // do not change relative postion in the 1st label!

L2.Controls.Add(new trLabel());
L3 = (trLabel)L2.Controls[pictureBox1.Controls.Count - 1];
L3.Text = theText;
L3.ForeColor = Color.FromArgb(200, 255, 255, 234);
L3.Location = new Point(-1,-1);  // do not change relative postion in the 2nd label!

但是你会注意到,由于在Winforms中使用真正透明控件是不可能的,我们需要额外的努力。你可能使用这样的标签子类:

public partial class trLabel : Label
{
    public trLabel()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
        BackColor = Color.Transparent;
        Visible = true;
        AutoSize = true;
    }
}

这似乎有效。但实际上它似乎只是这样,因为在创建时,每个标签都会从其父标签中获取其当前背景的副本。永远不会更新。这就是为什么我要添加第二个&amp;第3个标签没有到图片框我显示图像,但是第1和第2 分别是'透明'标签。

Winforms控件之间根本没有真正的透明度,除非你自己画画。

因此DrawString解决方案并不复杂。它为您提供了一个奖励,允许您扭曲图形对象的几个属性,如SmoothingmodeTextContrastInterpolationMode

答案 1 :(得分:-1)

简短建议:只使用黑色或白色。

算法为您提供了通过标准,但不是用于确定符合该标准的颜色的算法。因此,您必须创建这样的算法。一个天真的算法是遍历每个可能的颜色,并计算色差,然后看看差异是否大于125,如果是这样,你有一个好的颜色使用。更好的是,你可以搜索最大差异的颜色。

但那是愚蠢的 - 如果我给你的颜色R = 152,G = 138,B = 129 - 你认为什么是一种非常好的颜色来对比呢?只是通过直觉,我会猜测0,0,0。我选择了具有最远R值,G值和B值的颜色。如果你给我的颜色为50,200,75,我会选择R = 255,G = 0,B = 255。同样的逻辑。所以我的算法是如果R <128选择R = 255,否则选择R = 0.对于G和B同样的事情。

现在该算法只选择0或255的RGB值。但是如果你不喜欢它,现在你需要一个“非常”的数学定义,我会让你自己解决这个问题。 。 : - )