Browse Source

Add implementation

Johann Schmitz 6 months ago
parent
commit
cf30af4d18
Signed by: Johann Schmitz <johann@j-schmitz.net> GPG Key ID: A084064277C501ED
1 changed files with 92 additions and 167 deletions
  1. 92
    167
      pydenticon5/__init__.py

+ 92
- 167
pydenticon5/__init__.py View File

@@ -1,175 +1,95 @@
1 1
 # -*- coding: utf-8 -*-
2
-import math
3 2
 
4 3
 from PIL import Image, ImageDraw
5 4
 
6 5
 
7 6
 class Pydenticon5(object):
8 7
 
8
+    sprites = [
9
+        [(0.5, 1), (1, 0), (1, 1)],  # triangle
10
+        [(0.5, 0), (1, 0), (0.5, 1), (0, 1)],  # parallelogram
11
+        [(0.5, 0), (1, 0), (1, 1), (0.5, 1), (1, 0.5)],  # mouse ears
12
+        [(0, 0.5), (0.5, 0), (1, 0.5), (0.5, 1), (0.5, 0.5)],  # ribbon
13
+        [(0, 0.5), (1, 0), (1, 1), (0, 1), (1, 0.5)],  # sails
14
+        [(1, 0), (1, 1), (0.5, 1), (1, 0.5), (0.5, 0.5)],  # fins
15
+        [(0, 0), (1, 0), (1, 0.5), (0, 0), (0.5, 1), (0, 1)],  # beak
16
+        [(0, 0), (0.5, 0), (1, 0.5), (0.5, 1), (0, 1), (0.5, 0.5)],  # chevron
17
+        [(0.5, 0), (0.5, 0.5), (1, 0.5), (1, 1), (0.5, 1), (0.5, 0.5), (0, 0.5)],  # fish
18
+        [(0, 0), (1, 0), (0.5, 0.5), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)],  # kite
19
+        [(0, 0.5), (0.5, 1), (1, 0.5), (0.5, 0), (1, 0), (1, 1), (0, 1)],  # trough
20
+        [(0.5, 0), (1, 0), (1, 1), (0.5, 1), (1, 0.75), (0.5, 0.5), (1, 0.25)],  # rays
21
+        [(0, 0.5), (0.5, 0), (0.5, 0.5), (1, 0), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)],  # double rhombus
22
+        [(0, 0), (1, 0), (1, 1), (0, 1), (1, 0.5), (0.5, 0.25), (0.5, 0.75), (0, 0.59), (0.5, 0.25)],  # crown
23
+        [(0, 0.5), (0.5, 0.5), (0.5, 0), (1, 0), (0.5, 0.5), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)]  # radioactive
24
+    ]
25
+    default_sprite = [ (0, 0), (1, 0), (0.5, 0.5), (0.5, 0), (0, 0.5), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)] # tiles
26
+
27
+    center_sprites = [
28
+        [],  # empty
29
+        [0, 0, 1, 0, 1, 1, 0, 1],  # fill
30
+        [0.5, 0, 1, 0.5, 0.5, 1, 0, 0.5],  # diamond
31
+        [0, 0, 1, 0, 1, 1, 0, 1, 0, 0.5, 0.5, 1, 1, 0.5, 0.5, 0, 0, 0.5],  # reverse diamond
32
+        [0.25, 0, 0.75, 0, 0.5, 0.5, 1, 0.25, 1, 0.75, 0.5, 0.5, 0.75, 1, 0.25, 1, 0.5, 0.5, 0, 0.75, 0, 0.25, 0.5, 0.5],  # cross
33
+        [0, 0, 0.5, 0.25, 1, 0, 0.75, 0.5, 1, 1, 0.5, 0.75, 0, 1, 0.25, 0.5],  # morning star
34
+        [0.33, 0.33, 0.67, 0.33, 0.67, 0.67, 0.33, 0.67],  # small square
35
+        [0, 0, 0.33, 0, 0.33, 0.33, 0.66, 0.33, 0.67, 0, 1, 0, 1, 0.33, 0.67, 0.33, 0.67, 0.67,
36
+          1, 0.67, 1, 1, 0.67, 1, 0.67, 0.67, 0.33, 0.67, 0.33, 1, 0, 1, 0, 0.67, 0.33, 0.67,
37
+          0.33, 0.33, 0, 0.33]  # checkerboard
38
+    ]
39
+
40
+    default_center_sprite = [ 0, 0, 1, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1]  # tiles
41
+
42
+    def __init__(self, background=0):
43
+        self.background_color = background
44
+
9 45
     def _get_sprite(self, shape, size):
