The Pillow library is a popular imaging library available for Python. Among its many utilities is the ability to use custom fonts for text manipulation. In this article, we’re going to take a look at just how easy using a custom font with Pillow can be.
Pillow’s text manipulation toolsets can be used for general text manipulation or analytical purposes like measuring text size and attributes. Pillow’s text utilities cater for more visual use cases and shouldn’t be regarded as a text processing or natural language processing toolset.
TL;DR – Using Custom Fonts in Pillow
This article will walk you through the basics of loading a custom font in the Pillow imaging library for Python. For those that just want a copy/paste solution check out the code below.
# Load a OpenFont format font from a local directory as an ImageFont object # In this case, a TrueType font using the truetype method. font = ImageFont.truetype(font='path/to/your/font.ttf', size=42) # Create a new image onto which the text will be added image = Image.new(mode='RGB', size=(1024, 256), color='#ffaa00') # Create an ImageDraw object onto which the font text will be placed draw = ImageDraw.Draw(im=image) # Draw the text onto our image draw.text(xy=(512, 128), text="overcoded - @}", font=font, fill='black', anchor='mm') # Open the image via system standard image viewer image.show()
This code isn’t very flexible but demonstrates the basic steps involved in using a custom font with the Pillow imaging library:
- Load a FreeType compatible font (.ttf, .otf, etc.)
- Create a Pillow
Image
object to use as a background for the text - Create a Pillow
ImageDraw
object to access the text drawing features of Pillow - Add the custom text to the
ImageDraw
object - Use the
Image.show()
method to view results in system default image viewer
There are plenty of options that can be specified during these steps and several notable gotchas that might pop up. The remainder of this article will attempt to cover the basics as well as advise on how to handle some common issues that are known to pop up.
Project Structure + Setup
For the examples in this article, I’ll be using the Source Code Pro font available from Google Fonts. I have downloaded it and saved it to a local fonts
subdirectory in the project folder I created for these examples. Below is the structure of the project.
. └── ProjectRoot ├── fonts │ └── SourceCodePro-Bold.ttf └── pillow_custom_font_example.py
Pillow is an actively maintained fork of the now outdated Python Imaging Library (PIL). Pillow can be installed using pip as such: pip install Pillow
. Refer to the official Pillow installation docs for more guidance on various system installs, upgrading pip, and etc.
Note: Pillow and PIL cannot coexist within the same Python installation. You must uninstall PIL if you have it installed before Pillow will be functional.
The ImageFont Module
Among Pillow’s 30ish modules, the ImageFont module allows easy specification of custom fonts. As per the documentation, this module provides utility for bitmap fonts and fonts within the FreeType family (OpenType, TrueType, etc.).
The ImageFont module provides custom font support to be used along with the ImageDraw
module’s text
method. We’ll get to the ImageDraw.text()
method shortly but for now let’s start by using the ImageFont
module’s ImageFont
class to specify our custom Source Code Pro
font.
Supported Font Types
Fonts come in a variety of types including bitmap, raster, vector, TrueType, OpenType, and several others. Bitmap fonts are mostly outdated but can still be found out in the wild roaming around sometimes.
Pillow has supported bitmap fonts since its inception and modern releases support OpenType and TrueType with native ImageFont.truetype()
function. The ImageFont.load_path()
and ImageFont.load()
functions will only work with bitmap fonts containing related glyph data and will generate errors for other formats.
from PIL import ImageFont import os # Define the path to our custom .ttf font free_type_compatible_font = os.path.abspath( os.path.join( os.path.dirname(__file__), 'fonts/SourceCodePro-Black.ttf' ) ) # Open as TrueType native font = ImageFont.truetype(font=free_type_compatible_font, size=42) >>> <PIL.ImageFont.FreeTypeFont object at 0x0000021736AE94C0> # Define the path to our custom .otf font free_type_compatible_font = os.path.abspath( os.path.join( os.path.dirname(__file__), 'fonts/Montserrat-Regular.otf' ) ) # Open as OpenType font font = ImageFont.truetype(font=free_type_compatible_font, size=42) >>> <PIL.ImageFont.FreeTypeFont object at 0x00000249E03C94C0>
As you can see, both the .otf
and .ttf
fonts load successfully into memory via the ImageFont. Let’s see what happens when we try to load one of these fonts with the .load() or load_path() methods:
# Load using the load method font = ImageFont.load(free_type_compatible_font) >>> OSError: cannot find glyph data file # Load using the load_path method font = ImageFont.load_path(free_type_compatible_font) >>> OSError: cannot find font file
While the last error might seem a bit odd, the Pillow documentation lends the following explanation:
Same as ~PIL.ImageFont.load, but searches for a bitmap font along the Python path. Raises OSError if the file could not be read.
I got a little tripped up the first time I got the “cannot find font file” error when trying to [incorrectly] load a .ttf file with the load_path() method. If you see this error just know that it can result from either a missing file or a file not being read properly.
Image & ImageDraw Objects
Much of the utility provided by the Pillow library revolves around the use of the Image class. This class is a rollover from the original PIL library and one of the primary reasons one can’t use the Pillow library if PIL is installed in the same environment. From the documentation:
The Image module provides a class with the same name which is used to represent a PIL image. The module also provides a number of factory functions, including functions to load images from files, and to create new images.
The Image module, being so fundamental to the utilities of Pillow, has a LOT of functionality. We aren’t going to cover much of that here—just know that you can do a lot other than simply load
, save
, and show
images (our use case.) Let’s create an image object to use as a background to display our text:
# Create an 1024px x 256px image in solid yellow RGB mode image = Image.new(mode='RGB', size=(1024, 256), color='#ffaa00')
Our next step is to create an ImageDraw object that will allow us to use our custom font to create some text. I’ll go ahead and load our font again since it’s been a minute since the top of this article.
# Load the font from local storage font = ImageFont.truetype(font='fonts/SourceCodePro-Regular.ttf', size=42) # Create an ImageDraw object onto which the font text will be placed draw = ImageDraw.Draw(im=image) # Draw the text onto our image draw.text(xy=(512, 128), text="feeling overcoded? - @}", font=font, fill='black', anchor='mm') # Display the image using system standard photo viewer (it's a .png file) image.show()
I’ve kind of hit the gas peddle here and done several things. Let’s break this one down a bit.
- Font – the font we loaded from local storage
- The ImageDraw object, bound to our Image background
- The ImageDraw.text function sets the size, text, font, and color of our text
- The command that displays the end result
Note: I’m using the anchor=”mm” argument in the draw.text() method. This is a built-in helper method that controls the positioning (anchoring) of text. It’s a super-convenient group of functions that saves a ton of time placing text on images. Check out the full documentation for how to use text anchors with Pillow. For now, just know that “mm” means center the text in the middle. Here’s what all our hard work has created:
Keep in mind this is a very basic example of what the Pillow ImageFont class can achieve when coupled with other utilities the Pillow library provides. Pillow can help with text wrapping, creating ASCII text, or just about any other application involving the manipulation of text, images, or both that one can imagine.
Final Thoughts
Pillow is the defacto imaging library for more artistic applications in my opinion. The OpenCV wrapper for Python is certainly an industrial-strength imaging library with a lot to offer. In my experience, OpenCV is more suited towards imaging data applications such as machine learning, statistical analysis, or processing large swaths of visual data. Pillow is just one of the many libraries that makes Python one of the most popular programming languages out there. I find few other languages capable of achieving such a high workload with such minimal syntax.