In mylast post, I explained how I’d been using Pillow to draw regular tilings of the plane. What I was actually trying to do was get some new desktop wallpapers, and getting to use a new python library was just a nicebonus.
A while back, the Code Review Stack Exchange got a fresh design that featured, among other things, a low-contrast background of colouredsquares:

I was quite keen on the effect, and wanted to use it as my desktop wallpaper, albeit in different colours. I like using low contrast wallpapers, and this was a nice pattern to try to mimic. My usual work is entirely text-based; this was a fun way to dip my toe into the world of graphics. And a few hours of Python later, I could generate these patterns in arbitrarycolours:



In this post, I’ll explain how I went from having a tiling of the plane, to generating these wallpapers in arbitrarycolours.
In the previous post, we had three generators, which generated the coordinates for individual polygons in a tiling of the plane. We want to give each polygon its own colour, so we need another generator to create randomcolours.
One way to represent a colour in Pillow is to use the RGB colour space, and then a colour is a 3-tuple of integers between 0 and 255. For convenience, I’ve defined a namedtuple for storing colour data. So a generator of random colours from anywhere in the RGB space looks likethis:
from collections import namedtuple from random import randint Color = namedtuple('Color', ['red', 'green', 'blue']) def random_colors(): while True: yield Color(randint(0, 255), randint(0, 255), randint(0, 255))Unlike the coordinate generators, this generator is infinite: it will continue generating random colours until the system runs out of entropy (randomness). That’s useful, because we don’t know in advance how many colours we might need for a given image we just have to be careful not to try to store the entire output of this generator, or we’ll quickly run out ofmemory.
But really, we’d like to constrain the colours to a small sample of the RGB space that’s the “low-contrast” part. For my wallpapers, I typically want a few shades of the same colour, so I like to pick two colours, and have every colour fall somewhere in between. If you imagine the two colours as being points in 3-dimensional space, let’s draw a line between them and just pick colours along that line. Here’s what that lookslike:
def random_colors(color1, color2): """ Generate random colors between ``color1`` and ``color2``. Both arguments should be instances of ``Color``. """ # Get the difference along each axis d_red = color1.red - color2.red d_green = color1.green - color2.green d_blue = color1.blue - color2.blue while True: # What proportion of the line to move along proportion = random.uniform(0, 1) yield Color( red=color1.red - int(d_red * proportion)), green=color1.green - int(d_green * proportion)), blue=color1.blue - int(d_blue * proportion)) )So now we have a generator of colours, and a generator of shapes, we need to put them both together on a Pillow canvas. Python provides the zip function for combining two iterables, and we can use that to greateffect:
shapes = generate_shapes(500, 500, side_length=25) colors = random_colors(color1, color2) for shape, color in zip(shapes, colors): ...(Here generate_shapes is one of the generators we defined in the lastpost.)
Within the body of this for loop, we have a set of coordinates for the shape, and a Color tuple for the fill colour. The zip function runs until one of the iterables is exhausted, so this will run for precisely as many shapes as we need to tile theplane.
Finally, we need to actually use Pillow to draw these shapes! We can use the ImageDraw module, this time passing a fill argument to fill in the shapes we’redrawing:
from PIL import Image, ImageDraw # Create a blank 500x500 pixel image im = Image.new(mode='RGB', size=(500, 500)) # Generate the shapes and colors, and draw them on the canvas shapes = generate_shapes(500, 500) colors = random_colors(color1, color2) for shape, color