10 46
 
11
-        if shape == 0:  # triangle
12
-            coords = [ (0.5, 1), (1, 0), (1, 1) ]
13
-        elif shape == 1:  # parallelogram
14
-            coords = [ (0.5, 0), (1, 0), (0.5, 1), (0, 1) ]
15
-        elif shape == 2:  # mouse ears
16
-            coords = [ (0.5, 0), (1, 0), (1, 1), (0.5, 1), (1, 0.5) ]
17
-        elif shape == 3: # ribbon
18
-            coords = [ (0, 0.5), (0.5, 0), (1, 0.5), (0.5, 1), (0.5, 0.5)]
19
-        elif shape == 4: # sails
20
-            coords = [ (0, 0.5), (1, 0), (1, 1), (0, 1), (1, 0.5)]
21
-        elif shape == 5: # fins
22
-            coords = [ (1, 0), (1, 1), (0.5, 1), (1, 0.5), (0.5, 0.5)]
23
-        elif shape == 6: # beak
24
-            coords = [ (0, 0), (1, 0), (1, 0.5), (0, 0), (0.5, 1), (0, 1)]
25
-        elif shape == 7: # chevron
26
-            coords = [ (0, 0), (0.5, 0), (1, 0.5), (0.5, 1), (0, 1), (0.5, 0.5)]
27
-        elif shape == 8: # fish
28
-            coords = [ (0.5, 0), (0.5, 0.5), (1, 0.5), (1, 1), (0.5, 1), (0.5, 0.5), (0, 0.5)]
29
-        elif shape == 9: # kite
30
-            coords = [ (0, 0), (1, 0), (0.5, 0.5), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)]
31
-        elif shape == 10: # trough
32
-            coords = [ (0, 0.5), (0.5, 1), (1, 0.5), (0.5, 0), (1, 0), (1, 1), (0, 1)]
33
-        elif shape == 11: # rays
34
-            coords = [ (0.5, 0), (1, 0), (1, 1), (0.5, 1), (1, 0.75), (0.5, 0.5), (1, 0.25)]
35
-        elif shape == 12: # double rhombus
36
-            coords = [ (0, 0.5), (0.5, 0), (0.5, 0.5), (1, 0), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)]
37
-        elif shape == 13: # crown
38
-            coords = [ (0, 0), (1, 0), (1, 1), (0, 1), (1, 0.5), (0.5, 0.25), (0.5, 0.75), (0, 0.59), (0.5, 0.25)]
39
-        elif shape == 14: # radioactive
40
-            coords = [ (0, 0.5), (0.5, 0.5), (0.5, 0), (1, 0), (0.5, 0.5), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)]
41
-        else: # tiles
42
-            coords = [ (0, 0), (1, 0), (0.5, 0.5), (0.5, 0), (0, 0.5), (1, 0.5), (0.5, 1), (0.5, 0.5), (0, 1)]
47
+        coords = list(self.sprites[shape] if shape < len(self.sprites) else self.default_sprite)
43 48
 
44 49
         # scale up
45
-
46 50
         for i in range(len(coords)):
47 51
             coords[i] = (coords[i][0] * size, coords[i][1] * size)
48 52
 
49 53
         return coords
50 54
 
51
-    def draw_rotated_polygon(self, orig, box_size, sprite, x, y, shapeangle, angle, size, fillstyle):
55
+    def _get_center_sprite(self, shape, size):
56
+        coords = list(self.center_sprites[shape] if shape < len(self.center_sprites) else self.default_center_sprite)
52 57
 
