identicon.py 7.4 KB

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