python PIL在图像上绘制多行文字

use*_*541 32 python text image python-imaging-library

我尝试在图像的底部添加文本,实际上我已经完成了,但是如果我的文本比图像宽度长,则从两侧剪切,以简化我希望文本在多行中,如果它是比图像宽度长.这是我的代码:

FOREGROUND = (255, 255, 255)
WIDTH = 375
HEIGHT = 50
TEXT = 'Chyba najwy?szy czas zada? to pytanie na ?niadanie \n Chyba najwy?szy czas zada? to pytanie na ?niadanie'
font_path = '/Library/Fonts/Arial.ttf'
font = ImageFont.truetype(font_path, 14, encoding='unic')
text = TEXT.decode('utf-8')
(width, height) = font.getsize(text)

x = Image.open('media/converty/image.png')
y = ImageOps.expand(x,border=2,fill='white')
y = ImageOps.expand(y,border=30,fill='black')

w, h = y.size
bg = Image.new('RGBA', (w, 1000), "#000000")

W, H = bg.size
xo, yo = (W-w)/2, (H-h)/2
bg.paste(y, (xo, 0, xo+w, h))
draw = ImageDraw.Draw(bg)
draw.text(((w - width)/2, w), text, font=font, fill=FOREGROUND)


bg.show()
bg.save('media/converty/test.png')
Run Code Online (Sandbox Code Playgroud)

unu*_*tbu 44

你可以textwrap.wrap用来打破text一个字符串列表,每个字符串最多只有width字符:

import textwrap
lines = textwrap.wrap(text, width=40)
y_text = h
for line in lines:
    width, height = font.getsize(line)
    draw.text(((w - width) / 2, y_text), line, font=font, fill=FOREGROUND)
    y_text += height
Run Code Online (Sandbox Code Playgroud)

  • 多谢!只需复制和粘贴,它就像一个魅力。你是最棒的 :) (3认同)
  • @User`40`代表最大字符数。表示在换行之前最多允许40个字符。但是,如果一个单词是10个字符,然后下一个单词是31个字符,则由于第一个单词和第二个单词无法容纳该行,因此它将在第一个单词之后自动换行。 (2认同)
  • @teewuane 基于像素做这个怎么样?我们并不总是处理等宽字体 (2认同)
  • @User如果你想获取文本字符串的大小,你可以使用 PIL.ImageDraw.ImageDraw.textsize 它返回一个以像素为单位的(宽度,高度)元组。您可以使用此实现一个解决方案,在宽度超过某个阈值之前尝试最长的可能字符串 (2认同)

Fra*_*urt 12

对于使用一个完整的工作示例unutbu特技(测试与Python 3.6和枕头5.3.0):

from PIL import Image, ImageDraw, ImageFont
import textwrap

def draw_multiple_line_text(image, text, font, text_color, text_start_height):
    '''
    From unutbu on [python PIL draw multiline text on image](/sf/answers/538881031/)
    '''
    draw = ImageDraw.Draw(image)
    image_width, image_height = image.size
    y_text = text_start_height
    lines = textwrap.wrap(text, width=40)
    for line in lines:
        line_width, line_height = font.getsize(line)
        draw.text(((image_width - line_width) / 2, y_text), 
                  line, font=font, fill=text_color)
        y_text += line_height


def main():
    '''
    Testing draw_multiple_line_text
    '''
    #image_width
    image = Image.new('RGB', (800, 600), color = (0, 0, 0))
    fontsize = 40  # starting font size
    font = ImageFont.truetype("arial.ttf", fontsize)
    text1 = "I try to add text at the bottom of image and actually I've done it, but in case of my text is longer then image width it is cut from both sides, to simplify I would like text to be in multiple lines if it is longer than image width."
    text2 = "You could use textwrap.wrap to break text into a list of strings, each at most width characters long"

    text_color = (200, 200, 200)
    text_start_height = 0
    draw_multiple_line_text(image, text1, font, text_color, text_start_height)
    draw_multiple_line_text(image, text2, font, text_color, 400)
    image.save('pil_text.png')

if __name__ == "__main__":
    main()
    #cProfile.run('main()') # if you want to do some profiling
Run Code Online (Sandbox Code Playgroud)

结果:

在此处输入图片说明

  • 应该是最佳答案 (2认同)

pry*_*yma 10

接受的答案包装文本而不测量字体(最多40个字符,无论字体大小和盒子宽度是多少),因此结果只是近似值,可能很容易溢出或填充不足.

这是一个简单的库,可以正确解决问题:https: //gist.github.com/turicas/1455973

  • 这是最好的答案。我们应该尝试更改已接受的答案,因为它是 6 年前被接受的。 (3认同)

Igo*_*kiy 8

所有关于textwrap使用的建议都未能确定非等宽字体的正确宽度(如 Arial,在主题示例代码中使用)。

我写了简单的帮助类来包装关于真实字体字母大小的文本:

class TextWrapper(object):
    """ Helper class to wrap text in lines, based on given text, font
        and max allowed line width.
    """

    def __init__(self, text, font, max_width):
        self.text = text
        self.text_lines = [
            ' '.join([w.strip() for w in l.split(' ') if w])
            for l in text.split('\n')
            if l
        ]
        self.font = font
        self.max_width = max_width

        self.draw = ImageDraw.Draw(
            Image.new(
                mode='RGB',
                size=(100, 100)
            )
        )

        self.space_width = self.draw.textsize(
            text=' ',
            font=self.font
        )[0]

    def get_text_width(self, text):
        return self.draw.textsize(
            text=text,
            font=self.font
        )[0]

    def wrapped_text(self):
        wrapped_lines = []
        buf = []
        buf_width = 0

        for line in self.text_lines:
            for word in line.split(' '):
                word_width = self.get_text_width(word)

                expected_width = word_width if not buf else \
                    buf_width + self.space_width + word_width

                if expected_width <= self.max_width:
                    # word fits in line
                    buf_width = expected_width
                    buf.append(word)
                else:
                    # word doesn't fit in line
                    wrapped_lines.append(' '.join(buf))
                    buf = [word]
                    buf_width = word_width

            if buf:
                wrapped_lines.append(' '.join(buf))
                buf = []
                buf_width = 0

        return '\n'.join(wrapped_lines)
Run Code Online (Sandbox Code Playgroud)

用法示例:

wrapper = TextWrapper(text, image_font_intance, 800)
wrapped_text = wrapper.wrapped_text()
Run Code Online (Sandbox Code Playgroud)

它可能不是超快,因为它逐字呈现整个文本,以确定字宽。但在大多数情况下应该没问题。