为啥我的大小为 11 的 truetype 字体呈现与 windows 不同?

Posted

技术标签:

【中文标题】为啥我的大小为 11 的 truetype 字体呈现与 windows 不同?【英文标题】:why is my truetype font of size 11 rendering different than windows?为什么我的大小为 11 的 truetype 字体呈现与 windows 不同? 【发布时间】:2011-08-10 13:49:30 【问题描述】:

即:打开记事本,字体选为“Arial, Size 11”,仔细输入“这只是一个测试”字样,截图:

输入并运行以下Python代码:

import ImageFont, ImageDraw, Image
im = Image.open("c:/textimg.png") #the above image

pilfont = ImageFont.truetype("arial.ttf", 11)

compimg = Image.new("RGB", im.size, (255, 255, 255))
draw = ImageDraw.Draw(compimg)

draw.text((0,0), "this is just a test", (0,0,0), font=pilfont)

compimg.save("c:/compimg.png")

然而结果却令人失望:

不仅尺寸不对,而且还有些阴影,而记事本的渲染清晰且不跨越像素边界。

我怎样才能让它像记事本一样呈现?我在 pygame 上也遇到过这个确切的问题,所以我认为我在这里缺少对 TTF 的一些基本了解。

更新:我再次尝试使用 pygame。它做同样的事情。它确实有一个关闭抗锯齿的选项,但看起来它只是切断了它根据某个阈值进行抗锯齿的任何像素。我得到的最接近的近似值是使用 15 号。代码是:

pygfont = pygame.font.Font(r"c:\windows\fonts\arial.ttf", 15)
surf = pygfont.render("this is just a test", False, (0,0,0), (255,255,255))
pygame.image.save(surf, r"c:\pygameimg.png")

和结果(上面是记事本原件以供比较):

KILL ME http://tinypic.com/images/404.gif

哎呀,为什么我不能马上提供赏金呢?

更新:这里比较所有方法:

PIL 15,然后记事本 11,然后 pygame 15 抗锯齿关闭,然后 pygame 15 抗锯齿打开。

PIL 15 实际上具有正确的比例,它只是抗锯齿。所以:为什么是 15 对 11?如何让它像windows一样做呢? (而 wtf 是 pygame 在做什么?)

【问题讨论】:

这里的图片掉了,能更新一下链接吗? 【参考方案1】:

尽管 PIL 文档声明大小以点为单位,但 PIL 也使用 72DPI,因此如果将点大小转换为像素大小(除以 0.75),然后使用 math.ceil() 转换为整数,字体将正确呈现,并且类似于(但当然不完全是)在 windows 中呈现的内容。

【讨论】:

【参考方案2】:

正如 Ned 已经指出的那样,尝试从某个渲染库中获得相同的结果并不是很正确。如果您想要完全相同的原生外观,您可以尝试使用 Windows 的内部功能,正如我从您的另一篇文章中看到的那样,这就是您已经完成的工作,这非常有趣。

如果我们谈到一般的屏幕真实文本渲染,会有一些重要的细微差别。首先,所有矢量字体格式最初都不是为屏幕开发的,而仅仅是为排版而开发的,它有两个很大的区别。 对于屏幕,您只需要一个位图,即 8 位 Alpha 通道信息,然后您将其与像素值一起混合到屏幕背景中。 要使事情正常工作,您需要将 TTF 文件撕成一组位图。

粗略地说,这里有两种方法:

以 BW(1 位)的高分辨率提取字形。例如。对于大多数情况,~600px 位图就足够了。然后,您可以根据需要构造和调整字符串大小。

将它们作为 8 位数组直接提取到目标分辨率。在这种情况下,您不能调整它们的大小,而只能构造字符串,只需将这个 8 位数组放在 alpha 通道中并沿线放置。

在这两种情况下,您还需要精确的距离列表和字距调整对列表,这对于每个目标分辨率显然是不同的,并且提取此信息可能因不同的字体格式而异。

