Python PIL рисует многострочный текст на изображении
Я пытаюсь добавить текст в нижней части изображения, и на самом деле я это сделал, но в случае, если мой текст длиннее, а ширина изображения - с обеих сторон, чтобы упростить, я хотел бы, чтобы текст был в нескольких строках если длина изображения больше, чем ширина изображения. Вот мой код:
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')
Ответы
Ответ 1
Вы можете использовать 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
Ответ 2
Принятый ответ переносит текст без измерения шрифта (не более 40 символов, независимо от размера шрифта и ширины поля), поэтому результаты являются приблизительными и могут легко переполнить или не заполнить поле.
Вот простая библиотека, которая решает проблему правильно: https://gist.github.com/turicas/1455973
Ответ 3
Все рекомендации по использованию 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()
Это, вероятно, не супер-быстрый, потому что он отображает весь текст слово за словом, чтобы определить ширину слова. Но в большинстве случаев все должно быть в порядке.
Ответ 4
Для полного рабочего примера с использованием трюка 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](/questions/224740/python-pil-draw-multiline-text-on-image/1190637#1190637)
'''
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]()
Ответ 5
text = textwrap.fill("test ",width=35)
self.draw.text((x, y), text, font=font, fill="Black")
Ответ 6
Вы можете использовать PIL.ImageDraw.Draw.multiline_text()
.
draw.multiline_text((WIDTH, HEIGHT), TEXT, fill=FOREGROUND, font=font)
Вы даже установите spacing
или align
, используя те же имена параметров.