PIL如何根据图像大小调整文本大小的比例

66
我正在尝试动态缩放文本以放置在各种已知尺寸的图像上。该文本将被应用为水印。是否有任何方法可以根据图像尺寸缩放文本?我不要求文本占据整个表面积,只需要足够可见以便识别和难以删除。我在Linux上使用Python Imaging Library版本1.1.7。
我希望能够设置文本大小与图像尺寸的比例,比如说是1/10大小之类的。
我一直在查看字体大小属性来更改大小,但我没有成功创建一个缩放算法。我想知道是否有更好的方法。
有关如何实现这一点的任何想法?
谢谢
4个回答

106

你可以逐步增加字体大小直到找到合适的大小。font.getsize() 是一个函数,用于告诉你渲染文本的尺寸有多大。

from PIL import ImageFont, ImageDraw, Image

image = Image.open('hsvwheel.png')
draw = ImageDraw.Draw(image)
txt = "Hello World"
fontsize = 1  # starting font size

# portion of image width you want text width to be
img_fraction = 0.50

font = ImageFont.truetype("arial.ttf", fontsize)
while font.getsize(txt)[0] < img_fraction*image.size[0]:
    # iterate until the text size is just larger than the criteria
    fontsize += 1
    font = ImageFont.truetype("arial.ttf", fontsize)

# optionally de-increment to be sure it is less than criteria
fontsize -= 1
font = ImageFont.truetype("arial.ttf", fontsize)

print('final font size',fontsize)
draw.text((10, 25), txt, font=font) # put the text on the image
image.save('hsvwheel_txt.png') # save it

如果这种方法还不够高效,您可以实现一种根查找方案,但我猜测与您的其余图像处理流程相比,font.getsize()函数只是小菜一碟。


5
OSError: 无法打开资源。我需要先从某个地方下载字体吗? - Gulzar

16

我知道这是一个旧问题,已经有人用解决方法回答了。感谢@Paul!

但是每次增加一个字号可能会很耗时间(至少对于我的小服务器来说是这样的)。例如,像“Foo”这样的小文本需要大约1-2秒钟,具体取决于图像大小。

为了解决这个问题,我修改了Paul的代码,使其类似于二分查找一样查找数字。

breakpoint = img_fraction * photo.size[0]
jumpsize = 75
while True:
    if font.getsize(text)[0] < breakpoint:
        fontsize += jumpsize
    else:
        jumpsize = jumpsize // 2
        fontsize -= jumpsize
    font = ImageFont.truetype(font_path, fontsize)
    if jumpsize <= 1:
        break

这样做可以增加字体大小,直到超过断点,然后上下波动(每次下降时将跳跃大小减半),直到达到正确的大小。

有了这个,我的步骤可以从200多个减少到约10个,所以从1-2秒变为0.04到0.08秒。

对于Paul的代码来说,这是一个插入式替换(针对while语句和之后的2行代码,因为在while中已经获得了正确的字体大小)。

这是在几分钟内完成的,如果有任何改进,请指出!我希望这能帮助那些正在寻找更高效友好解决方案的人们。


2
int(jumpsize / 2) 可以替换为 jumpsize // 2 - Mark Ransom
谢谢@MarkRansom,我进行了调整。 - Nachtalb

12

通常情况下,当您更改字体大小时,字体大小的变化不会呈现线性变化。

非线性缩放

现在这通常取决于软件、字体等等... 这个例子来自于Typophile并使用了LaTex + Computer Modern字体. 如您所见,它并不是一个完全线性的缩放。所以,如果您遇到了非线性字体缩放的问题,那么我不确定该如何解决,但是有一个建议:

  1. 首先将字体渲染成尽可能接近所需大小的大小,然后通过常规图像缩放算法进行缩放...
  2. 接受无法进行完全线性缩放,并尝试创建某种表格/算法,以选择最接近的点大小与图像大小匹配的字体大小。

谢谢回复,Paul建议的方法解决了问题。不管怎样还是谢谢。 - Shpongle
1
看到这张图片,我的第一个想法是:“如果你不随着文本缩放行高,那么它当然不会呈现线性。” 但是比较线的长度,你可以看到6pt字体的渲染长度超过了12pt的一半,5pt的长度也超过了10pt的一半。 - Ian
TrueType字体是线性缩放的,只有字距会使文本偏离。LaTeX + Computer Modern不使用TrueType,Computer Modern甚至不是矢量字体,它是预渲染为不同点大小的字体。在这种情况下,字体的高度是线性缩放的,但每个字母的纵横比随着大小而改变,注意在5pt时x比高宽,而在10pt时则比宽高。TTF通常具有单个字母形状,其各向同性地缩放到所请求的大小。 - Cris Luengo

3

尽管其他答案说字体大小不是线性缩放的,但在我测试的所有示例中,它们都是线性缩放的(在1-2%之内)。

因此,如果您需要一个更简单、更有效的版本,它可以在几个百分点内工作,您可以复制/粘贴以下内容:

from PIL import ImageFont, ImageDraw, Image

def find_font_size(text, font, image, target_width_ratio):
    tested_font_size = 100
    tested_font = ImageFont.truetype(font, tested_font_size)
    observed_width, observed_height = get_text_size(text, image, tested_font)
    estimated_font_size = tested_font_size / (observed_width / image.width) * target_width_ratio
    return round(estimated_font_size)

def get_text_size(text, image, font):
    im = Image.new('RGB', (image.width, image.height))
    draw = ImageDraw.Draw(im)
    return draw.textsize(text, font)

函数find_font_size()可以像这样使用(完整的示例):
width_ratio = 0.5  # Portion of the image the text width should be (between 0 and 1)
font_family = "arial.ttf"
text = "Hello World"

image = Image.open('image.jpg')
editable_image = ImageDraw.Draw(image)
font_size = find_font_size(text, font_family, image, width_ratio)
font = ImageFont.truetype(font_family, font_size)
print(f"Font size found = {font_size} - Target ratio = {width_ratio} - Measured ratio = {get_text_size(text, image, font)[0] / image.width}")

editable_image.text((10, 10), text, font=font)
image.save('output.png')

对于一张225x225的图片,将会打印:

>> Font size found = 22 - Target ratio = 0.5 - Measured ratio = 0.502

我测试了各种字体和图片大小的find_font_size(),在所有情况下都能正常工作。
如果你想知道这个函数是如何工作的,基本上是使用tested_font_size来确定如果我们使用特定的字体大小生成文本将得到哪个比例。然后,我们使用交叉乘法规则来获取目标字体大小。
我测试了不同的tested_font_size值,并发现只要它不太小,就不会有任何区别。

textsize已被弃用:DeprecationWarning: textsize已被弃用,并将在Pillow 10(2023-07-01)中删除。请改用textbbox或textlength。 - LotoLo

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