最好坚持第一步,因为第二种方法也可以通过高分辨率位图实现,无需每次都将其光栅化。 对于小尺寸以获得更好的距离精度,您还可以生成“移位”字形,这意味着相同的字形,但在初始高分辨率图像中移动了 1/2 或 1/3 像素。这又需要稍微不同的字符串构造过程。

重要的关于大小的注意事项:没有字体大小这样精确的东西。在排版中,这是 Em 正方形的大小,与字体的 x 高度没有直接关系。 此外,如果我进行位图渲染或重采样,我只使用 Em-square 的像素大小(整个位图的边界)和下采样因子,如果我使用它来获取小位图。实际x-height尺寸可以近似计算,仅供参考。

对于非抗锯齿位图,这些小位图必须已包含在字体文件中,至少对于 Arial 和 Times 等原生字体而言。 IIRC 这些存储在 TTF 中,通常为 7 - 16 pt 的特定小尺寸,并作为 1 位掩码。唯一的问题是如何从应用程序中提取或获取它们。我真的不知道,我认为这些只是遗留的东西。我更关心现实的渲染问题。

【讨论】:

【参考方案3】:

成功——看红线:

praise the lord http://i54.tinypic.com/2r60dc3.png

使用我创建的方法here。

有趣的是,我仍然必须提供 15 号字体。不过,我不确定这是否与点与像素相关,正如文档所说:

> 0:字体映射器将此值转换为设备单位,并将其与可用字体的单元格高度相匹配。

字符高度相匹配。

但是,提供 -11 并没有得到想要的结果......它只是让它变小了......所以我不知道。这可能是点与像素。

【讨论】:

【参考方案4】:

尺寸不同,因为它们的指定方式不同。要将点转换为像素,请使用公式:pixels = points * 96 / 72 其中 96 是配置到 Windows 中的 DPI(不是显示器的实际 DPI)。在您的情况下,11*96/72 = 14.6666,四舍五入为 15。

至于使文本像素与像素相同,使用提供的工具是不可能的 - Ned is correct。如果这绝对重要,您将需要使用 Windows API 为您呈现此文本并将其复制到图像中。不是一个简单的过程。

【讨论】:

hmm 你能指点一下我可以从哪里开始使用winapi来呈现文本吗?【参考方案5】:

字体渲染是一个复杂而微妙的过程,并且已经实施了多次。在您的情况下,PIL 和 Windows 看起来不同,因为它们使用完全不同的字体渲染引擎。 Windows 使用它的内置渲染,而 PIL 使用它编译时使用的 Freetype。

我不知道每个环境如何解释其“大小”参数,但即使您将它们解释为相同,渲染也会完全不同。获得与记事本相同的像素的方法是启动记事本并抓取屏幕。

也许如果您详细说明为什么您需要与记事本相同的渲染,我们将为您的问题提供创造性的解决方案。

【讨论】:

有什么方法可以在 Python 中使用 Windows 的方法进行渲染?我想呈现与记事本相同的原因是因为我想自动检测程序正在使用什么字体。如果他们使用简单的非抗锯齿渲染,我只能希望它,并且我通过使用绘图/记事本并将其与屏幕截图进行比较成功地手动匹配它们,所以这就是我试图模仿的。 实际上很多程序都使用flash,所以如果我能找到一种方法让flash来渲染我的字体,那就太好了.. 也感谢您指出 PIL 的算法。我对字体渲染的了解比我想知道的要多..【参考方案6】:

我认为记事本的“大小”是点大小,而 ImageFont.truetype() 的“大小”是像素。

【讨论】:

还有..我能做些什么吗?我想像记事本一样渲染,特别是要摆脱可能发生的任何抗锯齿 我也不确定这是不是真的。记事本中“h”的高度为 11,而 PIL 中“h”的高度为 7 或 8-ish。

以上是关于为啥我的大小为 11 的 truetype 字体呈现与 windows 不同?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 replit 中使用 PIL.ImageFont.truetype 加载字体文件

为啥即使我没有明确设置表格字体大小,表格也不使用正文字体大小?

将带有 PostScript 轮廓的 OpenTypeFonts 转换为 TrueType 字体

TrueType字体文件提取关键信息

TrueType字体文件提取关键信息

TrueType字体文件提取关键信息