[英]PIL - draw multiline text on image
我嘗試在圖像底部添加文本,實際上我已經完成了,但是如果我的文本比圖像寬度長,它會從兩側被切割,為了簡化我希望文本在多行中,如果它是長於圖像寬度。 這是我的代碼:
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')
您可以使用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
接受的答案在不測量字體的情況下包裝文本(最多 40 個字符,無論字體大小和框寬是多少),因此結果只是近似值,可能很容易溢出或填充不足。
這是一個正確解決問題的簡單庫: https ://gist.github.com/turicas/1455973
對於使用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
結果:
所有關於textwrap
使用的建議都無法確定非等寬字體的正確寬度(如 Arial,在主題示例代碼中使用)。
我編寫了簡單的幫助類來包裝有關實際字體字母大小的文本:
from PIL import Image, ImageDraw
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()
它可能不是超快,因為它逐字呈現整個文本,以確定字寬。 但大多數情況下應該沒問題。
此功能會將text
拆分為max
長度的行,當以 font font
制作時,它會創建一個帶有文本的透明圖像。
def split_text(text, font, max)
text=text.split(" ")
total=0
result=[]
line=""
for part in text:
if total+font.getsize(f"{part} ")[0]<max:
line+=f"{part} "
total+=font.getsize(part)[0]
else:
line=line.rstrip()
result.append(line)
line=f"{part} "
total=font.getsize(f"{part} ")[0]
line=line.rstrip()
result.append(line)
image=new("RGBA", (max, font.getsize("gL")[1]*len(result)), (0, 0, 0, 0))
imageDrawable=Draw(image)
position=0
for line in result:
imageDrawable.text((0, position), line, font)
position+=font.getsize("gL")[1]
return image
一個最小的例子,不斷添加單詞,直到超過最大寬度限制。 get_line 函數返回當前行和剩余的單詞,它們可以再次循環使用,如下面的 draw_lines 函數。
def get_line(words, width_limit):
# get text which can fit in one line, remains is list of words left over
line_width = 0
line = ''
i = 0
while i < len(words) and (line_width + FONT.getsize(words[i])[0]) < width_limit:
if i == 0:
line = line + words[i]
else:
line = line + ' ' + words[i]
i = i + 1
line_width = FONT.getsize(line)[0]
remains = []
if i < len(words):
remains = words[i:len(words)]
return line, remains
def draw_lines(text, text_box):
# add some margin to avoid touching borders
box_width = text_box[1][0] - text_box[0][0] - (2*MARGIN)
text_x = text_box[0][0] + MARGIN
text_y = text_box[0][1] + MARGIN
words = text.split(' ')
while words:
line, words = get_line(words, box_width)
width, height = FONT.getsize(line)
im_draw.text((text_x, text_y), line, font=FONT, fill=FOREGROUND)
text_y += height
最簡單的解決方案是使用 textwrap + multiline_text function
from PIL import Image, ImageDraw
import textwrap
lines = textwrap.wrap("your long text", width=20)
draw.multiline_text((x,y), '\n'.join(lines))
您可以使用PIL.ImageDraw.Draw.multiline_text()
。
draw.multiline_text((WIDTH, HEIGHT), TEXT, fill=FOREGROUND, font=font)
您甚至可以使用相同的參數名稱設置spacing
或align
。
text = textwrap.fill("test ",width=35)
self.draw.text((x, y), text, font=font, fill="Black")
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.