Python PIL рисовать многострочный текст на изображении - PullRequest
32 голосов
/ 08 октября 2011

Я пытаюсь добавить текст внизу изображения, и на самом деле я сделал это, но в случае, если мой текст длиннее ширины изображения, он обрезается с обеих сторон, чтобы упростить, я хотел бы, чтобы текст был в несколько строк если он длиннее ширины изображения. Вот мой код:

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')

Ответы [ 6 ]

41 голосов
/ 08 октября 2011

Вы можете использовать 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
9 голосов
/ 15 сентября 2017

Принятый ответ переносит текст без измерения шрифта (не более 40 символов, независимо от размера шрифта и ширины поля), поэтому результаты являются приблизительными и могут легко переполнить или не заполнить поле.

Вот простая библиотека, которая решает проблему правильно: https://gist.github.com/turicas/1455973

2 голосов
/ 08 апреля 2018

Все рекомендации по использованию 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)

Пример использования:

wrapper = TextWrapper(text, image_font_intance, 800)
wrapped_text = wrapper.wrapped_text()

Это, вероятно, не супер-быстрый, потому что он отображает весь текст слово за словом, чтобы определить ширину слова.Но в большинстве случаев все должно быть в порядке.

0 голосов
/ 19 мая 2019

Для полного рабочего примера, используя unutbu * * * * * * * * * * * * * * * * * * * * * * * (протестировано с Python 3.6 и Pillow 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](https://stackoverflow.com/a/7698300/395857)
    '''
    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

Результат:

enter image description here

0 голосов
/ 11 апреля 2017

Вы можете использовать PIL.ImageDraw.Draw.multiline_text().

draw.multiline_text((WIDTH, HEIGHT), TEXT, fill=FOREGROUND, font=font)

Вы даже устанавливаете spacing или align, используя те же имена параметров.

0 голосов
/ 20 мая 2016
text = textwrap.fill("test ",width=35)
self.draw.text((x, y), text, font=font, fill="Black")
...