PyBitmessage/src/pydenticon.py

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