为何我的11号TrueType字体在渲染上与Windows不同?
简单来说:打开记事本,选择字体为“Arial,大小11”,然后仔细输入“this is just a test”这句话,最后截了个屏:
接着输入并运行以下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的显示方式一样?(还有pygame到底在干嘛?)
6 个回答
我觉得记事本里的“大小”是指字体的点数,而ImageFont.truetype()里的“大小”是指像素。
不同的大小是因为它们的定义方式不一样。要把点数转换成像素,可以用这个公式:像素 = 点数 * 96 / 72
,这里的96是Windows设置的DPI(每英寸点数),而不是显示器的实际DPI。在你的例子中,11*96/72 = 14.6666,四舍五入后就是15。
至于想让文字在像素上完全一致,这在现有的工具下是不可能的——Ned说得对。如果这真的很重要,你需要使用Windows的API来渲染这个文字,然后把它复制到图像里。这不是一个简单的过程。
字体渲染是一个复杂而微妙的过程,而且已经被实现了很多次。在你的情况中,PIL和Windows看起来不同,是因为它们使用了完全不同的字体渲染引擎。Windows使用的是它自带的渲染方式,而PIL则使用它编译时所用的Freetype。
我不太清楚每个环境是如何理解它的“大小”参数的,但即使你让它们的理解方式一致,渲染出来的效果也会有所不同。想要得到和记事本一样的像素效果,最简单的方法就是打开记事本,然后截取屏幕。
也许如果你能多解释一下为什么想要和记事本一样的渲染效果,我们可以找到一些更有创意的解决方案来帮你解决问题。