Drawing coloured squares/text in my terminal with Python
Last night, I posted a tweet wondering about the different colours of the “spool of thread” emoji, and I made an illustration to show the variety:
The emoji icons are from Emojipedia, and I’ve labelled each one with the name of the platform it comes from. Notice that I’ve colour-matched each label to the emoji, and it’s a different colour for each label – how did I pick the colours?
I already have code to extract dominant colours from images, using k‑means clustering. I was able to use the script from the end of that post to get a list of suggestions:
Problem is, I can’t visualise colours from their hex codes. Which colour is the murky brown, and which is the vibrant pink? Wouldn’t it be nicer if I could see the colours?
I found a Perl function that prints coloured blocks to the terminal using ANSI escape codes. I rewrote it in Python, and then I could see the hex code and the colour it represented:
Here’s my Python function:
def coloured_square(hex_string):
"""
Returns a coloured square that you can print to a terminal.
"""
hex_string = hex_string.strip("#")
assert len(hex_string) == 6
red = int(hex_string[:2], 16)
green = int(hex_string[2:4], 16)
blue = int(hex_string[4:6], 16)
return f"\033[48:2::{red}:{green}:{blue}m \033[49m"
The function returns a string that prints a single coloured square, which you can use and print like any other string. This won’t work in every terminal app, but it works in the one I use (iTerm2), and that’s good enough for my needs.
I don’t fully understand ANSI escape codes, but reading the Wikipedia entry while writing this post helped me understand what’s going on. The \033[…m
marks the escape code, and 48:2::r:g:b
sets the background colour of the terminal. Typing a single space draws the square of colour, and then 49
restores the default background colour.
Scrolling up on that page, I see that 38:2::r:g:b
lets you set the foreground colour, and that lets me write another interesting function:
def coloured_string(s, *, hex_string):
"""
Returns a coloured string that you can print to a terminal.
"""
hex_string = hex_string.strip("#")
assert len(hex_string) == 6
red = int(hex_string[:2], 16)
green = int(hex_string[2:4], 16)
blue = int(hex_string[4:6], 16)
return f"\033[38:2::{red}:{green}:{blue}m{s}\033[39m"
Notice that the result now ends 39
to restore the default foreground colour, not 49
. I got that wrong on my first attempt!
Combine this with something like Unicode block elements, and this could be a very powerful tool. For example, I can use a lower seven-eights block to add a gap between squares, and colour the hex swatches to match:
This is even more useful – it makes it visually obvious that, for example, the last colour would be unsuitable for text on a white background.
All this might seem like overkill for a throwaway tweet, but I’m bound to use it in other places. Now I’ve done this, I have two functions that I can drop into any project that involves colours – and unlike a library, I completely understand how they work. Writing blog post was a key part of that understanding – by explaining it to you, I first have to explain it to myself.
Being able to see colours in my terminal has been a “wouldn’t it be nice if” debugging idea for a while, but I’ve never got round to it before. Now I have, it’s a single copy/paste to start reaping the benefits, so I’m much more likely to do it – and maybe you will too.