| 1 | #!/usr/bin/env python
|
|---|
| 2 | # -*- coding:utf-8 -*-
|
|---|
| 3 | """
|
|---|
| 4 | identicon.py
|
|---|
| 5 | identicon python implementation.
|
|---|
| 6 | by Shin Adachi <shn@glucose.jp>
|
|---|
| 7 |
|
|---|
| 8 | = usage =
|
|---|
| 9 |
|
|---|
| 10 | == commandline ==
|
|---|
| 11 | >>> python identicon.py [code]
|
|---|
| 12 |
|
|---|
| 13 | == python ==
|
|---|
| 14 | >>> import identicon
|
|---|
| 15 | >>> identicon.render_identicon(code, size)
|
|---|
| 16 |
|
|---|
| 17 | Return a PIL Image class instance which have generated identicon image.
|
|---|
| 18 | ```size``` specifies `patch size`. Generated image size is 3 * ```size```.
|
|---|
| 19 | """
|
|---|
| 20 | # g
|
|---|
| 21 | # PIL Modules
|
|---|
| 22 | import Image, ImageDraw, ImagePath, ImageColor
|
|---|
| 23 |
|
|---|
| 24 | __all__ = ['render_identicon', 'IdenticonRendererBase']
|
|---|
| 25 |
|
|---|
| 26 | class Matrix2D(list):
|
|---|
| 27 | """Matrix for Patch rotation"""
|
|---|
| 28 | def __init__(self, initial = [0.] * 9):
|
|---|
| 29 | assert isinstance(initial, list) and len(initial)==9
|
|---|
| 30 | list.__init__(self, initial)
|
|---|
| 31 |
|
|---|
| 32 | def clear(self):
|
|---|
| 33 | for i in xrange(9):
|
|---|
| 34 | self[i] = 0.
|
|---|
| 35 |
|
|---|
| 36 | def set_identity(self):
|
|---|
| 37 | self.clear()
|
|---|
| 38 | for i in xrange(3):
|
|---|
| 39 | self[i] = 1.
|
|---|
| 40 |
|
|---|
| 41 | def __str__(self):
|
|---|
| 42 | return '[%s]' % ', '.join('%3.2f' % v for v in self)
|
|---|
| 43 |
|
|---|
| 44 | def __mul__(self, other):
|
|---|
| 45 | r = []
|
|---|
| 46 | if isinstance(other, Matrix2D):
|
|---|
| 47 | for y in xrange(3):
|
|---|
| 48 | for x in xrange(3):
|
|---|
| 49 | v = 0.0
|
|---|
| 50 | for i in xrange(3):
|
|---|
| 51 | v += (self[i * 3 + x] * other[y * 3 + i])
|
|---|
| 52 | r.append(v)
|
|---|
| 53 | else:
|
|---|
| 54 | raise NotImplementedError
|
|---|
| 55 | return Matrix2D(r)
|
|---|
| 56 |
|
|---|
| 57 | def for_PIL(self):
|
|---|
| 58 | return self[0:6]
|
|---|
| 59 |
|
|---|
| 60 | @classmethod
|
|---|
| 61 | def translate(kls, x, y):
|
|---|
| 62 | return kls([1.0, 0.0, float(x),
|
|---|
| 63 | 0.0, 1.0, float(y),
|
|---|
| 64 | 0.0, 0.0, 1.0])
|
|---|
| 65 |
|
|---|
| 66 | @classmethod
|
|---|
| 67 | def scale(kls, x, y):
|
|---|
| 68 | return kls([float(x), 0.0, 0.0,
|
|---|
| 69 | 0.0, float(y), 0.0,
|
|---|
| 70 | 0.0, 0.0, 1.0])
|
|---|
| 71 |
|
|---|
| 72 | """
|
|---|
| 73 | # need `import math`
|
|---|
| 74 | @classmethod
|
|---|
| 75 | def rotate(kls, theta, pivot=None):
|
|---|
| 76 | c = math.cos(theta)
|
|---|
| 77 | s = math.sin(theta)
|
|---|
| 78 |
|
|---|
| 79 | matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
|
|---|
| 80 | if not pivot:
|
|---|
| 81 | return matR
|
|---|
| 82 | return kls.translate(-pivot[0], -pivot[1]) * matR * kls.translate(*pivot)
|
|---|
| 83 | """
|
|---|
| 84 |
|
|---|
| 85 | @classmethod
|
|---|
| 86 | def rotateSquare(kls, theta, pivot=None):
|
|---|
| 87 | theta = theta % 4
|
|---|
| 88 | c = [1., 0., -1., 0.][theta]
|
|---|
| 89 | s = [0., 1., 0., -1.][theta]
|
|---|
| 90 |
|
|---|
| 91 | matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
|
|---|
| 92 | if not pivot:
|
|---|
| 93 | return matR
|
|---|
| 94 | return kls.translate(-pivot[0], -pivot[1]) * matR * kls.translate(*pivot)
|
|---|
| 95 |
|
|---|
| 96 |
|
|---|
| 97 | class IdenticonRendererBase(object):
|
|---|
| 98 | PATH_SET = []
|
|---|
| 99 |
|
|---|
| 100 | def __init__(self, code):
|
|---|
| 101 | """
|
|---|
| 102 | @param code code for icon
|
|---|
| 103 | """
|
|---|
| 104 | if not isinstance(code, int):
|
|---|
| 105 | code = int(code)
|
|---|
| 106 | self.code = code
|
|---|
| 107 |
|
|---|
| 108 | def render(self, size):
|
|---|
| 109 | """
|
|---|
| 110 | render identicon to PIL.Image
|
|---|
| 111 |
|
|---|
| 112 | @param size identicon patchsize. (image size is 3 * [size])
|
|---|
| 113 | @return PIL.Image
|
|---|
| 114 | """
|
|---|
| 115 |
|
|---|
| 116 | # decode the code
|
|---|
| 117 | middle, corner, side, foreColor, backColor = self.decode(self.code)
|
|---|
| 118 |
|
|---|
| 119 | # make image
|
|---|
| 120 | image = Image.new("RGB", (size * 3, size * 3))
|
|---|
| 121 | draw = ImageDraw.Draw(image)
|
|---|
| 122 |
|
|---|
| 123 | # fill background
|
|---|
| 124 | draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0)
|
|---|
| 125 |
|
|---|
| 126 | kwds = {
|
|---|
| 127 | 'draw': draw,
|
|---|
| 128 | 'size': size,
|
|---|
| 129 | 'foreColor': foreColor,
|
|---|
| 130 | 'backColor': backColor
|
|---|
| 131 | }
|
|---|
| 132 | # middle patch
|
|---|
| 133 | self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds)
|
|---|
| 134 |
|
|---|
| 135 | # side patch
|
|---|
| 136 | kwds['type'] = side[0]
|
|---|
| 137 | for i in xrange(4):
|
|---|
| 138 | pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i]
|
|---|
| 139 | self.drawPatch(pos, side[2] + 1 + i, side[1], **kwds)
|
|---|
| 140 |
|
|---|
| 141 | # corner patch
|
|---|
| 142 | kwds['type'] = corner[0]
|
|---|
| 143 | for i in xrange(4):
|
|---|
| 144 | pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i]
|
|---|
| 145 | self.drawPatch(pos, corner[2] + 1 + i, corner[1], **kwds)
|
|---|
| 146 |
|
|---|
| 147 | return image
|
|---|
| 148 |
|
|---|
| 149 | def drawPatch(self, pos, turn, invert, type, draw, size, foreColor, backColor):
|
|---|
| 150 | """
|
|---|
| 151 | @param size patch size
|
|---|
| 152 | """
|
|---|
| 153 | path = self.PATH_SET[type]
|
|---|
| 154 | if not path:
|
|---|
| 155 | # blank patch
|
|---|
| 156 | invert = not invert
|
|---|
| 157 | path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)]
|
|---|
| 158 | patch = ImagePath.Path(path)
|
|---|
| 159 | if invert:
|
|---|
| 160 | foreColor, backColor = backColor, foreColor
|
|---|
| 161 |
|
|---|
| 162 | mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\
|
|---|
| 163 | Matrix2D.translate(*pos) *\
|
|---|
| 164 | Matrix2D.scale(size, size)
|
|---|
| 165 |
|
|---|
| 166 | patch.transform(mat.for_PIL())
|
|---|
| 167 | draw.rectangle((pos[0] * size, pos[1] * size, (pos[0]+1) * size, (pos[1]+1) * size), fill=backColor)
|
|---|
| 168 | draw.polygon(patch, fill=foreColor, outline=foreColor)
|
|---|
| 169 |
|
|---|
| 170 |
|
|---|
| 171 | ### virtual functions
|
|---|
| 172 | def decode(self, code):
|
|---|
| 173 | raise NotImplementedError
|
|---|
| 174 |
|
|---|
| 175 | class DonRenderer(IdenticonRendererBase):
|
|---|
| 176 | """
|
|---|
| 177 | Don Park's implementation of identicon
|
|---|
| 178 | see : http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released
|
|---|
| 179 | """
|
|---|
| 180 |
|
|---|
| 181 | PATH_SET = [
|
|---|
| 182 | [(0, 0), (4, 0), (4, 4), (0, 4)], # 0
|
|---|
| 183 | [(0, 0), (4, 0), (0, 4)],
|
|---|
| 184 | [(2, 0), (4, 4), (0, 4)],
|
|---|
| 185 | [(0, 0), (2, 0), (2, 4), (0, 4)],
|
|---|
| 186 | [(2, 0), (4, 2), (2, 4), (0, 2)], # 4
|
|---|
| 187 | [(0, 0), (4, 2), (4, 4), (2, 4)],
|
|---|
| 188 | [(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)],
|
|---|
| 189 | [(0, 0), (4, 2), (2, 4)],
|
|---|
| 190 | [(1, 1), (3, 1), (3, 3), (1, 3)], # 8
|
|---|
| 191 | [(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)],
|
|---|
| 192 | [(0, 0), (2, 0), (2, 2), (0, 2)],
|
|---|
| 193 | [(0, 2), (4, 2), (2, 4)],
|
|---|
| 194 | [(2, 2), (4, 4), (0, 4)],
|
|---|
| 195 | [(2, 0), (2, 2), (0, 2)],
|
|---|
| 196 | [(0, 0), (2, 0), (0, 2)],
|
|---|
| 197 | [] # 15
|
|---|
| 198 | ]
|
|---|
| 199 | MIDDLE_PATCH_SET = [0, 4, 8, 15]
|
|---|
| 200 |
|
|---|
| 201 | # modify path set
|
|---|
| 202 | for idx in xrange(len(PATH_SET)):
|
|---|
| 203 | if PATH_SET[idx]:
|
|---|
| 204 | p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx])
|
|---|
| 205 | PATH_SET[idx] = p + p[:1]
|
|---|
| 206 |
|
|---|
| 207 | def decode(self, code):
|
|---|
| 208 | # decode the code
|
|---|
| 209 | middleType = self.MIDDLE_PATCH_SET[code & 0x03]
|
|---|
| 210 | middleInvert= (code >> 2) & 0x01
|
|---|
| 211 | cornerType = (code >> 3) & 0x0F
|
|---|
| 212 | cornerInvert= (code >> 7) & 0x01
|
|---|
| 213 | cornerTurn = (code >> 8) & 0x03
|
|---|
| 214 | sideType = (code >> 10) & 0x0F
|
|---|
| 215 | sideInvert = (code >> 14) & 0x01
|
|---|
| 216 | sideTurn = (code >> 15) & 0x03
|
|---|
| 217 | blue = (code >> 16) & 0x1F
|
|---|
| 218 | green = (code >> 21) & 0x1F
|
|---|
| 219 | red = (code >> 27) & 0x1F
|
|---|
| 220 |
|
|---|
| 221 | foreColor = (red << 3, green << 3, blue << 3)
|
|---|
| 222 |
|
|---|
| 223 | return (middleType, middleInvert, 0),\
|
|---|
| 224 | (cornerType, cornerInvert, cornerTurn),\
|
|---|
| 225 | (sideType, sideInvert, sideTurn),\
|
|---|
| 226 | foreColor, ImageColor.getrgb('white')
|
|---|
| 227 |
|
|---|
| 228 | def render_identicon(code, size, renderer=None):
|
|---|
| 229 | if not renderer:
|
|---|
| 230 | renderer = DonRenderer
|
|---|
| 231 | return renderer(code).render(size)
|
|---|
| 232 |
|
|---|
| 233 | if __name__=='__main__':
|
|---|
| 234 | import sys
|
|---|
| 235 |
|
|---|
| 236 | if len(sys.argv)<2:
|
|---|
| 237 | print 'usage: python identicon.py [CODE]....'
|
|---|
| 238 | raise SystemExit
|
|---|
| 239 |
|
|---|
| 240 | for code in sys.argv[1:]:
|
|---|
| 241 | if code.startswith('0x') or code.startswith('0X'):
|
|---|
| 242 | code = int(code[2:], 16)
|
|---|
| 243 | elif code.startswith('0'):
|
|---|
| 244 | code = int(code[1:], 8)
|
|---|
| 245 | else:
|
|---|
| 246 | code = int(code)
|
|---|
| 247 |
|
|---|
| 248 | icon = render_identicon(code, 24)
|
|---|
| 249 | icon.save('%08x.png' % code, 'PNG') |
|---|