On a recent yak-shaving exercise, I’ve been playing with Pillow , an imaging library for python. I’ve been creating some simple graphics: a task for which I usually use PGF or Ti k Z , but those both require LaTeX. In this case, I didn’t have a pre-existing LaTeX installation, so I took the opportunity to try Pillow, which is just a single pip install .
Along the way, I had to create a regular tiling with Pillow. In mathematics, a tiling is any arrangement of shapes that completely covers the 2D plane (a flat canvas), without leaving any gaps. A regular tiling is one in which every shape is a regular polygon that is, a polygon in which every angle is equal, and every side has the samelength.
There are just three regular tilings of the plane: with squares, equilateral triangles, and regular hexagons. Here’s what they look like, courtesy of Wikipedia :



In this post, I’ll explain how I reproduced this effect with Pillow. This is a stepping stone for something bigger, which I’ll write about in a separatepost.
If you just want the code, it’s allin a script you candownload.
CoordinatesystemsTo do any drawing, first we need to establish a coordinatesystem.
The usual ( x , y ) coordinate system has two perpendicular axes. There’s an origin at (0, 0), and values increase as you move bottom-to-top,left-to-right.
In Pillow, this is flipped vertically: an increase in the vertical axis means moving down, not up. The origin is the top left-hand corner of an image, and the image canvas sits below and to theright.

Practically speaking, this doesn’t change much but it’s worth noting the difference, or drawings can behave in a confusingmanner.
Drawing apolygonOnce you have a coordinate system, a polygon can be specified as a list of coordinate points: one for every vertex. This is a list of 2-tuples in Python, which looks very similar to mathematical notation. For example, arectangle:
rectangle = [(0, 0), (0, 30), (100, 30), (100, 0)]In Pillow, an image is represented by an instance of the Image class . We can draw shapes on the image using the ImageDraw module , passing it a list of coordinate points. For example, to draw this rectangle on a blankcanvas:
from PIL import Image, ImageDraw # Create a blank 500x500 pixel image im = Image.new('L', size=(500, 500)) # Draw the square ImageDraw.draw(im).polygon(rectangle) # Save the image to disk im.save('rectangle.png')We can call this draw(im) function as many times as we like. So if we had an iterable that gave us coordinates, we could draw multiple shapes on thecanvas:
for coords in coordinates: ImageDraw.draw(im).polygon(coords)So now we need to write some code that provides us with thesecoordinates.
A squaregridBecause a square corresponds so neatly to the coordinate system, it’s a good place to start. Let’s start by thinking about a single point ( x , y ): suppose this is the top left-hand corner of a unit square, and then we can write down the other three vertices of thesquare:

We can get these points ( x , y ) by iterating over the integer coordinates of the canvas, likeso:
def generate_unit_squares(image_width, image_height): """Generate coordinates for a tiling of unit squares.""" for x in range(image_width): for y in range(image_height): yield [(x, y), (x + 1, y), (x + 1, y + 1), (x, y + 1)]I’m using yield to make this function into a generator . This allows me to efficiently compute all the coordinates required, even when I have many shapes. Iteration is a very powerful feature in Python, and if you’re not familiar with it, I recommend this old PyCon talk as a goodintroduction.
To create bigger squares, we scale the coordinates in bothdirections:
def generate_squares(image_width, image_height, side_length=1): """Generate coordinates for a tiling of squares.""" scaled_width = int(image_width / side_length) + 1 scaled_height = int(image_height / side_length) + 1 for coords in generate_unit_squares(scaled_width, scaled_height): yield [(x * side_length, y *