|
| 1 | +#!/usr/bin/python |
| 2 | + |
| 3 | +# Image converter for 'Uncanny Eyes' project. Generates tables for |
| 4 | +# eyeData.h file. Requires Python Imaging Library. Expects four image |
| 5 | +# files: sclera, iris, upper lid map and lower lid map (defaults will be |
| 6 | +# used if not specified). Also generates polar coordinate map for iris |
| 7 | +# rendering (pass diameter -- must be an even value -- as 5th argument). |
| 8 | +# Output is to stdout; should be redirected to file for use. |
| 9 | + |
| 10 | +# This is kinda some horrible copy-and-paste code right now for each of |
| 11 | +# the four images...could be improved, but basically does the thing. |
| 12 | + |
| 13 | +import sys |
| 14 | +import math |
| 15 | +from PIL import Image |
| 16 | + |
| 17 | +columns = 8 # Number of columns in formatted output |
| 18 | + |
| 19 | +# Write hex digit (with some formatting for C array) to stdout |
| 20 | +def outputHex(n, digits): |
| 21 | + global columns, column, counter, limit |
| 22 | + column += 1 # Increment column number |
| 23 | + if column >= columns: # If max column exceeded... |
| 24 | + sys.stdout.write("\n ") # end current line, start new one |
| 25 | + column = 0 # Reset column number |
| 26 | + sys.stdout.write("{0:#0{1}X}".format(n, digits + 2)) |
| 27 | + counter += 1 # Increment element index |
| 28 | + if counter < limit: # If NOT last element in list... |
| 29 | + sys.stdout.write(",") # add column between elements |
| 30 | + if column < (columns - 1): # and space if not last column |
| 31 | + sys.stdout.write(" ") |
| 32 | + else: |
| 33 | + print(" };"); # Cap off table |
| 34 | + |
| 35 | + |
| 36 | +# OPEN AND VALIDATE SCLERA IMAGE FILE -------------------------------------- |
| 37 | + |
| 38 | +try: filename = sys.argv[1] |
| 39 | +except: filename = "sclera.png" |
| 40 | +im = Image.open(filename) |
| 41 | +im = im.convert("RGB") |
| 42 | +pixels = im.load() |
| 43 | + |
| 44 | +# GENERATE SCLERA ARRAY ---------------------------------------------------- |
| 45 | + |
| 46 | +# Initialize outputHex() global counters: |
| 47 | +counter = 0 # Index of next element to generate |
| 48 | +column = columns # Current column number in output |
| 49 | +limit = im.size[0] * im.size[1] # Total # of elements in generated list |
| 50 | + |
| 51 | +print "#define SCLERA_WIDTH" + str(im.size[0]) |
| 52 | +print "#define SCLERA_HEIGHT" + str(im.size[1]) |
| 53 | +print |
| 54 | + |
| 55 | +sys.stdout.write("const uint16_t sclera[SCLERA_HEIGHT][SCLERA_WIDTH] = {") |
| 56 | + |
| 57 | +# Convert 24-bit image to 16 bits: |
| 58 | +for y in range(im.size[1]): |
| 59 | + for x in range(im.size[0]): |
| 60 | + p = pixels[x, y] # Pixel data (tuple) |
| 61 | + outputHex(((p[0] & 0b11111000) << 8) | # Convert 24-bit RGB |
| 62 | + ((p[1] & 0b11111100) << 3) | # to 16-bit value w/ |
| 63 | + ( p[2] >> 3), 4) # 5/6/5-bit packing |
| 64 | + |
| 65 | + |
| 66 | +# OPEN AND VALIDATE IRIS IMAGE FILE ---------------------------------------- |
| 67 | + |
| 68 | +try: filename = sys.argv[2] |
| 69 | +except: filename = "iris.png" |
| 70 | +im = Image.open(filename) |
| 71 | +if (im.size[0] > 512) or (im.size[1] > 128): |
| 72 | + sys.stderr.write("Image can't exceed 512 pixels wide or 128 pixels tall") |
| 73 | + exit(1) |
| 74 | +im = im.convert("RGB") |
| 75 | +pixels = im.load() |
| 76 | + |
| 77 | +# GENERATE IRIS ARRAY ------------------------------------------------------ |
| 78 | + |
| 79 | +counter = 0 # Reset outputHex() counters again for new table |
| 80 | +column = columns |
| 81 | +limit = im.size[0] * im.size[1] |
| 82 | + |
| 83 | +print |
| 84 | +print "#define IRIS_MAP_WIDTH " + str(im.size[0]) |
| 85 | +print "#define IRIS_MAP_HEIGHT " + str(im.size[1]) |
| 86 | +print |
| 87 | + |
| 88 | +sys.stdout.write("const uint16_t iris[IRIS_MAP_HEIGHT][IRIS_MAP_WIDTH] = {") |
| 89 | + |
| 90 | +for y in range(im.size[1]): |
| 91 | + for x in range(im.size[0]): |
| 92 | + p = pixels[x, y] # Pixel data (tuple) |
| 93 | + outputHex(((p[0] & 0b11111000) << 8) | # Convert 24-bit RGB |
| 94 | + ((p[1] & 0b11111100) << 3) | # to 16-bit value w/ |
| 95 | + ( p[2] >> 3), 4) # 5/6/5-bit packing |
| 96 | + |
| 97 | + |
| 98 | +# OPEN AND VALIDATE UPPER EYELID THRESHOLD MAP ----------------------------- |
| 99 | + |
| 100 | +try: filename = sys.argv[3] |
| 101 | +except: filename = "upper.png" |
| 102 | +im = Image.open(filename) |
| 103 | +if (im.size[0] != 128) or (im.size[1] != 128): |
| 104 | + sys.stderr.write("Image size must match screen size") |
| 105 | + exit(1) |
| 106 | +im = im.convert("L") |
| 107 | +pixels = im.load() |
| 108 | + |
| 109 | +# GENERATE UPPER LID ARRAY ------------------------------------------------- |
| 110 | + |
| 111 | +counter = 0 |
| 112 | +column = columns |
| 113 | +limit = im.size[0] * im.size[1] |
| 114 | + |
| 115 | +print |
| 116 | +print "#define SCREEN_WIDTH " + str(im.size[0]) |
| 117 | +print "#define SCREEN_HEIGHT " + str(im.size[1]) |
| 118 | +print |
| 119 | + |
| 120 | +sys.stdout.write("const uint8_t upper[SCREEN_HEIGHT][SCREEN_WIDTH] = {") |
| 121 | + |
| 122 | +for y in range(im.size[1]): |
| 123 | + for x in range(im.size[0]): |
| 124 | + outputHex(pixels[x, y], 2) # 8-bit value per pixel |
| 125 | + |
| 126 | + |
| 127 | +# OPEN AND VALIDATE LOWER EYELID THRESHOLD MAP ----------------------------- |
| 128 | + |
| 129 | +try: filename = sys.argv[4] |
| 130 | +except: filename = "lower.png" |
| 131 | +im = Image.open(filename) |
| 132 | +if (im.size[0] != 128) or (im.size[1] != 128): |
| 133 | + sys.stderr.write("Image size must match screen size") |
| 134 | + exit(1) |
| 135 | +im = im.convert("L") |
| 136 | +pixels = im.load() |
| 137 | + |
| 138 | +# GENERATE LOWER LID ARRAY ------------------------------------------------- |
| 139 | + |
| 140 | +counter = 0 |
| 141 | +column = columns |
| 142 | +limit = im.size[0] * im.size[1] |
| 143 | + |
| 144 | +print |
| 145 | +sys.stdout.write("const uint8_t lower[SCREEN_HEIGHT][SCREEN_WIDTH] = {") |
| 146 | + |
| 147 | +for y in range(im.size[1]): |
| 148 | + for x in range(im.size[0]): |
| 149 | + outputHex(pixels[x, y], 2) # 8-bit value per pixel |
| 150 | + |
| 151 | + |
| 152 | +# GENERATE POLAR COORDINATE TABLE ------------------------------------------ |
| 153 | + |
| 154 | +try: irisSize = int(sys.argv[5]) |
| 155 | +except: irisSize = 80 |
| 156 | +slitPupil = False |
| 157 | +if irisSize % 2 != 0: |
| 158 | + sys.stderr.write("Iris diameter must be even value") |
| 159 | + exit(1) |
| 160 | +if irisSize < 0: |
| 161 | + irisSize = -irisSize |
| 162 | + slitPupil = True |
| 163 | + filename = "pupilMap.png" # HACKITY HACK, see notes later |
| 164 | + im = Image.open(filename) # OMG so wretched and hacky |
| 165 | + if (im.size[0] != irisSize) or (im.size[1] != irisSize): |
| 166 | + sys.stderr.write("Image size must match iris size") |
| 167 | + exit(1) |
| 168 | + im = im.convert("L") |
| 169 | + pixels = im.load() |
| 170 | +radius = irisSize / 2 |
| 171 | + |
| 172 | +print |
| 173 | +print "#define IRIS_WIDTH " + str(irisSize) |
| 174 | +print "#define IRIS_HEIGHT " + str(irisSize) |
| 175 | + |
| 176 | +# One element per screen pixel, 16 bits per element -- high 9 bits are |
| 177 | +# angle relative to center point (fixed point, 0-511) low 7 bits are |
| 178 | +# distance from circle perimeter (fixed point, 0-127, pixels outsize circle |
| 179 | +# are set to 127). |
| 180 | + |
| 181 | +counter = 0 |
| 182 | +column = columns |
| 183 | +limit = irisSize * irisSize |
| 184 | + |
| 185 | +sys.stdout.write("\nconst uint16_t polar[%s][%s] = {" % (irisSize, irisSize)) |
| 186 | + |
| 187 | +for y in range(irisSize): |
| 188 | + dy = y - radius + 0.5 |
| 189 | + for x in range(irisSize): |
| 190 | + dx = x - radius + 0.5 |
| 191 | + distance = math.sqrt(dx * dx + dy * dy) |
| 192 | + if(distance >= radius): # Outside circle |
| 193 | + outputHex(127, 4) # angle = 0, dist = 127 |
| 194 | + else: |
| 195 | + if slitPupil: |
| 196 | + # TODO: add magic here |
| 197 | + # I totally cheated on the dragon eye |
| 198 | + # included with the demo code -- made a |
| 199 | + # canned distance bitmap using Illustrator + |
| 200 | + # Photoshop and use that...but it's rigged |
| 201 | + # to the bitmap size and isn't a generalized |
| 202 | + # solution, which is what's needed here. |
| 203 | + angle = math.atan2(dy, dx) # -pi to +pi |
| 204 | + angle += math.pi # 0.0 to 2pi |
| 205 | + angle /= (math.pi * 2.0) # 0.0 to <1.0 |
| 206 | + distance = pixels[x, y] / 255.0 |
| 207 | + else: |
| 208 | + angle = math.atan2(dy, dx) # -pi to +pi |
| 209 | + angle += math.pi # 0.0 to 2pi |
| 210 | + angle /= (math.pi * 2.0) # 0.0 to <1.0 |
| 211 | + distance /= radius # 0.0 to <1.0 |
| 212 | + distance *= 128.0 # 0.0 to <128.0 |
| 213 | + a = int(angle * 512.0) # 0 to 511 |
| 214 | + d = 127 - int(distance) # 127 to 0 |
| 215 | + outputHex((a << 7) | d, 4) |
| 216 | + |
0 commit comments