Learning Python and being creative. Making art with code.

What do words look like as colours? What would Shakespear’s sonnets look like as colours? This mini project renders text as colours using Python and saves them in a grid as an image.

All titles of Shakespear’s sonnets

What is this?

It started as a bit of fun late one night on codepen, toying with ways to make images with code. In a really simple way, how can a colour represent a word? Easy… just convert a word to a colour code right?

As it turns out, it wasn’t so easy. Like most developers, I started looking for a JS library that already did this, wishful thinking as it’s not really that useful. I thought this from Charlie Coleman was pretty cool, but I was more interested in showing a more granular representation. A color for each word, no matter how many letters or words.

Skip to see the code on github

Why? And Why Python?

Like many developers I have met along the way, my degree wasn’t in a technical subject. In fact, it was Art. More specifically, sculpture and illustration, but, even though my career is in web development, I still have a creative practice. Code really is just another material to work with. (And no, I’m not saying code is art, because that’s silly.)

Why Python? Well, JS was good, but it was slow and getting slower. I heard python was really fast and I planned to be throwing hundreds of words at it. (Please don’t judge my amateur code too much). I don’t really know much Python, so this was a great excuse to learn.

Planning

After the Codepen version, I came to decide the following:

  • The colours created from the words should get the colour values using every letter, not just the first few.
  • The ‘canvas’ on which the colours sit should be adjustable, I should be able to print a huge image of a whole book, or a small image of my name.
  • The final rendering should be an image, not a web view.

An example: the letters ‘Abcd’ as a word

Given ‘Abcd’ each letter of the word is assigned a value to where it sits in the alphabet like this:

# position   0   1   2   3   4   5  ...
alphabet = ["a","b","c","d","e","f",...]

As RGB values only need 3 numbers, we use the remaining letters to increase the values of the numbers representing the first three letters. So…

  • Word is ‘Abcd’
  • Values are [0,1,2]
  • Remaining values are [3]
  • Add [3] to value 1.
Some small word examples

What happens with large words?

This is where it started to get tricky. I want all the letters in a word to affect the 3 RGB color values, leaving them out just seemed dodgy and not fair. So using a zip cycle, we can iterate and add the all values after the third letter accordingly, e.g.:

  • Word is ‘Abcdefghij’
  • Values are [0,1,2]
  • Remaining values are [3,4,5,6,7,8,9]
  • For each of the remaining values, go through the initial values and add them to the 3 RGB values one at a time so,
  • Remaining value 3 is added to val[0]
  • Remaining value 4 is added to val[1]
  • Remaining value 5 is added to val[2]
  • Remaining value 6 is added to val[0]
  • Remaining value 7 is added to val[1]
  • Etc

That zip cycle function saved me a lot of trouble:

for i, j in zip(cycle(range(len(colors))), additions):
colors[i] += j
colors[i] = int(colors[i])

There are also some other nice bits in the code to ensure the values don’t exceed [255,255,255], really it was just something that came up that needed some thought. You’ll notice that in the list above, the initial values could be small, like [0,1,2]. So they are multiplied by (255 /26) or (value limit / letters in alphabet). This gives a better base to add values to. [0,9.8,19.6].

As we start adding the leftover values, they are multiplied, to ensure we are don’t tip over 255, we divide by 26, for values above 3 but lower than 6. Then for words with more than 6 letters, they get really small values by dividing more. (x * n /26)(1/26). Here is that function:

Adding to the initial RGB values, getting smaller and smaller as the word grows
(left) E.E Cummings — I carry your heart with me (right) William Carlos Williams — The Red Wheelbarrow

What about large text inputs?

I made the canvas size a variable, so if large texts are used, you can increase the canvas size… it really will proccess anything. Here is the King James Bible:

The King James Bible — In squares

Drawing, Plotting, and Some Note-taking.

Having the colours list ready was one part, plotting the squares and drawing was another. This is pretty easy on the web, there are libraries like Masonry that have been able to position things really well. But, I had not even googled a Python image drawing library before. I found one called Pillow that did the trick.

Working out the plotting of the word squares

So why so many notes and working out? Well in the code at this point we have a list (potentially huge) of colour values, e.g.:

[[45,65,221],[36,187,35],[66,84,224]...]

This list needs to be looped over and a square drawn for each item using the values as colors. The hard part here was drawing the squares in a horizontal line and dropping down a row when we hit the end. But how do we know we are at the end? What size should the drop down value be?

Here is how that’s done with 5 words as an example:

  • Given I know how many squares I have (5) I work out what ‘grid’ I will need. For example, 5 words would require a 3 x 3 grid.
  • Now I know each row and column will consist of three squares, and I have a canvas size of 1000. 1000 / 3 will be the width and height of each inner square. And also the amount of distance I need to shift my drawing points horizontally and vertically. Nice!
  • To find what ‘grid’ I need, I created a function (grid(n,m) that takes the list of words and a list of squared numbers and works that out:
Calculating the grid

Finally, all that left is the most confusing iteration I have ever written:

Drawing the squares.

All in all, this started as a bit of fun, but I ended up learning a lot about Python and drawing with code (something I would love to do more of). One really nice feature is that the canvas can be huge and your squares a few millimeters. And thank you to my partner Ashley Burdett for helping me with the plotting math :)

Check out the code on github.