【问题标题】:Draw visible text on image regardless of image color无论图像颜色如何,在图像上绘制可见文本
【发布时间】:2015-02-27 00:04:38
【问题描述】:

我想使用 DrawString() 在 24bpp 图像上打印文本。问题是如果我选择白色作为文本颜色,文本在较亮的图像区域几乎不可见。如果我选择红色作为文本颜色,则文本在包含更多红色的图像区域中几乎不可见。以此类推。

我想要实现的是文本在任何情况下都是可见的。我尝试的是使用粗体字体绘制文本,然后使用相同的字体但再次常规绘制文本。但粗体文本略宽,所以这不是解决方案。

但是解决方案是什么?有吗?

谢谢!

【问题讨论】:

  • 在您的情况下是否可以在文本后面绘制一个不透明的白色背景矩形?
  • 我不记得在哪里听到的了,但据我所知,确保文本始终可见的最佳方法是使用带有黑色边框的白色文本。
  • 同一字符串中的颜色可以相差很大吗?或者假设每个字符串一种颜色就足够了是否合理?
  • 最简单的方法是在字符串文本周围制作一个边框(即黑色边框与白色文本 - 在白色 bg 上仅显示黑色轮廓;在黑色 - 仅显示白色)。我不确定绘制字符串是否有此选项,但您始终可以将两个字符串重叠,其中一个略大于另一个字符串以伪造此
  • @LeeHarrison:这不适用于灰度值。 (0x80,0x80,0x80) 的负数是(0x7f,0x7f,0x7f)。然而,有一种方法可以计算最远的颜色。

标签: c# winforms graphics


【解决方案1】:

如果图像没有太大差异,以至于可以假设您将在一个区域中绘制一个非常纯色的字符串,您可以使用以下解决方案。

您可以首先使用一种算法来计算与另一种颜色最不同的颜色,如下所示:

public static byte MostDifferent (byte original) {
    if(original < 0x80) {
        return 0xff;
    } else {
        return 0x00;
    }
}
public static Color MostDifferent (Color original) {
    byte r = MostDifferent(original.R);
    byte g = MostDifferent(original.G);
    byte b = MostDifferent(original.B);
    return Color.FromArgb(r,g,b);
}

现在我们已经完成了,我们必须计算将绘制string 的区域内的平均颜色。您可以在Bitmap 级别执行此操作:

public static unsafe Color AverageColor (Bitmap bmp, Rectangle r) {
    BitmapData bmd = bmp.LockBits (r, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    int s = bmd.Stride;
    int cr = 0;
    int cg = 0;
    int cb = 0;
    int* clr = (int*)(void*)bmd.Scan0;
    int tmp;
    int* row = clr;
    for (int i = 0; i < r.Height; i++) {
        int* col = row;
        for (int j = 0; j < r.Width; j++) {
            tmp = *col;
            cr += (tmp >> 0x10) & 0xff;
            cg += (tmp >> 0x08) & 0xff;
            cb += tmp & 0xff;
            col++;
        }
        row += s>>0x02;
    }
    int div = r.Width * r.Height;
    int d2 = div >> 0x01;
    cr = (cr + d2) / div;
    cg = (cg + d2) / div;
    cb = (cb + d2) / div;
    bmp.UnlockBits (bmd);
    return Color.FromArgb (cr, cg, cb);
}

最后,算法首先测量要绘制字符串的矩形,然后确定最不同的颜色,最后用该颜色绘制字符串:

public static void DrawColorString (this Graphics g, Bitmap bmp, string text, Font font, PointF point) {
    SizeF sf = g.MeasureString (text, font);
    Rectangle r = new Rectangle (Point.Truncate (point), Size.Ceiling (sf));
    r.Intersect (new Rectangle(0,0,bmp.Width,bmp.Height));
    Color brsh = MostDifferent (AverageColor (bmp, r));
    g.DrawString (text, font, new SolidBrush (brsh), point);
}

现在您可以调用该方法,例如:

Bitmap bmp = new Bitmap("Foo.png");
Graphics g = Graphics.FromImage(bmp);
g.DrawColorString (bmp, "Sky", new Font ("Arial", 72.0f), new PointF (600.0f, 150.0f));
g.DrawColorString (bmp, "Sand", new Font ("Arial", 72.0f), new PointF (600.0f, 450.0f));
bmp.Save ("result.jpg");

这个结果例如:

【讨论】:

  • 嗨,太好了,使用了您最不同的方法。很好的帮助,谢谢!
【解决方案2】:

我建议将文本绘制两次(或三次):一次在黑暗中,一次以明亮的颜色,根据字体大小向上和向左分开 1 或 2 个像素。

如果您的字体非常小并且在许多笔划上只有 1 个像素宽,那么使用三重绘图会有所帮助:两个外部的一种颜色(深色)和中间的第三个(明亮)

Random R = new Random();
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
    // using a loop for create random locations..:
    for (int i=0; i< 33; i++)
    {
        Point pt = new Point(R.Next(pictureBox2.ClientSize.Width), 
                             R.Next(pictureBox2.ClientSize.Width));

        e.Graphics.DrawString("Hello World", Font, Brushes.Black, pt.X - 1, pt.Y - 1);
        e.Graphics.DrawString("Hello World", Font, Brushes.Black, pt.X + 1, pt.Y + 1);
        e.Graphics.DrawString("Hello World", Font, Brushes.White, pt.X , pt.Y);
    }
}

这是一个三重绘图的例子

:

(请注意,原版看起来比我在浏览器中看到的要清晰得多。您可能需要下载它来检查..)

如果您的图像可能包含非常多的噪点并且字体必须非常小,那么在文本后面添加纯色背景效果会更好。

【讨论】:

  • 您也可以将黑色文本写为粗体(可能会增加字体),然后是白色字体,将两个调用居中,以便它们正确排列。他们到底是什么照片:车祸?
  • 我不认为排队只适用于几个字符。 OP曾尝试过类似的事情,但角色排列得并不好。 - 不是意外(还);一系列remarkable kludges中的一个
猜你喜欢
  • 2020-09-07
  • 2011-11-11
  • 2022-01-10
  • 1970-01-01
  • 2017-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多