root/lang/python/misc/identicon.py

Revision 7225, 7.6 kB (checked in by shn, 4 years ago)

copy //mirror/coderepos to //coderepos
add identicon

Line 
1#!/usr/bin/env python
2# -*- coding:utf-8 -*-
3"""
4identicon.py
5identicon python implementation.
6by 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
17Return 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
22import Image, ImageDraw, ImagePath, ImageColor
23
24__all__ = ['render_identicon', 'IdenticonRendererBase']
25
26class 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
97class 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
175class 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
228def render_identicon(code, size, renderer=None):
229    if not renderer:
230        renderer = DonRenderer
231    return renderer(code).render(size)
232       
233if __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')
Note: See TracBrowser for help on using the browser.