Python Imaging Library - 文字渲染

45

我试图使用PIL渲染一些文本,但结果实际上很糟糕。

例如,这是我在Photoshop中写的一些文本:

PhotoShop

而使用PIL得到的结果如下:

PIL

可以看到,PIL的结果并不理想。也许我只是过于挑剔了,但有没有办法使用PIL绘制文本以获得更接近参考图像的结果?

以下是我在Python 2.7中使用PIL 1.1.7的代码:

image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
6个回答

69

我自己想出了一种解决方案,我认为这个方案可行。

我的做法是将文本放大,比实际需要的大小大3倍左右,然后使用抗锯齿技术将其缩小,虽然不是完美的,但比默认效果好很多,而且不需要cairo或者pango。

例如:

image = Image.new("RGBA", (600,150), (255,255,255))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", fontsize)

draw.text((10, 0), txt, (0,0,0), font=font)
img_resized = image.resize((188,45), Image.ANTIALIAS)

最终结果如下图所示:

final result

使用相同的字体,比以前获得的结果好多了。


我需要文本居中,而不是左对齐。使用上述解决方案有办法实现吗? - Aarohi Kulkarni
3
请记住,字体通常为不同大小提供不同的字形(不仅是缩放一个字形)。 这在较小的目标尺寸中尤其明显。 - Tamás Szelei
另一种表达方式是:不要假设你需要这么做。我发现使用此函数渲染文本非常缓慢,这可能会导致不必要的减速。 - chris

21

尝试使用pycairo - Cairo绘图库的Python绑定,它可以用于更精细的绘图,包括抗锯齿线条等,并且您还可以生成矢量图像。

正确处理字体和布局很复杂,需要同时使用“pango”和“pangocairo”库。虽然它们是为严肃的字体工作而设计的(所有GTK+部件都使用pango进行字体呈现),但可用的文档和示例非常少。

下面的示例显示系统中可用的打印并将示例文本呈现在通过命令行传递的字体系列中。

# -*- coding: utf-8 -*-
import cairo
import pango
import pangocairo
import sys

surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 320, 120)
context = cairo.Context(surf)

#draw a background rectangle:
context.rectangle(0,0,320,120)
context.set_source_rgb(1, 1, 1)
context.fill()

#get font families:

font_map = pangocairo.cairo_font_map_get_default()
families = font_map.list_families()

# to see family names:
print [f.get_name() for f in   font_map.list_families()]

#context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)

# Positions drawing origin so that the text desired top-let corner is at 0,0
context.translate(50,25)

pangocairo_context = pangocairo.CairoContext(context)
pangocairo_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)

layout = pangocairo_context.create_layout()
fontname = sys.argv[1] if len(sys.argv) >= 2 else "Sans"
font = pango.FontDescription(fontname + " 25")
layout.set_font_description(font)

layout.set_text(u"Travis L.")
context.set_source_rgb(0, 0, 0)
pangocairo_context.update_layout(layout)
pangocairo_context.show_layout(layout)

with open("cairo_text.png", "wb") as image_file:
    surf.write_to_png(image_file)

渲染后的图片


4
唉,这个简单应用看起来需要太多的依赖了。(我正在尝试在10.6上使用homebrew设置pango和cairo)......我可能需要重新考虑一些事情。不过还是谢谢你的帮助。 - exiva
还有其他的图像库可能更容易设置。我首先想到的是pygame,但它不能处理任何严肃的文本工作(没有反锯齿或子像素渲染)。也许对于imagemagick的包装器(pythonmagick)可以更容易处理。 - jsbueno
仅仅是一个更新:Pygame确实具有抗锯齿和漂亮的字体渲染。只是它的一些绘图原语不支持。而且,相比上面的pango + cairo示例,它要容易处理一个数量级。 - jsbueno
1
@jsbueno 在Windows上,Pygame的字体渲染很丑陋,大多数字体度量都是错误的,与实际渲染相差甚远。(这是Pygame 1.9的情况。最新版本可能会更好,但我没有尝试过)。 - Mikhail V

12

我从未使用过PIL,但快速查看Draw方法的文档表明PIL提供了一种呈现简单图形的方式。Photoshop提供了一种呈现复杂图形的方式。要想接近Photoshop般的效果,至少需要字体提示和抗锯齿功能。 PIL的文档甚至没有暗示具有这样的功能。你可能需要考虑使用一个外部工具来更好地在图像上呈现文本。例如,ImageMagick(你需要使用处理标准24位RGB的8位版本)。你可以在这里找到一些文本绘制示例:http://www.imagemagick.org/Usage/draw/


谢谢Dave,你的回答对我非常有帮助,因为我正在寻找一种以编程方式生成各种颜色的大型单个字母图像的方法。Imagemagick能够轻松处理定位、图像尺寸和其他参数。 - MattH

6
建议:使用Wand或其他图像处理库。
以下是使用Wand的示例 -
from wand.color import Color
from wand.image import Image
from wand.drawing import Drawing
from wand.compat import nested

with Drawing() as draw:
    with Image(width=1000, height=100, background=Color('lightblue')) as img:
        draw.font_family = 'Indie Flower'
        draw.font_size = 40.0
        draw.push()
        draw.fill_color = Color('hsl(0%, 0%, 0%)')
        draw.text(0,int(img.height/2 + 20), 'Hello, world!')
        draw.pop()
        draw(img)
        img.save(filename='image.png')

image that comes out-


对我来说比其他两个都要好得多。这是我第一次听说“wand”,它是ImageMagick的Python绑定。 - YakovK

6
在Python3中,有一种字体的替代选项。我在任何地方都找不到这个答案,希望它能帮助像我这样在Google上找到这个问题并花费很长时间才找到答案的人。
draw = ImageDraw.Draw(img)
        draw.fontmode = "L"

文档中提到此处


4
对于我来说,仍然使用“L”会导致文本抗锯齿,但是将draw.fontmode =“1”设置有效。有关模式的说明可以在此处找到[(https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes)];似乎没有任何地方记录`ImageDraw`的`fontmode`成员变量;感谢您抽出时间发布这个答案! - Reign of Error

-4

你也可以尝试将字体写两次,这样可以极大地提高质量。

image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)

1
确实让它更加大胆,但我不能说它更好。也许这是主观的。 - physicalattraction
粗体并且边框不那么清晰,我认为这样更好。至少它接近于 Photoshop 或 GIMP 的标准质量。 - sharpshadow
2
为什么在同一位置再次渲染相同的内容会有所不同? - martineau
1
@martineau - 我猜测,当您绘制两次时,灰色而不是完全黑色的像素会变暗。 - ArtOfWarfare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接