【问题标题】:How do I implement this "color difference" algorithm?如何实现这种“色差”算法?
【发布时间】:2014-07-31 20:19:14
【问题描述】:

作为此问题的后续:(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 比 R 多于 B。

我不知道如何解决 #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();
}

这是一个很好的框架,但我需要努力从列表中选择“最佳”候选人。现在它只是返回“符合亮度标准的具有最大色差的合格颜色”。但是,这允许我修改浮点值(W3 的“需要 500 色差”完全是废话,零 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;
}

【问题讨论】:

  • 链接文档中颜色亮度范围为125,差异范围为500。
  • OT,但只有当你的背景相当同质时,整个想法才能很好地发挥作用。我宁愿打印两次,黑色和白色,偏移量为 1,1。对比色看起来很糟糕,你需要的是亮度对比。亮度最好通过color.GetBrightness() (0f-1f) 的系统调用来计算,最好的结果(在最小尺寸下可读)是提供背景。最不显眼的方法是用相同的颜色打印更大的、半透明的字母,但比背景更亮或更暗。..
  • 我的意思是你的值颠倒了,所以你不能得到 500 单位的亮度增量。在试图找出一种修改颜色的方法时,我感到很困惑。
  • @Jared 哦!接得好。我会解决的。
  • @TaW - 请记住,我只是在计算自动调整大小标签后面的位图部分的颜色。到目前为止,我的测试表明,在一张大照片(现代相机照片)的如此小区域中存在相当多的同质性。我从 SO 中采样了一些不同的“绘制轮廓文本”,但是对于小字体,它在照片上不是很清晰 - 部分问题是(正如我在下面对 Moby 提到的)黑色根本不会出现在大范围内照片。

标签: c# algorithm colors


【解决方案1】:

这更像是对原始问题的回答。我称之为自制大纲

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

它是阴影和透明度的混合体。我从红色部分中减去了一点,得到了你想的水色。

它首先通过打印左上 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));
}

编辑:这是通过单击按钮调用的,但实际上应该在绘制事件中。 在那里,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;
    }
}

这似乎有效。但实际上它似乎只是这样,因为在创建时,每个标签都会从其父级获取其当前背景的副本。永远不会更新。这就是为什么我必须将第二个和第三个标签添加到我显示图像的图片框中,而不是添加到第一个和第二个 分别为“透明”标签。

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

所以 DrawString 解决方案并不复杂。它还为您提供了额外的好处,允许您扭曲 Graphics 对象的多个属性,例如 SmoothingmodeTextContrastInterpolationMode

【讨论】:

  • 我很感兴趣,我想对此进行测试,但到目前为止,我一直在我的图片框上放置一个透明标签,并设置前景色。为了避免必须创建图形对象和处理绘制消息等:有没有办法覆盖标签的文本绘制以使用此代码绘制文本?
  • 在这种情况下,G 是什么?还有刷01?看起来可能缺少一些代码?
  • 我需要知道你为brush01做了什么。我假设 G 是您的图形上下文,但没有 Brush01,我无法使其清晰可见。
  • 对不起,在床上 ;-) Brush01:那只是一个错字,只有两个画笔。请查看我编辑的答案。我添加了一个带有标签和一些注释的版本。
  • 嗯,很漂亮,我给你。我认为这对于大型图形横幅等非常有用。我将您的代码放入 picturbox 绘制消息中并进行了实验。但是在常规大小的文本中,它不够清晰。我不得不玩这个很多。出于某种原因,文本与我的照片一起缩放 - 知道为什么吗?在最大的照片尺寸下,我仍然必须让字体超过 50 磅才能绘制到我估计为 18 磅的位置。此外,由于某种原因,PictureBox 在首次加载后没有收到绘画消息,我必须调整窗口大小才能让它绘画。
【解决方案2】:

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

这些算法为您提供了一个通过标准,但不是一个用于确定哪些颜色符合该标准的算法。因此,您将不得不创建这样的算法。一个简单的算法是遍历所有可能的颜色,并计算颜色差异,然后查看差异是否大于 125,如果是,那么你有一个好的颜色可以使用。更好的是,您可以搜索差异最大的颜色。

但那太愚蠢了——如果我给你颜色 R=152、G=138、B=129——你认为什么颜色可以用来对比?凭直觉,我猜是0,0,0。我选择了 R 值、G 值和 B 值尽可能远的颜色。如果你给我颜色 50,200,75,我会选择 R=255,G=0,B=255。同样的逻辑。所以我的算法是如果 R

现在该算法只选择 0 或 255 的 RGB 值。但如果你不喜欢这样,现在你需要一个数学定义来定义什么是“漂亮”,我会让你自己弄清楚. :-)

【讨论】:

  • 我在第一次测试我的位图颜色计算时确实实现了一个黑白算法。它有两个问题:白色文本不如深色背景上的 Aqua 或黄色文本漂亮,而且我测试的大多数黑白算法大部分时间都返回黑色,这在许多照片上都难以辨认。最终,客户想要透明背景上的颜色,所以我愿意花一些时间来实现它。请记住,我不太可能只使用“计算出的差异”颜色;我会进行计算,然后从接近的“已知颜色”列表中进行选择。
  • 黑色是否符合色差标准?如果是这样,为什么它难以辨认?我想知道关于使用“平均”颜色的假设是否不是一个好的假设。如果图像有黑色区域和白色区域怎么办?其他人建议使用阴影是一种更好的通用解决方案,因为无论图像颜色如何,它都可以工作。
  • 我使用了这里建议的两种 bw 算法:24ways.org/2010/calculating-color-contrast。它选择黑色的时间令人惊讶(可能是因为许多客户的照片似乎是户外场景,而不是红色而不是贪婪)。但有两个问题:9 点文本(允许的最大)根本不能很好地勾勒/阴影。下面评论中的第二个问题(这个太长了)。
  • 第二个问题:照片经常在两个维度上都没有填满整个屏幕,这意味着照片的侧面或上方有一个黑色区域。元数据文本通常至少部分位于该黑色区域中。我意识到这是一个设计选择,但现在,我正在使用它。所以黑色文本很少是一件好事,这就是为什么我仍然倾向于像 Aqua 和 Yellow 这样已知的明亮颜色。
  • 如果文字在照片两侧或上方的黑色区域,那么文字下方的“平均”颜色不应该是黑色吗?那么算法不应该选择白色吗?
猜你喜欢
  • 2014-03-03
  • 1970-01-01
  • 1970-01-01
  • 2011-07-28
  • 2013-08-27
  • 2013-11-16
  • 1970-01-01
  • 1970-01-01
  • 2020-09-08
相关资源
最近更新 更多