-
-
Save pojda/8bf989a0556845aaf4662cd34f21d269 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
# coding: utf-8 | |
# You need PIL <http://www.pythonware.com/products/pil/> to run this script | |
# Download unifont.ttf from <http://unifoundry.com/unifont.html> (or use | |
# any TTF you have) | |
# Copyright 2011 Álvaro Justen [alvarojusten at gmail dot com] | |
# License: GPL <http://www.gnu.org/copyleft/gpl.html> | |
from image_utils import ImageText | |
color = (50, 50, 50) | |
text = 'Python is a cool programming language. You should learn it!' | |
font = 'unifont.ttf' | |
img = ImageText((800, 600), background=(255, 255, 255, 200)) # 200 = alpha | |
#write_text_box will split the text in many lines, based on box_width | |
#`place` can be 'left' (default), 'right', 'center' or 'justify' | |
#write_text_box will return (box_width, box_calculed_height) so you can | |
#know the size of the wrote text | |
img.write_text_box((300, 50), text, box_width=200, font_filename=font, | |
font_size=15, color=color) | |
img.write_text_box((300, 125), text, box_width=200, font_filename=font, | |
font_size=15, color=color, place='right') | |
img.write_text_box((300, 200), text, box_width=200, font_filename=font, | |
font_size=15, color=color, place='center') | |
img.write_text_box((300, 275), text, box_width=200, font_filename=font, | |
font_size=15, color=color, place='justify') | |
#You don't need to specify text size: can specify max_width or max_height | |
# and tell write_text to fill the text in this space, so it'll compute font | |
# size automatically | |
#write_text will return (width, height) of the wrote text | |
img.write_text((100, 350), 'test fill', font_filename=font, | |
font_size='fill', max_height=150, color=color) | |
img.save('sample-imagetext.png') |
#!/usr/bin/env python | |
# coding: utf-8 | |
# Copyright 2011 Álvaro Justen [alvarojusten at gmail dot com] | |
# License: GPL <http://www.gnu.org/copyleft/gpl.html> | |
from PIL import Image, ImageDraw, ImageFont | |
import PIL | |
class ImageText(object): | |
def __init__(self, filename_or_size_or_Image, mode='RGBA', background=(0, 0, 0, 0), | |
encoding='utf8'): | |
if isinstance(filename_or_size_or_Image, str): | |
self.filename = filename_or_size_or_Image | |
self.image = Image.open(self.filename) | |
self.size = self.image.size | |
elif isinstance(filename_or_size_or_Image, (list, tuple)): | |
self.size = filename_or_size_or_Image | |
self.image = Image.new(mode, self.size, color=background) | |
self.filename = None | |
elif isinstance(filename_or_size_or_Image, PIL.Image.Image): | |
self.image = filename_or_size_or_Image | |
self.size = self.image.size | |
self.filename = None | |
self.draw = ImageDraw.Draw(self.image) | |
self.encoding = encoding | |
def save(self, filename=None): | |
self.image.save(filename or self.filename) | |
def show(self): | |
self.image.show() | |
def get_font_size(self, text, font, max_width=None, max_height=None): | |
if max_width is None and max_height is None: | |
raise ValueError('You need to pass max_width or max_height') | |
font_size = 1 | |
text_size = self.get_text_size(font, font_size, text) | |
if (max_width is not None and text_size[0] > max_width) or \ | |
(max_height is not None and text_size[1] > max_height): | |
raise ValueError("Text can't be filled in only (%dpx, %dpx)" % \ | |
text_size) | |
while True: | |
if (max_width is not None and text_size[0] >= max_width) or \ | |
(max_height is not None and text_size[1] >= max_height): | |
return font_size - 1 | |
font_size += 1 | |
text_size = self.get_text_size(font, font_size, text) | |
def write_text(self, xy, text, font_filename, font_size=11, | |
color=(0, 0, 0), max_width=None, max_height=None): | |
x, y = xy | |
if font_size == 'fill' and \ | |
(max_width is not None or max_height is not None): | |
font_size = self.get_font_size(text, font_filename, max_width, | |
max_height) | |
text_size = self.get_text_size(font_filename, font_size, text) | |
font = ImageFont.truetype(font_filename, font_size) | |
if x == 'center': | |
x = (self.size[0] - text_size[0]) / 2 | |
if y == 'center': | |
y = (self.size[1] - text_size[1]) / 2 | |
self.draw.text((x, y), text, font=font, fill=color) | |
return text_size | |
def get_text_size(self, font_filename, font_size, text): | |
font = ImageFont.truetype(font_filename, font_size) | |
return font.getsize(text) | |
def write_text_box(self, xy, text, box_width, font_filename, | |
font_size=11, color=(0, 0, 0), place='left', | |
justify_last_line=False, position='top', | |
line_spacing=1.0): | |
x, y = xy | |
lines = [] | |
line = [] | |
words = text.split() | |
for word in words: | |
new_line = ' '.join(line + [word]) | |
size = self.get_text_size(font_filename, font_size, new_line) | |
text_height = size[1] * line_spacing | |
last_line_bleed = text_height - size[1] | |
if size[0] <= box_width: | |
line.append(word) | |
else: | |
lines.append(line) | |
line = [word] | |
if line: | |
lines.append(line) | |
lines = [' '.join(line) for line in lines if line] | |
if position == 'middle': | |
height = (self.size[1] - len(lines)*text_height + last_line_bleed)/2 | |
height -= text_height # the loop below will fix this height | |
elif position == 'bottom': | |
height = self.size[1] - len(lines)*text_height + last_line_bleed | |
height -= text_height # the loop below will fix this height | |
else: | |
height = y | |
for index, line in enumerate(lines): | |
height += text_height | |
if place == 'left': | |
self.write_text((x, height), line, font_filename, font_size, | |
color) | |
elif place == 'right': | |
total_size = self.get_text_size(font_filename, font_size, line) | |
x_left = x + box_width - total_size[0] | |
self.write_text((x_left, height), line, font_filename, | |
font_size, color) | |
elif place == 'center': | |
total_size = self.get_text_size(font_filename, font_size, line) | |
x_left = int(x + ((box_width - total_size[0]) / 2)) | |
self.write_text((x_left, height), line, font_filename, | |
font_size, color) | |
elif place == 'justify': | |
words = line.split() | |
if (index == len(lines) - 1 and not justify_last_line) or \ | |
len(words) == 1: | |
self.write_text((x, height), line, font_filename, font_size, | |
color) | |
continue | |
line_without_spaces = ''.join(words) | |
total_size = self.get_text_size(font_filename, font_size, | |
line_without_spaces) | |
space_width = (box_width - total_size[0]) / (len(words) - 1.0) | |
start_x = x | |
for word in words[:-1]: | |
self.write_text((start_x, height), word, font_filename, | |
font_size, color) | |
word_size = self.get_text_size(font_filename, font_size, | |
word) | |
start_x += word_size[0] + space_width | |
last_word_size = self.get_text_size(font_filename, font_size, | |
words[-1]) | |
last_word_x = x + box_width - last_word_size[0] | |
self.write_text((last_word_x, height), words[-1], font_filename, | |
font_size, color) | |
return (box_width, height - y) |
You're right, I've never tested that condition. :) As soon as I get back to work on this project I'll fix that. Or if you have a suggestion just leave it here and I'll add it to the code.
Just set height=y instead of height-=y. Simple typo.
Just set height=y instead of height-=y. Simple typo.
You're right! Just fixed.
Actually it should be:
height = y - text_height
Also, you could add support for line breaks for the autofill feature by changing get_text_size
to something like this
def get_text_size(self, font_filename, font_size, text):
total_size = [0, 0]
lines = text.split('\n')
for line in lines:
font = ImageFont.truetype(font_filename, font_size)
line_size = font.getsize(line)
total_size[0] = max(total_size[0], line_size[0])
total_size[1] += line_size[1]
return tuple(total_size)
The reason it is not height=y-text_height is because that condition is already taken care of by the 'bottom' alignment. This condition is the normal graphics condition of the upper right-hand corner being the origin of the graphic box.
I'm not sure if I understand what you mean. Take this example...
img = ImageText((250, 250), background=(222, 222, 222, 255))
img.write_text_box(
(0, 0),
'This is a phrase',
box_width=200,
font_filename=example_font,
font_size=20,
color=(50, 50, 50),
place='left',
position='top'
)
+1 fir fnanni-0's comment, I would expect the same thing. I love using your snippet though, saves me a lot of time tinkering with all the possible cases.
I gotta say I'm loving this experience, this is the first code I ever do that got this much attention 😅
I'll work on this in the next few days and see if I can add your inputs.
Well done!
Any chance the text box handles new lines in text?
alright, I just added one more function:
def write_multi_line_text_box(self, xy, text, box_width, font_filename,
font_size=11, color=(0, 0, 0), place='left',
justify_last_line=False, position='top',
line_spacing=1.0):
x, y = xy
height = 0
for l in text.splitlines(True):
w, h=self.write_text_box((x, y+height), l, box_width, font_filename,
font_size, color, place,
justify_last_line, position,
line_spacing)
height+=h
if l=="\n":
height+=self.get_text_size(font_filename, font_size,"dummy")[1]*line_spacing
return (box_width, height - y)
I'm not sure if I understand what you mean. Take this example...
img = ImageText((250, 250), background=(222, 222, 222, 255)) img.write_text_box( (0, 0), 'This is a phrase', box_width=200, font_filename=example_font, font_size=20, color=(50, 50, 50), place='left', position='top' )
Fixed this by moving height += text_height
to the end of its loop in write_text_box
.
justify option not working properly with RTL languages like Arabic/Persian
I'm not sure if I understand what you mean. Take this example...
img = ImageText((250, 250), background=(222, 222, 222, 255)) img.write_text_box( (0, 0), 'This is a phrase', box_width=200, font_filename=example_font, font_size=20, color=(50, 50, 50), place='left', position='top' )
Change this:
if position == 'middle':
height = (self.size[1] - len(lines)*text_height + last_line_bleed)/2
height -= text_height # the loop below will fix this height
elif position == 'bottom':
height = self.size[1] - len(lines)*text_height + last_line_bleed
height -= text_height # the loop below will fix this height
else:
height = y
to this:
if position == 'middle':
height = (self.size[1] - len(lines) * text_height + last_line_bleed) / 2
elif position == 'bottom':
height = self.size[1] - len(lines) * text_height + last_line_bleed
else:
height = y
height -= text_height # the loop below will fix this height
when the position was top the height not had -= text_height
, so you never rest text_height
and it creates the space between your text and the top
variable "height" is undefined in the else condition.