I recently came across an old Stack Exchange post again, Images with all colours. If you haven’t seen it, I really recommend checking out the answers. Some of them look really nice, and they’re constructed algorithmically.
The summary of the question is as follows. As a challenge; we need to generate images that contain every single RGB color, no color should be missing and none should be repeated.
In this post I will attempt to generate such images, walk you through my method and hopefully provide some example pictures. Rather than generation a rainbow-like structure with colour similarity (like most answers in the Stack Exchange link), I will be trying to replicate a given image using the whole colour palette.
In order to replicate an image; we’re going to need to load it as a target, and create another one with the same dimensions. This will be like our canvas.
let target = loadImage("target.jpg");
let canvas = createImage(target.width, target.height);
Filling the canvas with the palette
Before arranging pixels to look like our target, we need to have those pixels in the first place. So let’s loop through all the colours and put them to our canvas.
int initX = 0;
int initY = 0;
for (int r = 0; r < 256; r++) {
for (int g = 0; g < 256; g++) {
for (int b = 0; b < 256; b++) {
canvas.setPixel(initX, initY, color(r, g, b));
initX++;
if (initX == target.width()) {
initY++;
initX = 0;
}
}
}
}
This will give us a canvas that looks roughly like this.
It might be looking too bland or too uniform but trust me, that image has more than 16 million unique colours.
Arranging the pixels
In order to replicate our target image, we’re going to use a tiny hill climber. You can read up on Hill Climbing here. Here’s the outline of what we’re going to do.
- Pick two coordinate pairs randomly. Let’s call them (x1, y1) and (x2, y2).
- Check the colour-distance of our pairs
- Check what the colour-distance would become if you swapped the coordinates.
- If our canvas would become closer to the target, swap them. If not, do nothing.
- Repeat
Tiny disclaimer
While this post uses a psudeo-code language to describe the logic, the real program that generated the images is written in C++.
It also has an implementation in Javascript but it wasn’t used due to performance converns.
<script>
var imgTarget;
var imgBest;
function setup() {
createCanvas(256, 256);
imgTarget = loadImage('./stallman.png');
imgBest = createImage(256, 128);
imgBest.loadPixels();
let i = 0;
for (let y = 0; y < 128; y++) {
for (let x = 0; x < 256; x++) {
let r = i << 3 & 0xF8;
let g = i >> 2 & 0xF8;
let b = i >> 7 & 0xF8;
imgBest.set(x, y, color(r, g, b));
i += 1;
}
}
imgBest.updatePixels();
}
function runIteration() {
imgBest.loadPixels();
let x1 = random(0, 256);
let x2 = random(0, 256);
let y1 = random(0, 128);
let y2 = random(0, 128);
let pix1 = imgBest.get(x1, y1);
let pix2 = imgBest.get(x2, y2);
let pix1T = imgTarget.get(x1, y1);
let pix2T = imgTarget.get(x2, y2);
let dist1 = dist(pix1[0], pix1[1], pix1[2], pix1T[0], pix1T[1], pix1T[2]);
let dist2 = dist(pix2[0], pix2[1], pix2[2], pix2T[0], pix2T[1], pix2T[2]);
let dist3 = dist(pix1[0], pix1[1], pix1[2], pix2T[0], pix2T[1], pix2T[2]);
let dist4 = dist(pix2[0], pix2[1], pix2[2], pix1T[0], pix1T[1], pix1T[2]);
if (abs(dist3 + dist4) < abs(dist1 + dist2)) {
imgBest.set(x1, y1, pix2);
imgBest.set(x2, y2, pix1);
}
imgBest.updatePixels();
}
function draw() {
for (var i = 0; i < 200; i++) {
runIteration();
}
image(imgTarget, 0, 0);
image(imgBest, 0, 128);
}
</script>