53
-        print("Draw rotated polygon: sprite=%s, x=%s, y=%s, shapeangle=%s, angle=%s, size=%s, fillstyle=%s" % (sprite, x, y, shapeangle, angle, size, fillstyle))
58
+        # apply ratios
59
+        for i in range(len(coords)):
60
+            coords[i] = coords[i] * size
54 61
 
55
-        poly_img = Image.new('RGBA', (box_size, box_size))
62
+        return coords
56 63
 
57
-        # ImageDraw.Draw(poly_img).polygon([
58
-        #     (0, size),
59
-        #     (size/2, 0),
60
-        #     (size, size)
61
-        # ], fill=(255,255,255))
62
-        
64
+    def draw_rotated_polygon(self, orig, box_size, sprite, x, y, shapeangle, angle, size, fillstyle):
65
+
66
+        poly_img = Image.new('RGBA', (box_size, box_size), color=self.background_color)
63 67
         ImageDraw.Draw(poly_img).polygon(sprite, fill=fillstyle)
64
-        
65
-        pillow_angle = (angle - 180) * -1
68
+
69
+        # .rotate on the 2d canvas in identicon5.js rotates clockwise while pillow rotates counter clockwise
70
+        pillow_angle = -360 - angle
71
+        poly_img = poly_img.rotate(pillow_angle)
72
+
73
+        pillow_angle = -360 - shapeangle
66 74
         poly_img = poly_img.rotate(pillow_angle)
75
+
67 76
         orig.paste(poly_img, (x, y))
68 77
         
69
-        #
70
-        # #img.rotate(angle * math.pi / 180, center=(x, y))
71
-        # if angle == 90:
72
-        #     img = orig.rotate(angle * -1, center=(x, y)) # Pillow rotate is counter clockwise!
73
-        #     img.show()
74
-        # else:
75
-        #     img = orig
76
-        #
77
-        # draw = ImageDraw.Draw(img)
78
-        # draw.rectangle(
79
-        #     [x, y, x+size, y+size], outline=(0,0,0)
80
-        # )
81
-        #
82
-        # #center = (x, y)
83
-        # #img.rotate(angle * math.pi / 180, center=center)
84
-        # #img.rotate(shapeangle, center=(size/2, size/2))
85
-        #
86
-        # # move sprite to the correct box
87
-        # translated_sprite = [
88
-        #     (s[0] + x, s[1] + y) for s in sprite
89
-        # ]
90
-        # draw.polygon(translated_sprite, fill=fillstyle)
91
-        #
92
-        #
93
-        #
94
-        #
95
-        #
96
-        # # # var halfSize = size / 2;
97
-        # # half_size = size / 2
98
-        # #
99
-        # # # # ctx.translate(x, y);
100
-        # # # # ctx.rotate(angle * Math.PI / 180);
101
-        # # center = (x, y)
102
-        # # img.rotate(angle * math.pi / 180, center=center)
103
-        # # #
104
-        # # # # ctx.translate(halfSize, halfSize);
105
-        # # # # ctx.rotate(shapeangle);
106
-        # # center = (half_size, half_size)
107
-        # # img.rotate(shapeangle, center=center)
108
-        # # #
109
-        # # # # var tmpSprite = [];
110
-        # # # # for (var p = 0; p < sprite.length; p++) {
111
-        # # # #     tmpSprite[p] = sprite[p] - halfSize;
112
-        # # # # }
113
-        # # # # fillPoly(ctx, tmpSprite);
114
-        # # tmpsprite = [(coords[0] + center[0] - half_size, coords[1] + center[1] - half_size) for coords in sprite]
115
-        # # draw.polygon(tmpsprite, fillstyle)
116
-        #
117
-        # # debug
118
-        #
119
-        # # img.rotate(shapeangle * -1, center=center)
120
-        # # img.rotate((angle * math.pi / 180) * -1, center=(x, y))
121 78
         return orig
122 79
 
80
+    def draw(self, hash, size, rotate=True):
123 81
 
