Writing text on an image (Python + PIL). Different ways to assemble multiple lines.

I covered the basics in one of my articles on Video generation with Python, and today I want to write about different ways to assemble text on the image so it does not look too bland.

1 – Arrange \n

To write a long text that has to be on multiple lines, you need to arrange the lines. To do that you need to set a maximum_length of the current_line, then iterate the array of the words of the text and accumulate each until current_length of current_line reaches the maximum_length.

  1. Iterate the words of the split text
  2. Arrange the words in lines until the length of the line reaches the maximum length of the line

So the code for that is pretty simple:

current_length = []
current_line = []
result = []
words = text.split()
for word in words:
   word_length, word_height = Imagedraw.Draw(you_img).textsize(word, font)
   if sum(current_length + [word_length]) >= maximum_length:
      # new line
      result.append(current_line)
      current_line = [word]
      current_length = [word_length]
   else:
       # add to the old line
       current_line.append(word)
       current_length.append(word_length)

# and then in case we have a trailing line
if current_line: result.append(current_line)

result = "\n".join([" ".join(x) for x in result)]

2 – Positions

Now each text has its formula to calculate the position on the list. It depends on the length of each text (after you arrange the \n’s) and the amount_of_texts. The general formula is:

The new variable here is text_proportions – this you need to derive from the lengths of the texts and the proportion between this values of all the texts. It depends heavily on the resolution of the picture. So far I failed to get a definite formula so for a limited amout of the most populat resolution I just hardcoded the value.


Now let’s apply this knowledge

and assemble text on the frame!

Here are two lines – in German and Russian (this is basically a “phrase in foreign language and its translation” frame).

Option 1: One under another (+ different style)

Option 1: Two lines – one under another – 1
Option 1: Two lines – one under another – 2

Option 2: Just one line in the center (+ different style + with a transparent background)

Option 2: Just one line in the center – 1

To style the text I would recommend these options:

  • Font size
  • Font color
  • Font style – here I used italic, but I did not arrange it with PIL – I used an italic version of the font. It’s much easier and more versatile.
Option 2: Just one line in the center – 2

You can also add a background – it adds up to the structure of the frame, makes it easier to read.

To do that I make a white_<image_name>.jpg version of the background once and use it instead of the original background when necessary. The code for adding this transparent layer is fairly simple. Make a class of the background:

class Background():
    def __init__(self, filename):
        self.file = filename
        self.file_white = Background.background_name_to_white(filename)
        print(self.file_white)
        if not Path(self.file_white).exists():
            self.make_transparent_rect()

    @classmethod
    def background_name_to_white(cls, backgr):
        return os.path.join("/".join(backgr.split("/")[:-1]), f'white_{backgr.split("/")[-1]}')

    def make_transparent_rect(self):
        # background, new_place
        img = Image.open(self.file).convert("RGBA")
        strip_width, strip_height = img.size
        rec_width = strip_width * 0.93
        rec_height = strip_height * 0.93

        overlay = Image.new('RGBA', img.size, TINT_COLOR + (0,))
        draw = ImageDraw.Draw(overlay)  # Create a context for drawing things on it.
        draw.rectangle(((strip_width * 0.07, strip_height * 0.07), (rec_width, rec_height)),
                       fill=TINT_COLOR + (OPACITY,))

        # Alpha composite these two images together to obtain the desired result_1.
        print(img.mode)
        print(overlay.mode)
        img = Image.alpha_composite(img, overlay)
        img = img.convert("RGB")  # Remove alpha for saving in jpg format.
        img.save(self.file_white)
        # img.show()

        return
Option 2: Just one line in the center – 3

There are many other options, these are what I use in my work.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.