Date: 2020-06-24
I first encountered the concept when I stumbled across allrgb.com.
The objective of allRGB is simple: To create images with one pixel for every RGB color (16,777,216); not one color missing, and not one color twice.
As I assume would be the case for most developers, after the idea was presented to me I started to contemplate how I might go about transforming an image such that it meets that criteria but remains recognizable.
Find the code On my GitHub.
The thing that caught my interest about the subject was that I almost immediately came up with a technique that I figured might work, but honestly I had no idea until I put together a proof of concept.
The idea is simple enough that I can just show you an MVP:
import numpy as np
from PIL import Image
# Create a 4096x4096 version of the input image, then squash it down to a single row
= Image.open("examples/mandrill.tiff").convert("RGB")
im = im.resize((4096, 4096))
resized = np.reshape(np.array(resized), (4096*4096, 3))
src
# Generate a row containing every RGB colour (Our result array)
= np.arange(256, dtype='u1'), np.arange(256, dtype='u1'), np.arange(256, dtype='u1')
red, green, blue = np.array(np.meshgrid(red, green, blue)).T.reshape(-1, 3)
res
# Split the two images into their red, green, and blue components
= res.astype(np.uint32).transpose()
res_r, res_g, res_b = src.astype(np.uint32).transpose()
src_r, src_g, src_b
# Combine all three values into 1 to use in sorting later
= (res_r << 16) | (res_g << 8) | res_b
combined_res = (src_r << 16) | (src_g << 8) | src_b
combined_src
# Sort the result pixels (By red, then green, then blue due to how they were combined) [1]
= res[np.argsort(combined_res)]
res_sorted
# Generate a mapping of unsorted source image -> sorted source image
= np.argsort(combined_src)
mapping
# Reverse that mapping so that it is sorted souce image > source image
= np.argsort(mapping)
reverse_mapping
# reverse the sorting on the sorted result image as if it was the sorted source image
= res_sorted[reverse_mapping]
final_result
# Turn the image back into 4096x4096 and save it
= Image.fromarray(np.reshape(final_result, (4096, 4096, 3)), mode='RGB')
image 'examples/mandrill-sorted.webp') image.save(
The general idea is as follows:
Figure 1: Mandrill
Figure 2: Not the greatest reproduction, but it is recognizable.
With the concept at least showing that it could work, I started experimenting with different strategies by which the pixels should be sorted.
It turns out that red, then green, then blue is rarely a good strategy.
Note: If you actually downloaded and tested the images embeded in this page, you’d find that they actually do not contain all RGB values uniquely, but this is just because they have been compressed to reduce the page weight. (You can download full examples here)[https://www.dropbox.com/sh/gjp1wsf8ubvzfl8/AAD27FD_RgjjItuyK3eMoFdca?dl=0].
Figure 3: This example is likely the best of the methods I ran the mandrill image through. It sorts the images by blue, red, then green with a twist that part of the computation is purposly allowed to overflow. Originally this strategy was a bug with a previous implementation, but after looking at the results I noticed that it often produced nice looking images, so I decided to keep the bugged behavior as one of the options.
Figure 4: It’s a bit boring, but a method that almost always produces a good looking image is to take the average value of the red, green, and blue value for each pixel and use that as the criteria by which the lists are sorted.
As part of writing this project I also wrote a verification function that confirms if an image is a valid allRGB image or not.