# Composing Images and Text with Python

> Use the Pillow image library to add text and icons layers to an image programmatically.

- Canonical: https://dennisokeeffe.com/blog/2021-07-22-composing-images-and-text-with-python
- Published: 2021-07-22
- Updated: 2021-07-22
- Tags: beginner, python, 100daysofpython

> This is Day 3 of the #100DaysOfPython challenge.

This post will use the [Pillow image library](https://pillow.readthedocs.io/en/stable/) to add text and icons layers to an image programmatically.

## Prerequisites

1. An image that is 1000x600 pixels.
2. Some demo icons 100x100 pixels.
3. Familiarity with [Pipenv]. See [here](https://blog.dennisokeeffe.com/blog/2020-07-04-abcs-of-pipenv) for my post on Pipenv.
4. Familiarity with [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/gettingstarted/install.html). See [here](https://blog.dennisokeeffe.com/blog/2021-07-19-hello-jupyter-notebook) for my post on JupyterLab.
5. [Open Sans Bold](https://fonts.google.com/specimen/Open+Sans) font installed to `fonts/OpenSans-Bold.ttf`.

## Getting started

Let's create the `hello-img-layers` directory and install Pillow.

```s
# Make the `hello-img-layers` directory
$ mkdir hello-img-layers
$ cd hello-img-layers
# Create a folder to place your icons
$ mkdir icons

# Init the virtual environment
$ pipenv --three
$ pipenv install pillow
$ pipenv install --dev jupyterlab
```

At this stage, add your 1000x600 image to the root directory (`hello-img-layers`) as `base_img.png` (at least that is what I am using) and add your 100x100 icons to `hello-img-layers/icons/`.

Now we can start up the notebook server.

```s
# Startup the notebook server
$ pipenv run jupyter-lab
# ... Server is now running on http://localhost:8888/lab
```

The server will now be up and running.

## Creating the notebook

Once on http://localhost:8888/lab, select to create a new Python 3 notebook from the launcher.

Ensure that this notebook is saved in `hello-img-layers/docs/<your-file-name>`.

We will create four cells to handle four parts of this mini project:

1. Load the image in.
2. Creating the destination image and adding the icon layers.
3. Creating the text layer.
4. Saving and displaying the destination image.

## Loading in the image

This section simply loads the image in var `base_img` (assuming you are following the directory structure where the notebook is in the `docs` folder).

```py
from PIL import Image
# Concatenating an image
base_img = Image.open('../base_img.png')
```

## Creating the destination image and adding the icon layers

We create the destination layer here based on the base images width and height and paste that base image back in.

Then we make use of the glob library to fetch all the paths to our icons and paste them on top of the base image.

```py
# Add in icons using a glob
import glob
from os.path import join, dirname, abspath

dst_img = Image.new('RGBA', (base_img.width, base_img.height))
dst_img.paste(base_img, (0, 0))

icons_dir = join(dirname(abspath("__file__")), '../icons')
icons = glob.glob(f"{icons_dir}/*")
for i, icon_path in enumerate(icons):
	icon = Image.open(icon_path)
	# @see https://stackoverflow.com/questions/5324647/how-to-merge-a-transparent-png-image-with-another-image-using-pil
	dst_img.paste(icon, (60 + (i * icon.width) + (i * 20), base_img.height - 100 - icon.height), icon)
```

## Creating the text layer

This layer will load our `Open Sans` font and draw that onto the image.

The `max_text_width=25` that I selected here was through trial and error. There may be a more reusable way to handle this for different sized images.

```py
import os

# Add your own font in
font = ImageFont.truetype(os.path.join('fonts/OpenSans-Bold.ttf'), 58)

import textwrap
text = "Sample Text that is too long but let us write a bunch anyway"
print(dst_img.width)
max_text_width = 25
wrapped_text = textwrap.wrap(text, width=max_text_width)

# setup up a variable to invoke our text method
draw = ImageDraw.Draw(dst_img)

for i, line in enumerate(wrapped_text):
		# draw.text((0, height - (i * 20)), line, (0, 0, 0), font=font)
		draw.text((60, 100 + (i * 80)),line,(255,255,255),font=font)
```

## Saving and displaying the destination image

Finally, we will save and display our base image.

```py
dst_img.save('../dest_img.png')

# Display the image inline
display(Image.open('../dest_img.png'))
```

![Outcome](https://cdn.dennisokeeffe.com/assets/2021-07-22-composing-images-and-text-with-python/1-outcome-img.png)

## Summary

Today's post demonstrated how to use the `Pillow` package to add text and icons layers to an image programmatically.

This will be the technique I use for helping to build out my blog post main images (including this one!).

## Resources and further reading

1. [The ABCs of Pipenv](https://blog.dennisokeeffe.com/blog/2020-07-04-abcs-of-pipenv) for the minimum you will need.
2. [Hello, JupyterLab](https://blog.dennisokeeffe.com/blog/2021-07-19-hello-jupyter-notebook).
3. [Pillow image library](https://pillow.readthedocs.io/en/stable/)
