Created
February 10, 2010 20:01
-
-
Save EricBurnett/300780 to your computer and use it in GitHub Desktop.
Convert an image to all-RGB by colourizing from 3d hilbert line
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Image # Python Imaging Library (PIL) | |
import random | |
import math | |
import os | |
import sys | |
import getopt | |
# Main work function. Takes the path to a (square) image and the number of | |
# bits per colour channel desired. That value dictates the size of the output | |
# image, so it must be divisible by 2. For < 8, lowest order bits dropped. | |
def allrgbify(path, bitsPerChannel): | |
if bitsPerChannel & 1 != 0: | |
print "bitsPerChannel must be divisible by 2" | |
return | |
if (bitsPerChannel > 8): | |
print "Do you really want a picture " + \ | |
str(int(math.sqrt(2**(3*bitsPerChannel)))) + " pixels on a side?" | |
return | |
size = 2**(3*bitsPerChannel/2) | |
shift = 8-bitsPerChannel | |
im = Image.open(path) | |
im = im.resize((size, size)) | |
pix = im.load() | |
# Flat is an array of tuples, (index, x, y) | |
flat = [(hindex(bitsPerChannel, map(lambda a: a>>shift, pix[x,y])), x, y) | |
for y in xrange(size) for x in xrange(size)] | |
# Shuffle so pixels with the same calculated index treated "fairly". | |
random.shuffle(flat) | |
# Sort by calculated index function. | |
def cmp(x,y): | |
return x[0]-y[0] | |
flat.sort(cmp) | |
# Map new colour scheme back to the respective pixels in the image. | |
for i in xrange(2**(3*(8-shift))): | |
pt = flat[i] | |
pix[pt[1], pt[2]] = tuple(map(lambda a: a<<shift, | |
hindexInverse(bitsPerChannel, i))) | |
name, ext = os.path.splitext(path) | |
im.save(name + "_allrgbify.png", "PNG") | |
# Generate allrgb versions of selected images with specified bit depth. | |
def genAll(): | |
files = ["C:/Users/Eric Burnett/Desktop/Python/zoom5.png", | |
"C:/Users/Eric Burnett/Desktop/Python/zoom6.png"] | |
for f in files: | |
allrgbify(f, 6) | |
# Semi-related function - output the hilbert colours in a line. | |
def hilbertLine(): | |
im = Image.new("RGB", (512, 1)) | |
im.putdata([tuple(map(lambda a: a<<5, hindexInverse(3, i))) | |
for i in range(512)]) | |
im.save("C:/Users/Eric Burnett/Desktop/Python/hilbertLine.png") | |
# Verify a file to ensure it actually uses every colour only once. Only works | |
# for images generated with bit depth 8. | |
def verify(path): | |
im = Image.open(path) | |
pix = im.load() | |
size = im.size | |
flat = [(pix[x,y][0]<<16)|(pix[x,y][1]<<8)|pix[x,y][2] | |
for y in range(size[0]) for x in range(size[1])] | |
flat.sort() | |
for i in xrange(2**24-1): | |
if flat[i] != i: | |
print "incorrect value! i="+str(i) | |
return | |
# Hilbert logic from https://www.cs.dal.ca/sites/default/files/CS-2006-07.pdf | |
# Algorithm a direct implementation of the logic presented there. | |
# returns the index into the rgb cube indexed by the 3d hilbert curve | |
def hindex(m, p): | |
dim = 3 | |
h = 0 | |
e = 0 | |
d = 0 | |
for i in range(m)[::-1]: | |
l = bit(p[2], i) << 2 | bit(p[1], i) << 1 | bit(p[0], i) | |
l = cycleLeft(l, 1, dim) | |
l = T(e, d, dim, l) | |
w = gcInverse(dim, l) | |
e = e ^ (cycleLeft(E(w), d+1, dim)) | |
d = (d + D(w, dim) + 1) % dim | |
h = (h << dim) | w | |
return h | |
def hindexInverse(m, h): | |
dim = 3 | |
e = 0 | |
d = 0 | |
p = [0, 0, 0] | |
for i in range(m)[::-1]: | |
w = bit(h, 3*i+2) << 2 | bit(h, 3*i+1) << 1 | bit(h, 3*i) | |
l = gc(w) | |
l = TInverse(e, d, dim, l) | |
for j in range(dim): | |
p[j] |= bit(l, j) << i | |
e = e ^ (cycleLeft(E(w), d+1, dim)) | |
d = (d + D(w, dim) + 1) % dim | |
return p | |
# trailing set bits | |
def tsb(i): | |
# Split on last 4 bits | |
if ((i & 7) == 7): | |
# All 4 set | |
return 4 + tsb(i >> 4) | |
tsb4 = (0, 1, 0, 2, 0, 1, 0, 3) | |
return tsb4[i & 7] | |
# the greycode of the integer i | |
def gc(i): | |
return i ^ (i >> 1) | |
def gcInverse(m, g): | |
i = g | |
j = 1 | |
while j < m: | |
i = i ^ (g >> j) | |
j = j + 1 | |
return i | |
def D(i, n): | |
if i == 0: | |
return 0 | |
if i & 1: | |
return tsb(i) % n | |
return tsb(i-1) % n | |
def E(i): | |
if i == 0: | |
return 0 | |
return gc((i-1) & (~1)) | |
def cycle(a, b, n): | |
return (a >> b) | ((a << (n-b)) & (2**n - 1)) | |
def cycleLeft(a, b, n): | |
return cycle(a, n-b, n) | |
def T(e, d, n, b): | |
return cycle(b^e,d+1,n) | |
def TInverse(e, d, n, b): | |
return T(cycle(e, d+1, n), n-d-1, n, b) | |
def bit(n, i): | |
return (n >> i) & 1 | |
def bitSet(n, i, val): | |
return n | val << i | |
# Ensure hilbert mapping is a bijection as expected. | |
def testHilbertInvertible(): | |
for i in xrange(2**24-1): | |
if hindex(8, hindexInverse(8, i)) != i: | |
print "failure on ", i | |
return 0 | |
def main(): | |
possibleArgs = ["image=", "bpc="] | |
usage = "Usage: python allrgbify.py --image=\"image path\" --bpc=desired_bits_per_channel" | |
try: | |
opts, args = getopt.getopt(sys.argv[1:], "", possibleArgs) | |
except getopt.error, msg: | |
print msg | |
print usage | |
sys.exit(2) | |
path = "" | |
bpc = 0 | |
for o, a in opts: | |
if o == "--image": | |
path = a | |
else: | |
bpc = int(a) | |
if path == "": | |
print usage | |
sys.exit(2) | |
allrgbify(path, bpc) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment