GDI+在基线上绘制不同大小的文本存在偏差 1px 的问题

发布于 2025-01-01 10:22:14 字数 3880 浏览 5 评论 0原文

我需要打印数字,其中通过增加字体大小和粗细来强调中间的一些数字。在下面的示例中,强调了 456

在此处输入图像描述

用户可以配置所使用的字体和两种尺寸。

当前代码使用对 Graphics.DrawString(...) 的三次调用来完成此操作。

我遇到的问题是,对于大多数字体,我看到了 1 个像素的问题(相对于灰线,456 比其他数字高出一个额外的像素):

在此处输入图像描述

我在帖子底部附加了一些针对各种字体的调试转储(鲍勃·鲍威尔公式)。其他技术也产生了类似的结果。

为了在公共基线上打印文本,需要计算特定Font的基线偏移量。我尝试过使用三种技术:

首先,MSDN的代码: http://msdn.microsoft.com/en-us/library/xwf9s90b(v=vs.80).aspx

ascent = fontFamily.GetCellAscent(FontStyle.Regular);
ascentPixel = font.Size * ascent / fontFamily.GetEmHeight(FontStyle.Regular)

其次,代码来自:使用 GDI+,最简单的方法是什么沿公共基线对齐文本(以几种不同的字体绘制)?

最后,来自 Bob Powell 帖子的代码:在公共基线上设置文本格式。

这是我的画法方法:

private void DrawOnBaseline(Graphics g, string text, FontWithBaseline fwb, Brush brush, float x, float y) {
  g.DrawString(text, fwb.Font, brush, x, y - fwb.Baseline, StringFormat.GenericTypographic);
}

其中 FontWithBaseline 只是将字体与其各自的基线计算相关联:

public class FontWithBaseline {
  private Font m_font;
  private float m_baseline;

  public FontWithBaseline(Font font) {
    m_font = font;
    m_baseline = CalculateBaseline(font);
  }

  public Font Font { get { return m_font; } }
  public float Baseline { get { return m_baseline; } }

  private static float CalculateBaseline(Font font) {
    ... // I've tried the three formulae here.
  }
}

我还没有尝试过Graphics.TestRenderingHint 还没有。这就是魔法酱吗? 我缺少什么?是否有我可以使用的替代 API,我可以在其中调用绘图提供基线的 Y 坐标?

在此处输入图像描述


更新 1

我使用 @LarsTech 插入了我的代码。他正在做一件略有不同的事情;他添加了 0.5f。然而,即使这个变体也不能解决问题。这是代码:

protected override void OnPaint(PaintEventArgs e) {
  base.OnPaint(e);
  TryLarsTechnique(e);
}

private void TryLarsTechnique(PaintEventArgs e) {
  base.OnPaint(e);
  Graphics g = e.Graphics;
  GraphicsContainer c = g.BeginContainer();
  g.Clear(Color.White);
  g.SmoothingMode = SmoothingMode.AntiAlias;
  g.TextRenderingHint = TextRenderingHint.AntiAlias;
  Font small = new Font("Arial", 13, FontStyle.Regular, GraphicsUnit.Pixel);
  Font large = new Font("Arial", 17, FontStyle.Bold, GraphicsUnit.Pixel);

  int x = 100;
  int y = 100;
  x += DrawLars(g, "12.3", small, x, y);
  x += DrawLars(g, "456", large, x, y);
  x += DrawLars(g, "8", small, x, y);
  g.EndContainer(c);
}

// returns width of text
private int DrawLars(Graphics g, string text, Font font, int x, int y) {
  float offset = font.SizeInPoints /
                 font.FontFamily.GetEmHeight(font.Style) *
                 font.FontFamily.GetCellAscent(font.Style);
  float pixels = g.DpiY / 72f * offset;
  int numTop = y - (int)(pixels + 0.5f);      
  TextRenderer.DrawText(g, text, font, new Point(x, numTop), Color.Black, Color.Empty, TextFormatFlags.NoPadding);
  return TextRenderer.MeasureText(g, text, font, Size.Empty, TextFormatFlags.NoPadding).Width;
}

我想知道使用 GraphicsUnit.Pixel 指定字体大小是否是罪魁祸首。也许有一种方法可以找到任何特定字体的首选尺寸?


更新 2

