Using Pillow to center multi-line text without it going off the image

For context, I am trying to make a ‘sigma-male’ meme generator. The idea is I can feed in a load of pre-defined ‘sigma-male’ jokes/quotes and overaly it on an image of a ‘sigma-male’. The format of these pictures should be that in the center of the image there will be a line saying ‘Sigma-Male Rule #X’ and underneath there would be some bad life advice e.g. ‘Don’t be part of the problem, be the whole problem’.
Here is the picture I am starting with (note that for my purposes all images are the same size, 1080×1080. So issues of variable image size shouldn’t be a problem):

Image of character

But when I try to add the ‘life-advice’ I end up with this:
Character with text

As you can see, the text runs straight off of the image. One fix I have tried is by breaking it up into several lines: Character with text after breaking line up

Theoretically, this could be useful but the problem is I would manually have to adjust each different quote, which would stop this from being an automatic procedure.

Ideally, what I would like is an image like this: Desired output

Something else I would need to account for is if I have a short quote that’s only one line, how can I also guarentee that this will be centered appropriatley relative to the ‘Sigma Male Rule#X

Here is my code:

import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
import textwrap

my_image = Image.open("Bateman1Raw resized.jpg")
title_font = ImageFont.truetype('Bebas-Regular.ttf', 100)


title_text = "Sigma Male Rule #1"
text_test='Don\'t be part of the\n problem, be the whole problem'


image_editable = ImageDraw.Draw(my_image)
image_editable.text((200,400), title_text, (255, 255, 255), font=title_font,stroke_width=2,stroke_fill='black')
image_editable.text((0,500), words,(255, 255, 255),align='center',font=title_font,stroke_width=2,stroke_fill='black')

my_image.save("result.jpg")

>Solution :

ImageFont has getlength(text) which you can use to split text to lines.

I first split all text to words and I add next word to text and check getlength(text). If it is shorter then I check with next word. If it is longer then I add this line to results and start with empty text to create new line.

width, height = my_image.size

text_test = "Don't be part of the problem, be the whole problem"

all_words = text_test.split(' ')
all_lines = []
line = []

while all_words:
    word = all_words[0]
    new_text = ' '.join(line + [word])
    #print('>', word, new_text)

    if title_font.getlength(new_text) > width:
        # if longer then keep shorter line and start with new line
        all_lines.append(' '.join(line))
        line = []
    else:
        # if shorter then add word to line, and remove word from list 
        line += [word]
        all_words = all_words[1:]
   
# add last line to results 
if line:
    all_lines.append(' '.join(line))
print(all_lines)    

And next I can use for-loop to draw lines:

x = 0
y = 500
for text in all_lines:
    #x = (width - title_font.getlength(text))//2
    image_editable.text((x, y), text, (255, 255, 255), align='center', font=title_font, stroke_width=2, stroke_fill='black')
    y += 100

Full working code

from PIL import Image, ImageDraw, ImageFont

my_image = Image.open("lenna.jpg")
my_image = my_image.resize((1080,1080))

width, height = my_image.size

title_font = ImageFont.truetype('Arial.ttf', 100)

text_test = "Don't be part of the problem, be the whole problem"

all_words = text_test.split(' ')
all_lines = []
line = []
while all_words:
    word = all_words[0]
    new_text = ' '.join(line + [word])
    print('>', word, new_text)
    if title_font.getlength(new_text) > width:
        all_lines.append(' '.join(line))
        line = []
    else:
        line += [word]
        all_words = all_words[1:]
    
if line:
    all_lines.append(' '.join(line))
print(all_lines)    

image_editable = ImageDraw.Draw(my_image)

title_text = "Sigma Male Rule #1"
image_editable.text((200, 400), title_text, (255, 255, 255), font=title_font, stroke_width=2, stroke_fill='black')

y = 500
for text in all_lines:
    image_editable.text((0, y), text, (255, 255, 255), align='center', font=title_font, stroke_width=2, stroke_fill='black')
    y += 100

my_image.save("result.jpg")

my_image.show()

Image Lenna from Wikipedia

Result:
enter image description here

Leave a Reply