409 lines
11 KiB
Python
409 lines
11 KiB
Python
|
"""
|
||
|
Python implementation of PHP Identicons, found here:
|
||
|
http://sourceforge.net/projects/identicons/
|
||
|
|
||
|
Licensed under the GPLv3.
|
||
|
"""
|
||
|
from PIL import Image, ImageDraw, ImagePath, ImageColor
|
||
|
from math import fabs as abs
|
||
|
from hashlib import md5
|
||
|
import StringIO
|
||
|
|
||
|
class Pydenticon:
|
||
|
"""
|
||
|
Generate an identicon.
|
||
|
|
||
|
"""
|
||
|
|
||
|
WHITE = (255, 255, 255)
|
||
|
|
||
|
def __init__(self, input, size = 128):
|
||
|
self.hash = md5(input).hexdigest()
|
||
|
self.size = size
|
||
|
|
||
|
def as_string(self):
|
||
|
"""
|
||
|
Return the image as a string representation of a PNG file.
|
||
|
|
||
|
"""
|
||
|
if not hasattr(self, 'image'):
|
||
|
self.image = self._render()
|
||
|
output = StringIO.StringIO()
|
||
|
self.image.save(output, format="PNG")
|
||
|
contents = output.getvalue()
|
||
|
output.close()
|
||
|
return contents
|
||
|
|
||
|
def save(self, filename):
|
||
|
"""
|
||
|
Save the result as a PNG file.
|
||
|
|
||
|
"""
|
||
|
if not hasattr(self, 'image'):
|
||
|
self.image = self._render()
|
||
|
self.image.save(filename, format="PNG")
|
||
|
|
||
|
def _get_sprite(self, shape, r, g, b, rotation):
|
||
|
"""
|
||
|
Generate sprite for corners and sides and returnro a PIL.Image object.
|
||
|
|
||
|
"""
|
||
|
sprite = Image.new("RGB", (self.size, self.size), self.WHITE)
|
||
|
|
||
|
if shape == 0: # triangle
|
||
|
points = [
|
||
|
0.5,1,
|
||
|
1,0,
|
||
|
1,1
|
||
|
]
|
||
|
elif shape == 1: #parallelogram
|
||
|
points = [
|
||
|
0.5,0,
|
||
|
1,0,
|
||
|
0.5,1,
|
||
|
0,1
|
||
|
]
|
||
|
elif shape == 2: # mouse ears
|
||
|
points = [
|
||
|
0.5,0,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0.5,1,
|
||
|
1,0.5
|
||
|
]
|
||
|
elif shape == 3: # ribbon
|
||
|
points = [
|
||
|
0,0.5,
|
||
|
0.5,0,
|
||
|
1,0.5,
|
||
|
0.5,1,
|
||
|
0.5,0.5
|
||
|
]
|
||
|
elif shape == 4: # sails
|
||
|
points = [
|
||
|
0,0.5,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0,1,
|
||
|
1,0.5
|
||
|
]
|
||
|
elif shape == 5: # fins
|
||
|
points = [
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0.5,1,
|
||
|
1,0.5,
|
||
|
0.5,0.5
|
||
|
]
|
||
|
elif shape == 6: # beak
|
||
|
points = [
|
||
|
0,0,
|
||
|
1,0,
|
||
|
1,0.5,
|
||
|
0,0,
|
||
|
0.5,1,
|
||
|
0,1
|
||
|
]
|
||
|
elif shape == 7: # chevron
|
||
|
points = [
|
||
|
0,0,
|
||
|
0.5,0,
|
||
|
1,0.5,
|
||
|
0.5,1,
|
||
|
0,1,
|
||
|
0.5,0.5
|
||
|
]
|
||
|
elif shape == 8: # fish
|
||
|
points = [
|
||
|
0.5,0,
|
||
|
0.5,0.5,
|
||
|
1,0.5,
|
||
|
1,1,
|
||
|
0.5,1,
|
||
|
0.5,0.5,
|
||
|
0,0.5
|
||
|
]
|
||
|
elif shape == 9: # kite
|
||
|
points = [
|
||
|
0,0,
|
||
|
1,0,
|
||
|
0.5,0.5,
|
||
|
1,0.5,
|
||
|
0.5,1,
|
||
|
0.5,0.5,
|
||
|
0,1
|
||
|
]
|
||
|
elif shape == 10: # trough
|
||
|
points = [
|
||
|
0,0.5,
|
||
|
0.5,1,
|
||
|
1,0.5,
|
||
|
0.5,0,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0,1
|
||
|
]
|
||
|
elif shape == 11: # rays
|
||
|
points = [
|
||
|
0.5,0,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0.5,1,
|
||
|
1,0.75,
|
||
|
0.5,0.5,
|
||
|
1,0.25
|
||
|
]
|
||
|
elif shape == 12: # double rhombus
|
||
|
points = [
|
||
|
0,0.5,
|
||
|
0.5,0,
|
||
|
0.5,0.5,
|
||
|
1,0,
|
||
|
1,0.5,
|
||
|
0.5,1,
|
||
|
0.5,0.5,
|
||
|
0,1
|
||
|
]
|
||
|
elif shape == 13: # crown
|
||
|
points = [
|
||
|
0,0,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0,1,
|
||
|
1,0.5,
|
||
|
0.5,0.25,
|
||
|
0.5,0.75,
|
||
|
0,0.5,
|
||
|
0.5,0.25
|
||
|
]
|
||
|
elif shape == 14: # radioactive
|
||
|
points = [
|
||
|
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
|
||
|
]
|
||
|
else: # tiles
|
||
|
points = [
|
||
|
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
|
||
|
]
|
||
|
|
||
|
# apply ratios
|
||
|
for i in range(0, len(points)):
|
||
|
points[i] = points[i] * self.size
|
||
|
|
||
|
draw = ImageDraw.Draw(sprite)
|
||
|
draw.polygon(points, fill=(r, g, b))
|
||
|
|
||
|
for i in range(rotation):
|
||
|
sprite = sprite.rotate(90)
|
||
|
|
||
|
return sprite
|
||
|
|
||
|
def _get_center(self, shape, fR, fG, fB, bR, bG, bB, useBg):
|
||
|
"""
|
||
|
Generate sprite for center block and return a PIL.Image object.
|
||
|
|
||
|
"""
|
||
|
sprite = Image.new("RGB", (self.size, self.size), self.WHITE)
|
||
|
|
||
|
# make sure there's enough contrast before we use background color of side sprite
|
||
|
sufficient_contrast = (
|
||
|
abs(fR - bR) > 127 or abs(fG - bG) > 127 or abs(fB - bB) > 127
|
||
|
)
|
||
|
if useBg > 0 and sufficient_contrast:
|
||
|
bg = (bR, bG, bB)
|
||
|
else:
|
||
|
bg = (255, 255, 255)
|
||
|
|
||
|
if shape == 0: # empty
|
||
|
points = []
|
||
|
|
||
|
elif shape == 1: # fill
|
||
|
points = [
|
||
|
0,0,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0,1
|
||
|
]
|
||
|
elif shape == 2: # diamond
|
||
|
points = [
|
||
|
0.5,0,
|
||
|
1,0.5,
|
||
|
0.5,1,
|
||
|
0,0.5
|
||
|
]
|
||
|
elif shape == 3: # reverse diamond
|
||
|
points = [
|
||
|
0,0,
|
||
|
1,0,
|
||
|
1,1,
|
||
|
0,1,
|
||
|
0,0.5,
|
||
|
0.5,1,
|
||
|
1,0.5,
|
||
|
0.5,0,
|
||
|
0,0.5
|
||
|
]
|
||
|
elif shape == 4: # cross
|
||
|
points = [
|
||
|
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
|
||
|
]
|
||
|
elif shape == 5: # morning star
|
||
|
points = [
|
||
|
0,0,
|
||
|
0.5,0.25,
|
||
|
1,0,
|
||
|
0.75,0.5,
|
||
|
1,1,
|
||
|
0.5,0.75,
|
||
|
0,1,
|
||
|
0.25,0.5
|
||
|
]
|
||
|
elif shape == 6: # small square
|
||
|
points = [
|
||
|
0.33,0.33,
|
||
|
0.67,0.33,
|
||
|
0.67,0.67,
|
||
|
0.33,0.67
|
||
|
]
|
||
|
elif shape == 7: # checkerboard
|
||
|
points = [
|
||
|
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,
|
||
|
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,
|
||
|
0.33,0.33,
|
||
|
0,0.33
|
||
|
]
|
||
|
|
||
|
# apply ratios
|
||
|
for i in range(0, len(points)):
|
||
|
points[i] = points[i] * self.size
|
||
|
|
||
|
if len(points) > 0:
|
||
|
draw = ImageDraw.Draw(sprite)
|
||
|
draw.polygon(points, fill=(fR, fG, fB))
|
||
|
|
||
|
return sprite
|
||
|
|
||
|
def _render(self):
|
||
|
"""
|
||
|
Render the image and return the PIL.Image object.
|
||
|
|
||
|
"""
|
||
|
# parse hash string
|
||
|
corner_sprite_shape = int(self.hash[0:1], 16)
|
||
|
side_sprite_shape = int(self.hash[1:2], 16)
|
||
|
center_sprite_shape = int(self.hash[2:3], 16) & 7
|
||
|
|
||
|
corner_sprite_rot = int(self.hash[3:4], 16) & 3
|
||
|
side_sprite_rot = int(self.hash[4:5], 16) & 3
|
||
|
center_sprite_bg = int(self.hash[5:6], 16) % 2
|
||
|
|
||
|
# corner sprite foreground color
|
||
|
corner_sprite_fg_r = int(self.hash[6:8], 16)
|
||
|
corner_sprite_fg_g = int(self.hash[8:10], 16)
|
||
|
corner_sprite_fg_b = int(self.hash[10:12], 16)
|
||
|
|
||
|
# side sprite foreground color
|
||
|
side_sprite_fg_r = int(self.hash[12:14], 16)
|
||
|
side_sprite_fg_g = int(self.hash[14:16], 16)
|
||
|
side_sprite_fg_b = int(self.hash[16:18], 16)
|
||
|
|
||
|
# final angle of rotation
|
||
|
angle = int(self.hash[18:20], 16)
|
||
|
|
||
|
# start with blank 3X sized identicon
|
||
|
identicon = Image.new("RGB", (self.size*3, self.size*3), self.WHITE)
|
||
|
|
||
|
# generate corner sprites
|
||
|
corner = self._get_sprite(
|
||
|
corner_sprite_shape,
|
||
|
corner_sprite_fg_r,
|
||
|
corner_sprite_fg_g,
|
||
|
corner_sprite_fg_b,
|
||
|
corner_sprite_rot
|
||
|
)
|
||
|
identicon.paste(corner, (0, 0))
|
||
|
corner = corner.rotate(90)
|
||
|
identicon.paste(corner, (0, self.size*2))
|
||
|
corner = corner.rotate(90)
|
||
|
identicon.paste(corner, (self.size*2, self.size*2))
|
||
|
corner = corner.rotate(90)
|
||
|
identicon.paste(corner, (self.size*2, 0))
|
||
|
|
||
|
# generate side sprites
|
||
|
side = self._get_sprite(
|
||
|
side_sprite_shape,
|
||
|
side_sprite_fg_r,
|
||
|
side_sprite_fg_g,
|
||
|
side_sprite_fg_b,
|
||
|
side_sprite_rot
|
||
|
)
|
||
|
identicon.paste(side, (self.size, 0))
|
||
|
side = side.rotate(90)
|
||
|
identicon.paste(side, (0, self.size))
|
||
|
side = side.rotate(90)
|
||
|
identicon.paste(side, (self.size, self.size*2))
|
||
|
side = side.rotate(90)
|
||
|
identicon.paste(side, (self.size*2, self.size))
|
||
|
|
||
|
# generate center sprite
|
||
|
center = self._get_center(
|
||
|
center_sprite_shape,
|
||
|
corner_sprite_fg_r,
|
||
|
corner_sprite_fg_g,
|
||
|
corner_sprite_fg_b,
|
||
|
side_sprite_fg_r,
|
||
|
side_sprite_fg_g,
|
||
|
side_sprite_fg_b,
|
||
|
center_sprite_bg
|
||
|
)
|
||
|
identicon.paste(center, (self.size, self.size))
|
||
|
|
||
|
# resize image
|
||
|
resized = identicon.resize(
|
||
|
(self.size, self.size),
|
||
|
Image.ANTIALIAS
|
||
|
)
|
||
|
|
||
|
return resized
|