124
-    def get_center(self, shape, size):
125
-
126
-        if shape == 0:  # empty
127
-            center = []
128
-        elif shape == 1:  # fill
129
-            center = [0, 0, 1, 0, 1, 1, 0, 1]
130
-        elif shape == 2:  # diamond
131
-            center = [ 0.5, 0, 1, 0.5, 0.5, 1, 0, 0.5]
132
-
133
-        elif shape == 3:  # reverse diamond
134
-            center = [0, 0, 1, 0, 1, 1, 0, 1, 0, 0.5, 0.5, 1, 1, 0.5, 0.5, 0, 0, 0.5]
135
-
136
-        elif shape == 4:  # cross
137
-            center = [ 0.25, 0, 0.75, 0, 0.5, 0.5, 1, 0.25, 1, 0.75, 0.5, 0.5, 0.75, 1, 0.25, 1, 0.5, 0.5, 0, 0.75, 0, 0.25, 0.5, 0.5]
138
-
139
-        elif shape == 5:  # morning star
140
-            center = [ 0, 0, 0.5, 0.25, 1, 0, 0.75, 0.5, 1, 1, 0.5, 0.75, 0, 1, 0.25, 0.5]
141
-
142
-        elif shape == 6:  # small square
143
-            center = [ 0.33, 0.33, 0.67, 0.33, 0.67, 0.67, 0.33, 0.67]
144
-
145
-        elif shape == 7:  # checkerboard
146
-            center = [ 0, 0, 0.33, 0, 0.33, 0.33, 0.66, 0.33, 0.67, 0, 1, 0, 1, 0.33, 0.67, 0.33, 0.67, 0.67,
147
-                    1, 0.67, 1, 1, 0.67, 1, 0.67, 0.67, 0.33, 0.67, 0.33, 1, 0, 1, 0, 0.67, 0.33, 0.67,
148
-                    0.33, 0.33, 0, 0.33]
149
-        else: # tiles
150
-            center = [ 0, 0, 1, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1]
151
-
152
-        # apply ratios
153
-        for i in range(len(center)):
154
-            center[i] = center[i] * size
155
-
156
-        return center
157
-
158
-    def draw(self, hash, size, rotate):
159
-
160
-        img = Image.new('RGBA', (size, size))
161
-        #img = ImageDraw.Draw(i)
82
+        img = Image.new('RGBA', (size, size), color=self.background_color)
162 83
 
163 84
         corner_sprite_shape = int(hash[0], 16)
164 85
         side_sprite_shape = int(hash[1], 16)
165 86
         center_sprite_shape = int(hash[2], 16) & 7
166 87
 
167
-        half_pi = math.pi / 2
168
-
169
-        corner_sprite_rotation = half_pi * (int(hash[3], 16) & 3)
170
-        side_sprite_rotation = half_pi * (int(hash[4], 16) & 3)
171 88
         center_sprite_background = int(hash[5], 16) % 2
172 89
 
90
+        corner_sprite_rotation = (int(hash[3], 16) & 3) * 90
91
+        side_sprite_rotation = (int(hash[4], 16) & 3) * 90
92
+
173 93
         # corner sprite foreground color
174 94
         cfr = int(hash[6:8], 16)
175 95
         cfg = int(hash[8:10], 16)
@@ -182,46 +102,51 @@ class Pydenticon5(object):
182 102
 
183 103
         # size of each sprite
184 104
         block_size = int(size / 3)
185
-        # totalsize = size
186
-
187
-        # start with blank 3x3 identicon
188 105
 
189 106
         # generate corner sprites
190
-        corner = self._get_sprite(corner_sprite_shape, block_size)
191
-        ### ctx.fillStyle = "rgb(" + cfr + "," + cfg + "," + cfb + ")";
192
-
193 107
         if not rotate:
194 108
             corner_sprite_rotation = 0
195 109
 
110
+        corner_sprite = self._get_sprite(corner_sprite_shape, block_size)
196 111
         fillstyle = (cfr, cfg, cfb)
197 112
 
