├── .gitignore ├── Makefile ├── verify.py ├── generate.py ├── octree_c.py ├── octree.py ├── README.md ├── octree.c └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | octree 2 | *.png 3 | 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: octree 2 | 3 | octree: octree.c 4 | gcc -std=c99 -O3 -shared -o octree octree.c 5 | -------------------------------------------------------------------------------- /verify.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | import sys 3 | import wx 4 | 5 | def verify(path): 6 | app = wx.App() 7 | image = wx.Image(path) 8 | data = image.GetData() 9 | data = [ord(x) for x in data] 10 | if any(x < 0 or x > 255 for x in data): 11 | raise Exception('Invalid: Values must be 0 <= x < 256.') 12 | rgb = zip(data[::3], data[1::3], data[2::3]) 13 | if len(rgb) != len(set(rgb)): 14 | raise Exception('Invalid: There are duplicate colors.') 15 | print 'Valid!' 16 | 17 | if __name__ == '__main__': 18 | if len(sys.argv) != 2: 19 | print 'Usage: python verify.py input.png' 20 | else: 21 | verify(sys.argv[1]) 22 | -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | from itertools import izip, product 2 | import random 3 | import wx 4 | 5 | BITS = 6 6 | SIZE = int(((2 ** BITS) ** 3) ** 0.5) 7 | STEP = 2 ** (8 - BITS) 8 | 9 | def color_func(color): 10 | r, g, b = color 11 | r, g, b = 0.30 * r, 0.59 * g, 0.11 * b 12 | return r + g + b 13 | 14 | # def index_func(index): 15 | # x = index % SIZE 16 | # y = index / SIZE 17 | # bx = bin(x | SIZE)[3:] 18 | # by = bin(y | SIZE)[3:] 19 | # bz = ''.join(b + a for a, b in izip(bx, by)) 20 | # z = int(bz, 2) 21 | # return z 22 | 23 | def index_func(index): 24 | x, y = index % SIZE, index / SIZE 25 | x, y = x - SIZE / 2, y - SIZE / 2 26 | return x * x + y * y 27 | 28 | def create_data(indexes, colors): 29 | result = [None] * (SIZE * SIZE) 30 | for index, color in izip(indexes, colors): 31 | r, g, b = color 32 | result[index] = chr(r) + chr(g) + chr(b) 33 | return ''.join(result) 34 | 35 | def main(): 36 | indexes = sorted(xrange(SIZE * SIZE), key=index_func) 37 | colors = sorted(product(range(0, 256, STEP), repeat=3), key=color_func) 38 | data = create_data(indexes, colors) 39 | app = wx.App() 40 | image = wx.EmptyImage(SIZE, SIZE) 41 | image.SetData(data) 42 | image.SaveFile('output.png', wx.BITMAP_TYPE_PNG) 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /octree_c.py: -------------------------------------------------------------------------------- 1 | from ctypes import CDLL, POINTER, c_int, byref 2 | 3 | dll = CDLL('octree') 4 | 5 | dll.allocate.restype = POINTER(c_int) 6 | dll.allocate.argtypes = [] 7 | def dll_allocate(): 8 | return dll.allocate() 9 | 10 | dll.deallocate.restype = None 11 | dll.deallocate.argtypes = [POINTER(c_int)] 12 | def dll_deallocate(tree): 13 | dll.deallocate(tree) 14 | 15 | dll.initialize.restype = None 16 | dll.initialize.argtypes = [POINTER(c_int)] 17 | def dll_initialize(tree): 18 | dll.initialize(tree) 19 | 20 | dll.pop.restype = None 21 | dll.pop.argtypes = [ 22 | POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_int)] 23 | def dll_pop(tree, r, g, b): 24 | r, g, b = c_int(r), c_int(g), c_int(b) 25 | dll.pop(tree, byref(r), byref(g), byref(b)) 26 | return (r.value, g.value, b.value) 27 | 28 | dll.apply.restype = None 29 | dll.apply.argtypes = [POINTER(c_int), POINTER(c_int)] 30 | def dll_apply(colors, indexes): 31 | colors = (c_int * len(colors))(*colors) 32 | indexes = (c_int * len(indexes))(*indexes) 33 | dll.apply(colors, indexes) 34 | return list(colors) 35 | 36 | class Octree(object): 37 | def __init__(self): 38 | self.tree = dll_allocate() 39 | dll_initialize(self.tree) 40 | def __del__(self): 41 | dll_deallocate(self.tree) 42 | def pop(self, r, g, b): 43 | return dll_pop(self.tree, r, g, b) 44 | -------------------------------------------------------------------------------- /octree.py: -------------------------------------------------------------------------------- 1 | LOOKUP = [ 2 | [0, 1, 4, 5, 2, 3, 6, 7], 3 | [1, 0, 5, 4, 3, 2, 7, 6], 4 | [2, 3, 6, 7, 0, 1, 4, 5], 5 | [3, 2, 7, 6, 1, 0, 5, 4], 6 | [4, 5, 0, 1, 6, 7, 2, 3], 7 | [5, 4, 1, 0, 7, 6, 3, 2], 8 | [6, 7, 2, 3, 4, 5, 0, 1], 9 | [7, 6, 3, 2, 5, 4, 1, 0], 10 | ] 11 | 12 | class Octree(object): 13 | def __init__(self): 14 | self.data = [0] * sum(8 ** i for i in xrange(9)) 15 | self.initialize(0, 8 ** 8) 16 | def initialize(self, index, value): 17 | self.data[index] = value 18 | if value == 1: 19 | return 20 | value /= 8 21 | for i in xrange(8): 22 | self.initialize(8 * index + i + 1, value) 23 | def pop(self, r, g, b): 24 | r = bin(r | 256)[3:] 25 | g = bin(g | 256)[3:] 26 | b = bin(b | 256)[3:] 27 | desired_path = [int(''.join(x), 2) for x in zip(r, g, b)] 28 | actual_path = [] 29 | index = 0 30 | for desired_child in desired_path: 31 | base = 8 * index + 1 32 | for child in LOOKUP[desired_child]: 33 | new_index = base + child 34 | if self.data[new_index]: 35 | break 36 | actual_path.append(new_index - base) 37 | index = new_index 38 | self.data[index] -= 1 39 | bits = [bin(x | 8)[3:] for x in actual_path] 40 | r, g, b = [int(''.join(x), 2) for x in zip(*bits)] 41 | return r, g, b 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AllRGB 2 | 3 | Efficiently create AllRGB images that target an input image. The input must be 4096x4096px. The output will also be 4096x4096px and will contain all 16,777,216 distinct RGB values once and only once. 4 | 5 | ### Usage 6 | 7 | python main.py input.png 8 | 9 | The output will be stored in output.png. The program takes 2 - 3 minutes to run (longer if the C module is not compiled and the pure Python module is used instead). 10 | 11 | ### Sample 12 | 13 | ![Screenshot](http://i.imgur.com/gQuJo83.jpg) 14 | 15 | ### Algorithm 16 | 17 | An octree is used to spatially represent the RGB colors. The octree is only 9 levels deep from root to leaf (inclusive). Each node in the octree stores a count for how many colors in its subtree are still available to be used. The octree is stored in a flat array, as it is a complete octree. Children indexes are computed as: 18 | 19 | 8 * i + 1 + x 20 | 21 | ...where `i` is the current index and `x` specifies the xth (0 - 7) child. To find a color in the octree, use a single bit from each of R, G and B to form a 3-bit number representing the next child node to visit. Repeat this process from the most significant to the least significant bits. 22 | 23 | For example, if RGB = (27, 89, 233)... 24 | 25 | 12345678 26 | R: 00011011 27 | G: 01011001 28 | B: 11101001 29 | 30 | 001, 011, 001, 110, 111, 000, 100, 111 => 1, 3, 1, 6, 7, 0, 4, 7 31 | 32 | When encountering a node with a value of zero (meaning no colors are available in that space), visit a different child instead to find as similar of a color as possible. But don't just pick a random other child, pick one that will minimize error, especially in the green channel. A lookup table is used for this purpose. 33 | 34 | LOOKUP = [ 35 | [0, 1, 4, 5, 2, 3, 6, 7], 36 | [1, 0, 5, 4, 3, 2, 7, 6], 37 | [2, 3, 6, 7, 0, 1, 4, 5], 38 | [3, 2, 7, 6, 1, 0, 5, 4], 39 | [4, 5, 0, 1, 6, 7, 2, 3], 40 | [5, 4, 1, 0, 7, 6, 3, 2], 41 | [6, 7, 2, 3, 4, 5, 0, 1], 42 | [7, 6, 3, 2, 5, 4, 1, 0], 43 | ] 44 | 45 | Optionally, if some areas of the image are more important than others, such as a face, let those pixels pick their colors first - give them priority. This was not done in the sample image. The pixels in the sample were simply ordered randomly. 46 | 47 | See http://allrgb.com/ for details on the concept. 48 | -------------------------------------------------------------------------------- /octree.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TREE_SIZE 19173961 4 | #define COLORS 16777216 5 | 6 | static int LOOKUP[8][8] = { 7 | {0, 1, 4, 5, 2, 3, 6, 7}, 8 | {1, 0, 5, 4, 3, 2, 7, 6}, 9 | {2, 3, 6, 7, 0, 1, 4, 5}, 10 | {3, 2, 7, 6, 1, 0, 5, 4}, 11 | {4, 5, 0, 1, 6, 7, 2, 3}, 12 | {5, 4, 1, 0, 7, 6, 3, 2}, 13 | {6, 7, 2, 3, 4, 5, 0, 1}, 14 | {7, 6, 3, 2, 5, 4, 1, 0} 15 | }; 16 | 17 | int *allocate() { 18 | return malloc(sizeof(int) * TREE_SIZE); 19 | } 20 | 21 | void deallocate(int *tree) { 22 | free(tree); 23 | } 24 | 25 | void _initialize(int *tree, int index, int value) { 26 | tree[index] = value; 27 | if (value == 1) { 28 | return; 29 | } 30 | value /= 8; 31 | for (int i = 0; i < 8; i++) { 32 | _initialize(tree, 8 * index + 1 + i, value); 33 | } 34 | } 35 | 36 | void initialize(int *tree) { 37 | _initialize(tree, 0, COLORS); 38 | } 39 | 40 | void pop(int *tree, int *r, int *g, int *b) { 41 | int path[8]; 42 | int dr = *r; 43 | int dg = *g; 44 | int db = *b; 45 | for (int i = 7; i >= 0; i--) { 46 | int br = dr & 1; 47 | int bg = dg & 1; 48 | int bb = db & 1; 49 | path[i] = (br << 2) | (bg << 1) | (bb << 0); 50 | dr >>= 1; 51 | dg >>= 1; 52 | db >>= 1; 53 | } 54 | int index = 0; 55 | for (int i = 0; i < 8; i++) { 56 | int base = 8 * index + 1; 57 | for (int j = 0; j < 8; j++) { 58 | int child = LOOKUP[path[i]][j]; 59 | int new_index = base + child; 60 | if (tree[new_index]) { 61 | path[i] = child; 62 | tree[new_index]--; 63 | index = new_index; 64 | break; 65 | } 66 | } 67 | } 68 | int pr = 0; 69 | int pg = 0; 70 | int pb = 0; 71 | for (int i = 0; i < 8; i++) { 72 | pr <<= 1; 73 | pg <<= 1; 74 | pb <<= 1; 75 | pr |= (path[i] >> 2) & 1; 76 | pg |= (path[i] >> 1) & 1; 77 | pb |= (path[i] >> 0) & 1; 78 | } 79 | *r = pr; 80 | *g = pg; 81 | *b = pb; 82 | } 83 | 84 | void apply(int *colors, int *indexes) { 85 | int *tree = allocate(); 86 | initialize(tree); 87 | for (int i = 0; i < COLORS; i++) { 88 | int index = indexes[i]; 89 | int color = colors[index]; 90 | int r = 0xff & (color >> 16); 91 | int g = 0xff & (color >> 8); 92 | int b = 0xff & (color >> 0); 93 | pop(tree, &r, &g, &b); 94 | colors[index] = (r << 16) | (g << 8) | (b); 95 | } 96 | deallocate(tree); 97 | } 98 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from math import hypot 2 | import random 3 | import sys 4 | import wx 5 | 6 | try: 7 | import octree_c as octree 8 | print 'using C octree' 9 | except Exception: 10 | import octree 11 | print 'using Python octree' 12 | 13 | SIZE = 4096 14 | 15 | def load_target(path): 16 | target = wx.Image(path) 17 | data = target.GetData() 18 | data = [ord(x) for x in data] 19 | r, g, b = data[::3], data[1::3], data[2::3] 20 | return zip(r, g, b) 21 | 22 | def load_indexes(): 23 | points = [ 24 | (SIZE / 2, SIZE / 2), 25 | # (2915, 1340), 26 | # (1115, 1760), 27 | ] 28 | def index_func(index): 29 | x, y = index % SIZE, index / SIZE 30 | offset = (random.random() - 0.5) * 512 31 | return min(hypot(x - a, y - b) for a, b in points) + offset 32 | indexes = range(SIZE * SIZE) 33 | random.shuffle(indexes) 34 | # indexes = sorted(indexes, key=index_func) 35 | return indexes 36 | 37 | def load_indexes_mask(path): 38 | result = [] 39 | mask = load_target(path) 40 | colors = sorted(set(mask)) 41 | groups = dict((x, []) for x in colors) 42 | for index, color in enumerate(mask): 43 | groups[color].append(index) 44 | for color in colors: 45 | group = groups[color] 46 | random.shuffle(group) 47 | result.extend(group) 48 | return result 49 | 50 | def create_image_data(colors): 51 | result = [None] * (SIZE * SIZE) 52 | for index, (r, g, b) in enumerate(colors): 53 | result[index] = chr(r) + chr(g) + chr(b) 54 | return ''.join(result) 55 | 56 | def rgb_int(colors): 57 | return [(r << 16) | (g << 8) | (b) for r, g, b in colors] 58 | 59 | def int_rgb(colors): 60 | result = [] 61 | for color in colors: 62 | r = 0xff & (color >> 16) 63 | g = 0xff & (color >> 8) 64 | b = 0xff & (color >> 0) 65 | result.append((r, g, b)) 66 | return result 67 | 68 | def main(path): 69 | app = wx.App() 70 | print 'loading target image' 71 | target = load_target(path) 72 | print 'loading indexes' 73 | indexes = load_indexes() 74 | # indexes = load_indexes_mask('mask.png') 75 | print 'initializing octree' 76 | tree = octree.Octree() 77 | print 'picking colors' 78 | colors = [(0, 0, 0)] * (SIZE * SIZE) 79 | for i, index in enumerate(indexes): 80 | if i % 65536 == 0: 81 | pct = 100.0 * i / (SIZE * SIZE) 82 | print '%.1f%% percent complete' % pct 83 | colors[index] = tree.pop(*target[index]) 84 | print 'creating output image' 85 | data = create_image_data(colors) 86 | image = wx.EmptyImage(SIZE, SIZE) 87 | image.SetData(data) 88 | image.SaveFile('output.png', wx.BITMAP_TYPE_PNG) 89 | 90 | if __name__ == '__main__': 91 | if len(sys.argv) != 2: 92 | print 'Usage: python main.py input.png' 93 | else: 94 | main(sys.argv[1]) 95 | --------------------------------------------------------------------------------