GDI+在基线上绘制不同大小的文本存在偏差 1px 的问题
我需要打印数字,其中通过增加字体大小和粗细来强调中间的一些数字。在下面的示例中,强调了 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.
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):
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?
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:
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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您没有显示足够的代码来重现该问题,因此这里是一个使用您提供的 Bob Powell 示例的工作示例。
仅演示代码:
这会产生:
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:
This produces:
也许问题在于您以像素为单位指定字体大小,而偏移量以点为单位计算并以像素为单位转换回来。这可能会带来各种不精确性。
尝试以点为单位指定字体大小,看看是否有效。
Maybe the problem is that you are specifying your font size in
pixels
while your offset is calculated inpoints
and converted back inpixels
. This might introduce all sorts of imprecisions.Try specifying the font sizes in
points
and see if it works.当
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.