198
-        img = self.draw_rotated_polygon(img, block_size, corner, 0, 0, corner_sprite_rotation, 0, block_size, fillstyle)
199
-        img = self.draw_rotated_polygon(img, block_size, corner, size - block_size, 0, corner_sprite_rotation, 90, block_size, fillstyle)
200
-        img = self.draw_rotated_polygon(img, block_size, corner, size - block_size, size - block_size, corner_sprite_rotation, 180, block_size, fillstyle)
201
-        img = self.draw_rotated_polygon(img, block_size, corner, 0, size - block_size, corner_sprite_rotation, 270, block_size, fillstyle)
113
+        # corner sprites
114
+        # top left
115
+        img = self.draw_rotated_polygon(img, block_size, corner_sprite, 0, 0, corner_sprite_rotation, 0, block_size, fillstyle)
116
+        # top right
117
+        img = self.draw_rotated_polygon(img, block_size, corner_sprite, size - block_size, 0, corner_sprite_rotation, 90, block_size, fillstyle)
118
+        # bottom right
119
+        img = self.draw_rotated_polygon(img, block_size, corner_sprite, size - block_size, size - block_size, corner_sprite_rotation, 180, block_size, fillstyle)
120
+        # bottom left
121
+        img = self.draw_rotated_polygon(img, block_size, corner_sprite, 0, size - block_size, corner_sprite_rotation, 270, block_size, fillstyle)
202 122
 
203 123
         # draw sides
204 124
         if not rotate:
205 125
             side_sprite_rotation = 0
206 126
 
207
-        side = self._get_sprite(side_sprite_shape, size)
208
-
127
+        # side sprites
128
+        side_sprite = self._get_sprite(side_sprite_shape, block_size)
209 129
         fillstyle = (sfr, sfg, sfb)
210
-        img = self.draw_rotated_polygon(img, block_size, side, 0, block_size, side_sprite_rotation, 0, block_size, fillstyle)
211
-        img = self.draw_rotated_polygon(img, block_size, side, 2 * block_size, 0, side_sprite_rotation, 90, block_size, fillstyle)
212
-        img = self.draw_rotated_polygon(img, block_size, side, 3 * block_size, 2 * block_size, side_sprite_rotation, 180, block_size, fillstyle)
213
-        img = self.draw_rotated_polygon(img, block_size, side, block_size, 3 * block_size, side_sprite_rotation, 270, block_size, fillstyle)
214
-        #
215
-        # center = self.get_center(center_sprite_shape, size)
216
-        #
217
-        # # make sure there's enough contrast before we use background color of side sprite
218
-        #
219
-        # if center_sprite_background > 0 and (abs(cfr - sfr) > 127 or abs(cfg - sfg) > 127 or abs(cfb - sfb) > 127):
220
-        #     fillstyle = (sfr, sfg, sfb)
221
-        # else:
222
-        #     fillstyle = (cfr, cfg, cfb)
223
-        #
224
-        # if center:
225
-        #     self.draw_rotated_polygon(img, center, size, size, 0, 0, size, fillstyle)
130
+
131
+        # left
132
+        img = self.draw_rotated_polygon(img, block_size, side_sprite, 0, block_size, side_sprite_rotation, 0, block_size, fillstyle)
133
+        # top
134
+        img = self.draw_rotated_polygon(img, block_size, side_sprite, 1 * block_size, 0 * block_size, side_sprite_rotation, 90, block_size, fillstyle)
135
+        # right
136
+        img = self.draw_rotated_polygon(img, block_size, side_sprite, 2 * block_size, 1 * block_size, side_sprite_rotation, 180, block_size, fillstyle)
137
+        # bottom
138
+        img = self.draw_rotated_polygon(img, block_size, side_sprite, 1 * block_size, 2 * block_size, side_sprite_rotation, 270, block_size, fillstyle)
139
+        
140
+        # center
141
+        center_sprite = self._get_center_sprite(center_sprite_shape, block_size)
142
+
143
+        # make sure there's enough contrast before we use background color of side sprite
144
+        if center_sprite_background > 0 and (abs(cfr - sfr) > 127 or abs(cfg - sfg) > 127 or abs(cfb - sfb) > 127):
145
+            fillstyle = (sfr, sfg, sfb)
146
+        else:
147
+            fillstyle = (cfr, cfg, cfb)
148
+
149
+        if center_sprite:
150
+            self.draw_rotated_polygon(img, block_size, center_sprite, block_size, block_size, 0, 0, block_size, fillstyle)
226 151
         
227 152
         return img