我尝试以点而不是像素指定字体大小,但这也不能完全解决问题。请注意,在我的情况下,仅使用整点大小不是一个选择。为了看看这是否可行,我在 Windows 写字板上尝试了这一点。尺寸 使用 96 dpi(定义为每英寸 72 点),17px、13px 转换为 12.75 和 9.75。以下是比较的输出:

在此处输入图像描述

请注意较小的字体在像素级别的高度如何相同。因此,写字板以某种方式成功地做到了这一点,而无需将字体大小四舍五入到方便的值。

I need to print numbers where some of the digits in the middle are emphasized by increasing the font size and weight. In the example below, 456 is emphasized.

enter image description here

The font and the two sizes used are user-configurable.

The current code does this using three calls to Graphics.DrawString(...).

The problem I am having is that with most fonts, I am seeing off-by-1pixel problems (relative to the gray line, the 456 is sitting an extra pixel higher that the other digits):

enter image description here

I've attached some debugging dump (of the Bob Powell formula) for various fonts at the bottom of my post. The other techniques yielded similar results.

In order to print text on a common baseline, one needs to calculate the baseline offset for a particular Font. I've tried using three techniques:

First, MSDN's code: http://msdn.microsoft.com/en-us/library/xwf9s90b(v=vs.80).aspx

ascent = fontFamily.GetCellAscent(FontStyle.Regular);
ascentPixel = font.Size * ascent / fontFamily.GetEmHeight(FontStyle.Regular)

Second, code from: Using GDI+, what's the easiest approach to align text (drawn in several different fonts) along a common baseline?

Finally, code from Bob Powell's post: Formatting text on a common baseline.

Here's my draw method:

private void DrawOnBaseline(Graphics g, string text, FontWithBaseline fwb, Brush brush, float x, float y) {
  g.DrawString(text, fwb.Font, brush, x, y - fwb.Baseline, StringFormat.GenericTypographic);
}

Where FontWithBaseline simply associates a Font with its respective baseline calculation:

public class FontWithBaseline {
  private Font m_font;
  private float m_baseline;

  public FontWithBaseline(Font font) {
    m_font = font;
    m_baseline = CalculateBaseline(font);
  }

  public Font Font { get { return m_font; } }
  public float Baseline { get { return m_baseline; } }

  private static float CalculateBaseline(Font font) {
    ... // I've tried the three formulae here.
  }
}

I have not experimented with Graphics.TestRenderingHint yet. Is that the magic sauce?
What am I missing? Is there an alternative API I can use, where I call the call to draw supplies the baseline's Y coordinate?

enter image description here


Update 1

I interpolated my code with @LarsTech. He was doing one subtly different; he was adding a 0.5f. However, even this variant does not fix the issue. Here's the code:

protected override void OnPaint(PaintEventArgs e) {
  base.OnPaint(e);
  TryLarsTechnique(e);
}

private void TryLarsTechnique(PaintEventArgs e) {
  base.OnPaint(e);
  Graphics g = e.Graphics;
  GraphicsContainer c = g.BeginContainer();
  g.Clear(Color.White);
  g.SmoothingMode = SmoothingMode.AntiAlias;
  g.TextRenderingHint = TextRenderingHint.AntiAlias;
  Font small = new Font("Arial", 13, FontStyle.Regular, GraphicsUnit.Pixel);
  Font large = new Font("Arial", 17, FontStyle.Bold, GraphicsUnit.Pixel);

  int x = 100;
  int y = 100;
  x += DrawLars(g, "12.3", small, x, y);
  x += DrawLars(g, "456", large, x, y);
  x += DrawLars(g, "8", small, x, y);
  g.EndContainer(c);
}

// returns width of text
private int DrawLars(Graphics g, string text, Font font, int x, int y) {
  float offset = font.SizeInPoints /
                 font.FontFamily.GetEmHeight(font.Style) *
                 font.FontFamily.GetCellAscent(font.Style);
  float pixels = g.DpiY / 72f * offset;
  int numTop = y - (int)(pixels + 0.5f);      
  TextRenderer.DrawText(g, text, font, new Point(x, numTop), Color.Black, Color.Empty, TextFormatFlags.NoPadding);
  return TextRenderer.MeasureText(g, text, font, Size.Empty, TextFormatFlags.NoPadding).Width;
}

I am wondering whether specifying font size using GraphicsUnit.Pixel is the culprit. Perhaps there is a way to find on the preferred sizes for any particular font?


Update 2

