StringIO and cStringIO modules are replaced with io.StringIO in Python 3. six.StringIO is an alias for StringIO.StringIO in Python 2 and io.StringIO in Python 3
112 lines
2.7 KiB
Python
112 lines
2.7 KiB
Python
from PIL import Image, ImageDraw
|
|
from hashlib import md5
|
|
import base64
|
|
import random
|
|
from six import StringIO
|
|
|
|
GRID_SIZE = 5
|
|
BORDER_SIZE = 20
|
|
SQUARE_SIZE = 40
|
|
|
|
|
|
class Identicon(object):
|
|
def __init__(self, str_, background='#fafbfc'):
|
|
"""
|
|
`str_` is the string used to generate the identicon.
|
|
`background` is the background of the identicon.
|
|
"""
|
|
w = h = BORDER_SIZE * 2 + SQUARE_SIZE * GRID_SIZE
|
|
self.image = Image.new('RGB', (w, h), background)
|
|
self.draw = ImageDraw.Draw(self.image)
|
|
self.hash = self.digest(str_)
|
|
|
|
def digest(self, str_):
|
|
"""
|
|
Returns a md5 numeric hash
|
|
"""
|
|
return int(md5(str_.encode('utf-8')).hexdigest(), 16)
|
|
|
|
def calculate(self):
|
|
"""
|
|
Creates the identicon.
|
|
First three bytes are used to generate the color,
|
|
remaining bytes are used to create the drawing
|
|
"""
|
|
# color = (self.hash & 0xff, self.hash >> 8 & 0xff, self.hash >> 16 & 0xff)
|
|
color = random.choice((
|
|
(254, 196, 197),
|
|
(253, 138, 139),
|
|
(254, 231, 206),
|
|
(254, 208, 159),
|
|
(210, 211, 253),
|
|
(163, 165, 252),
|
|
(247, 213, 247),
|
|
(242, 172, 238),
|
|
(235, 247, 206),
|
|
(217, 241, 157),
|
|
(211, 248, 237),
|
|
(167, 242, 221),
|
|
(255, 249, 207),
|
|
(254, 245, 161),
|
|
(211, 241, 254),
|
|
(168, 228, 254),
|
|
(207, 245, 210),
|
|
(159, 235, 164),
|
|
))
|
|
# print color
|
|
# color = (254, 232, 206)
|
|
|
|
self.hash >>= 24 # skip first three bytes
|
|
square_x = square_y = 0 # init square position
|
|
for x in range(GRID_SIZE * (GRID_SIZE + 1) // 2):
|
|
if self.hash & 1:
|
|
x = BORDER_SIZE + square_x * SQUARE_SIZE
|
|
y = BORDER_SIZE + square_y * SQUARE_SIZE
|
|
self.draw.rectangle(
|
|
(x, y, x + SQUARE_SIZE, y + SQUARE_SIZE),
|
|
fill=color,
|
|
outline=color
|
|
)
|
|
# following is just for mirroring
|
|
x = BORDER_SIZE + (GRID_SIZE - 1 - square_x) * SQUARE_SIZE
|
|
self.draw.rectangle(
|
|
(x, y, x + SQUARE_SIZE, y + SQUARE_SIZE),
|
|
fill=color,
|
|
outline=color
|
|
)
|
|
self.hash >>= 1 # shift to right
|
|
square_y += 1
|
|
if square_y == GRID_SIZE: # done with first column
|
|
square_y = 0
|
|
square_x += 1
|
|
|
|
def generate(self):
|
|
"""
|
|
Save and show calculated identicon
|
|
"""
|
|
self.calculate()
|
|
with open('identicon.png', 'wb') as out:
|
|
self.image.save(out, 'PNG')
|
|
self.image.show()
|
|
|
|
def base64(self, format='PNG'):
|
|
'''
|
|
usage: i = Identicon('xx')
|
|
print(i.base64())
|
|
return: this image's base64 code
|
|
created by: liuzheng712
|
|
bug report: https://github.com/liuzheng712/identicons/issues
|
|
'''
|
|
self.calculate()
|
|
fp = StringIO()
|
|
self.image.encoderinfo = {}
|
|
self.image.encoderconfig = ()
|
|
|
|
if format.upper() not in Image.SAVE:
|
|
Image.init()
|
|
save_handler = Image.SAVE[format.upper()]
|
|
try:
|
|
save_handler(self.image, fp, '')
|
|
finally:
|
|
fp.seek(0)
|
|
return "data:image/png;base64,{0}".format(base64.b64encode(fp.read()))
|