I have some code which opens an image and prints a representation of it to the terminal using ANSI escape sequences. It takes each pixel in a downsized version of the image and prints a character with a colour that matches it.
Not all terminals support RGB output however, so I wanted to implement other colour modes such as 4-bit. I did this with a dictionary lookup table containing the ANSI codes and the RGB values they produce: {(r, g, b) : code, ...
. I then print the value closest to the pixel colour in terms of euclidean distance.
from PIL import Image, ImageOps
from math import sqrt, inf
def match_colour_to_table(table, colour):
best_colour = None
best_diff = inf
for table_colour in table.keys():
# Calculate the distance between the two colours
delta = (c - t for c, t in zip(colour, table_colour))
diff = sqrt(sum((p ** 2 for p in delta)))
if diff < best_diff:
best_colour = table_colour
best_diff = diff
return table[selected_colour]
def term_pixel_4bit(colour):
colour_table = {
(0, 0, 0) : 40,
(255, 0, 0) : 41,
(0, 255, 0) : 42,
(255, 255, 0) : 43,
(0, 0, 255) : 44,
(255, 0, 255) : 45,
(0, 255, 255) : 46,
(255, 255, 255) : 47,
}
code = match_colour_to_table(colour_table, colour)
return f"\033[;{code}m \033[0m"
def term_pixel_256(colour):
match_colour_to_table(TABLE_256, colour)
return f"\033[48;5;{code}m ";
def print_image(image, size):
width, height = size
image = image.resize(size, resample=1).convert("RGB")
# Print each row of characters
for y in range(height):
row = [term_pixel_256(image.getpixel((x, y)))
for x in range(width)]
# Reset to avoid trailing colours
row.append("\033[0m")
printf("".join(row))
This approach works very well for 4-bit colour, but far less so for 256-colour. I converted the json data at https://jonasjacek.github.io/colors/data.json to a dictionary.
TABLE_256 = {
(0, 0, 0) : 0, (128, 0, 0) : 1, (0, 128, 0) : 2,
...
(228, 228, 228) : 254, (238, 238, 238) : 255
}
It does produce a nice-looking result, but understandably it takes a while to compute. I'm certain there is a much faster way of doing this, but I'm not entirely sure where to begin. Any help would be much appreciated.
Just in case it's needed, here's the calling site:
path = os.path.join(os.path.dirname(__file__), "")
image = Image.open(path + sys.argv[1])
print_image(image, (100, 50))