I've tried to specify font sizes in Points instead of Pixels, and this doesn't fully solve the problem, either. Note that using just whole point sizes is not an option in my case. To see if this is possible, I tried this on Windows Wordpad. Sizes Using 96 dpi (and 72 points per inch by definition), 17px, 13px translate to 12.75 and 9.75. Here's the output compared:

enter image description here

Notice how the smaller fonts are the same height at a pixel level. So Wordpad somehow manages to get this correct without rounding the font sizes to convenient values.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

帝王念 2025-01-08 10:22:14

您没有显示足够的代码来重现该问题,因此这里是一个使用您提供的 Bob Powell 示例的工作示例。

仅演示代码:

private void panel1_Paint(object sender, PaintEventArgs e) {
  e.Graphics.Clear(Color.White);
  e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
  e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

  string[] numbers = new string[] { "1", "2", ".", "3", "4", "5", "6", "8" };

  int x = 10;
  int y = 30;

  foreach (string num in numbers) {
    Font testFont;
    if (num == "4" || num == "5" || num == "6")
      testFont = new Font("Arial", 16, FontStyle.Bold);
    else
      testFont = new Font("Arial", 11, FontStyle.Regular);

    float offset = testFont.SizeInPoints / 
                   testFont.FontFamily.GetEmHeight(testFont.Style) * 
                   testFont.FontFamily.GetCellAscent(testFont.Style);
    float pixels = e.Graphics.DpiY / 72f * offset;

    int numTop = y - (int)(pixels + 0.5f);

    TextRenderer.DrawText(e.Graphics, num, testFont, new Point(x, numTop), 
                          Color.Black, Color.Empty, TextFormatFlags.NoPadding);

    x += TextRenderer.MeasureText(e.Graphics, num, testFont, 
                                  Size.Empty, TextFormatFlags.NoPadding).Width;
  }

  e.Graphics.DrawLine(Pens.Red, new Point(5, y + 1), new Point(x + 5, y + 1));
}

这会产生:

在此处输入图像描述

You haven't shown enough code to reproduce the problem, so here is a working example using that Bob Powell example you gave.

Demonstration code only:

private void panel1_Paint(object sender, PaintEventArgs e) {
  e.Graphics.Clear(Color.White);
  e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
  e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

  string[] numbers = new string[] { "1", "2", ".", "3", "4", "5", "6", "8" };

  int x = 10;
  int y = 30;

  foreach (string num in numbers) {
    Font testFont;
    if (num == "4" || num == "5" || num == "6")
      testFont = new Font("Arial", 16, FontStyle.Bold);
    else
      testFont = new Font("Arial", 11, FontStyle.Regular);

    float offset = testFont.SizeInPoints / 
                   testFont.FontFamily.GetEmHeight(testFont.Style) * 
                   testFont.FontFamily.GetCellAscent(testFont.Style);
    float pixels = e.Graphics.DpiY / 72f * offset;

    int numTop = y - (int)(pixels + 0.5f);

    TextRenderer.DrawText(e.Graphics, num, testFont, new Point(x, numTop), 
                          Color.Black, Color.Empty, TextFormatFlags.NoPadding);

    x += TextRenderer.MeasureText(e.Graphics, num, testFont, 
                                  Size.Empty, TextFormatFlags.NoPadding).Width;
  }

  e.Graphics.DrawLine(Pens.Red, new Point(5, y + 1), new Point(x + 5, y + 1));
}

This produces:

enter image description here

一抹淡然 2025-01-08 10:22:14

也许问题在于您以像素为单位指定字体大小,而偏移量以点为单位计算并以像素为单位转换回来。这可能会带来各种不精确性。

尝试以点为单位指定字体大小,看看是否有效。

Maybe the problem is that you are specifying your font size in pixels while your offset is calculated in points and converted back in pixels. This might introduce all sorts of imprecisions.

Try specifying the font sizes in points and see if it works.

自此以后,行同陌路 2025-01-08 10:22:14

Graphics.TextRenderingHint 设置为使用网格拟合时,缩放后的 TrueType 字体的实际像素度量由 GDI+ 通过搜索字体的 VDMX 表,而不是简单地缩放和舍入其设计指标。这就是我在反汇编中单步执行 Graphics.DrawString 得出的结论。

When Graphics.TextRenderingHint is set to use grid fitting, the actual metrics of a scaled TrueType font in pixels are determined by GDI+ through searching font's VDMX table, rather than by simply scaling and rounding its design metrics. That's what I figured from stepping through Graphics.DrawString in disassembly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文