Drawing images consisting of all possible RGB colours!

Posted on Sat 22 November 2014 in fun

This is also an old, but fun project I did 8 months ago, inspired by the video that József Fejes posted on his blog in March: All RGB colors in one image. It was part of the challenge posted here, where people had to write code that makes colorful images. Fun, fun, fun!

The challenge has already finished when I saw it, but anyway, it sounded interesting and I wanted to see what I can come up with.

A bit about colours

There's a lot of colours out there! If you look into one pixel on your screen and concentrate really really hard, you will notice that it can (probably) display $16777216$ different colours! Here we are talking about $24$-bit colours, where each one is represented as a combination of red ($8$ bits), green ($8$ bits) and blue ($8$ bits) colours. $8$ bits can take $256$ different values, so $256 \times 256 \times 256 = 16777216$. And that's a lot! If you wore a different coloured shirt every minute, it would take you almost $32$ years to wear all of the possible $24$-bit RGB colours. I know you wanted to know that.

The result was colorful!

Below you can see what I got in the end.
Colours final!

In this case the image was $640\times480px$, which means it only contained $307200$ different colours, that is, only around $1.831054687\%$ of all possible $24$-bit RGB colours. OHNOES!

Make it bigger!

The next generated image was a bit bigger. It was $1088\times1920px$ big, containing $2088960$ different colours, which is around $12.451171875\%$ of all the colours we wanted to use.

Click to see it bigger!
Colours final!

That's better. Why $1088$ you ask? Because the implementation was a quick hack and required the dimensions to be divisible by $16$. That's what you get when you want to see the results as quickly as possible. But you know what's cool? Each colour in that image is used only once and it would take you almost $4$ years to wear shirts in all of those colours if you wore each one only for a minute!

A video would be even more colorful!

The next obvious thing that needed to be done was a video of how this image was generated. Here are four frames from the first image shown in this post being generated:

Colours 30000! Colours 100000!

Colours 170000! Colours 230000!

Having limited time, I decided that $12.451171875$ is roughly equal to $100$ and that a HD video will be enough to call this project done. So, a frame was saved after generating every so many pixels of the $1088\times1090px$ image, the frames were turned into a video and it was uploaded to YouTube to live there happily ever after.

The end?

Enter savfk!

It wasn't the end! After uploading the video on YouTube, my colleague Saverio messaged me and said:

I did a 2:30 thingy on purpose for your colour stuff!

Turns out he makes music (which I didn't know) and he made real music for the crazy colour snake video!! The music fit perfectly and the result can be seen below.

Now the project was finally complete! Yay!

If you want to see other videos of the images being generated, here's a few (but without music): a small one, the 640x480px version, the 3 and a half hour long version and the

HD version.

But, but, all of the colours??

Yes, it will work with all of the colours if you have the time to generate the frames. That's why I'm providing the code below!

Code

It's written in python and is again not cleaned up because I'm too lazy to do that. If you want to use it to generate an image really using all of the colours, the dimensions of it should be for example $4096\times4096px$. Just set the height and width variables to $256$, and that should work. It could also not work, I don't know, this was written a long time ago. :P

And I forgot to mention how it actually works! For the traversing part, it is just a randomized DFS algorithm. Every time when a pixel get visited, a colour is chosen randomly and it is used if it is less than a certain distance (Euclidean distance in RGB space) from the previously used colour. If it isn't, another colour gets chosen randomly. If after a number of random choices the colour still isn't less than the threshold distance from the previous colour, then the threshold gets increased. And that's it! Magic!

Oh, and BTW, you should have a folder called generated where all the images will get saved to.

colours.py download
import cv2
import numpy as np
import math
import random
from sets import Set
  
dr = [-1, -1, -1, 0, 0, 1, 1, 1]
dc = [-1, 0, 1, -1, 1, -1, 0, 1]
  
height = 68 #1080/16
width = 120 #1920/16
  
field = np.zeros((height * 16, width * 16, 3), dtype = "uint8")
visited = np.zeros((height * 16, width * 16), dtype = "bool")
  
total_colours = 0
colours = []
  
def distance(c1, c2):
    return math.sqrt((c1[0] - c2[0]) * (c1[0] - c2[0]) +
                     (c1[1] - c2[1]) * (c1[1] - c2[1]) +
                     (c1[2] - c2[2]) * (c1[2] - c2[2]))
  
  
def fill(queue, prev_colour):
      
    while (len(queue) > 0):
  
        current_point = queue.pop(0)
  
        rand_it =  list(range(len(dr)))
        random.shuffle(rand_it)
  
        for i in xrange(len(dr)):
            r = current_point[0] + dr[rand_it[i]]
            c = current_point[1] + dc[rand_it[i]]
  
            if (r < 0 or r >= field.shape[0]):
                continue
              
            if (c < 0 or c >= field.shape[1]):
                continue
  
            if (visited[r, c]):
                continue
  
            if ((r,c) in queue):
                continue
  
            queue.insert(0, (r, c))
  
        r = current_point[0]
        c = current_point[1]
  
        field[r, c, 0] = prev_colour[0]
        field[r, c, 1] = prev_colour[1]
        field[r, c, 2] = prev_colour[2]
  
        visited[r, c] = True
  
        print len(colours)
  
        colours.remove(prev_colour)
        if (len(colours) > 0):
            next_colour = colours[random.randint(0, len(colours) - 1)]
              
            tries = 0
  
            min_dist = 30
  
            while (distance(prev_colour, next_colour) > min_dist):
                next_colour = colours[random.randint(0, len(colours) - 1)]
                tries += 1
  
                if (tries > 0.1 * len(colours)):
                    min_dist += 0.1
  
            prev_colour = next_colour
  
        cv2.imwrite("generated/img_" + str((total_colours - len(colours))).zfill(10) + ".png", field)
  
cv2.namedWindow("field", flags = cv2.cv.CV_WINDOW_NORMAL)
  
for r in xrange(16):
    for c in xrange(16):
        for i in xrange(height):
            for j in xrange(width):
                colours.append((int((float(r * 16 + c) / 256) * 255),
                                int((float(i) / height) * 255),
                                int((float(j) / width) * 255)))
      
  
    print r, "/", 16
  
total_colours = len(colours)
  
prev_colour = colours[random.randint(0, len(colours))]
  
current_point = (random.randint(0, field.shape[0]),
                 random.randint(0, field.shape[1]))
  
queue = []
  
queue.append(current_point)
  
fill(queue, prev_colour)
  
cv2.imshow("field", field)
cv2.waitKey(0)

The code is also available on github.

Thanks for scrolling through!