Identicons #542

Merged
Atheros1 merged 26 commits from master into master 2013-11-02 23:16:08 +01:00
5 changed files with 759 additions and 4 deletions
Showing only changes of commit deeac2c99b - Show all commits

View File

@ -47,6 +47,20 @@ except Exception as err:
print 'Error message:', err
sys.exit()
# for the md5 hash (used for the identicons)
import hashlib
# load identicon code
# :Author:Shin Adachi <shn@glucose.jp>
# Licesensed under FreeBSD License.
import identicon
# usage: identicon.render_identicon(code, size)
# requires PIL
# load another identicon code
# http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py
from pydenticon import Pydenticon
try:
_encoding = QtGui.QApplication.UnicodeUTF8
except AttributeError:
@ -55,6 +69,34 @@ except AttributeError:
def _translate(context, text):
return QtGui.QApplication.translate(context, text)
def identiconize(address):
suffix = "" # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators
# instead, you could also use a pseudo-password to salt the generation of the identicons
# Attacks where someone creates an address to mimic someone else's identicon should be impossible then
# i think it should generate a random string by default
# hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest()[:8]
# print hash
##japanese code
# idcon_render = identicon.render_identicon(int(hash, 16), 8)
# idcon_render.save('images/'+hash+'.png')
# im = idcon_render
# http://qt-project.org/forums/viewthread/5866
# from PIL import Image
# from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication
# PHP-like code
idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix)
image = idcon_render._render()
# im = Image.open('images/'+hash+'.png')
# http://stackoverflow.com/questions/6756820/python-pil-image-tostring
data = image.convert("RGBA").tostring("raw", "RGBA")
image = QImage(data, image.size[0], image.size[1], QImage.Format_ARGB32)
pix = QPixmap.fromImage(image)
idcon = QtGui.QIcon()
idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off)
return idcon
class MyForm(QtGui.QMainWindow):
@ -327,6 +369,7 @@ class MyForm(QtGui.QMainWindow):
newItem.setTextColor(QtGui.QColor(128, 128, 128))
if shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'):
newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta
newItem.setIcon(identiconize(addressInKeysFile))
self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem)
newItem = QtGui.QTableWidgetItem(str(
addressStream(addressInKeysFile)))
@ -354,9 +397,11 @@ class MyForm(QtGui.QMainWindow):
for row in queryreturn:
label, address = row
self.ui.tableWidgetAddressBook.insertRow(0)
# address book item
newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8'))
self.ui.tableWidgetAddressBook.setItem(0, 0, newItem)
newItem = QtGui.QTableWidgetItem(address)
newItem.setIcon(identiconize(address))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.tableWidgetAddressBook.setItem(0, 1, newItem)
@ -401,6 +446,16 @@ class MyForm(QtGui.QMainWindow):
self.numberOfBroadcastsProcessed = 0
self.numberOfPubkeysProcessed = 0
# Set the icon sizes for the identicons
identicon_size = 3*7
self.ui.tableWidgetInbox.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.ui.tableWidgetSent.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.ui.tableWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.ui.tableWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.ui.tableWidgetWhitelist.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.ui.tableWidgetBlacklist.setIconSize(QtCore.QSize(identicon_size, identicon_size))
self.UISignalThread = UISignaler()
QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
"writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable)
@ -607,6 +662,7 @@ class MyForm(QtGui.QMainWindow):
else:
newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8'))
newItem.setToolTip(unicode(toLabel, 'utf-8'))
newItem.setIcon(identiconize(toAddress))
newItem.setData(Qt.UserRole, str(toAddress))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
@ -618,6 +674,7 @@ class MyForm(QtGui.QMainWindow):
else:
newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8'))
newItem.setToolTip(unicode(fromLabel, 'utf-8'))
newItem.setIcon(identiconize(fromAddress))
newItem.setData(Qt.UserRole, str(fromAddress))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
@ -747,10 +804,12 @@ class MyForm(QtGui.QMainWindow):
if queryreturn != []:
for row in queryreturn:
fromLabel, = row
# message row
self.ui.tableWidgetInbox.insertRow(0)
newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8'))
newItem.setToolTip(unicode(toLabel, 'utf-8'))
# to
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
if not read:
@ -760,7 +819,9 @@ class MyForm(QtGui.QMainWindow):
newItem.setTextColor(QtGui.QColor(137, 04, 177))
if shared.safeConfigGetBoolean(str(toAddress), 'chan'):
newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange
newItem.setIcon(identiconize(toAddress))
self.ui.tableWidgetInbox.setItem(0, 0, newItem)
# from
if fromLabel == '':
newItem = QtGui.QTableWidgetItem(
unicode(fromAddress, 'utf-8'))
@ -773,8 +834,9 @@ class MyForm(QtGui.QMainWindow):
if not read:
newItem.setFont(font)
newItem.setData(Qt.UserRole, str(fromAddress))
newItem.setIcon(identiconize(fromAddress))
self.ui.tableWidgetInbox.setItem(0, 1, newItem)
# subject
newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8'))
newItem.setToolTip(unicode(subject, 'utf-8'))
newItem.setData(Qt.UserRole, unicode(message, 'utf-8)'))
@ -783,6 +845,7 @@ class MyForm(QtGui.QMainWindow):
if not read:
newItem.setFont(font)
self.ui.tableWidgetInbox.setItem(0, 2, newItem)
# time received
newItem = myTableWidgetItem(unicode(strftime(shared.config.get(
'bitmessagesettings', 'timeformat'), localtime(int(received))), 'utf-8'))
newItem.setToolTip(unicode(strftime(shared.config.get(
@ -1473,6 +1536,8 @@ class MyForm(QtGui.QMainWindow):
toLabel = toAddress
self.ui.tableWidgetInbox.item(
i, 0).setText(unicode(toLabel, 'utf-8'))
self.ui.tableWidgetInbox.item(
i, 0).setIcon(identiconize(toAddress))
# Set the color according to whether it is the address of a mailing
# list or not.
if shared.safeConfigGetBoolean(toAddress, 'mailinglist'):
@ -1493,6 +1558,8 @@ class MyForm(QtGui.QMainWindow):
fromLabel = fromAddress
self.ui.tableWidgetSent.item(
i, 1).setText(unicode(fromLabel, 'utf-8'))
self.ui.tableWidgetSent.item(
i, 0).setIcon(identiconize(fromAddress))
def rerenderSentToLabels(self):
for i in range(self.ui.tableWidgetSent.rowCount()):
@ -1512,7 +1579,7 @@ class MyForm(QtGui.QMainWindow):
toLabel, = row
self.ui.tableWidgetSent.item(
i, 0).setText(unicode(toLabel, 'utf-8'))
def rerenderSubscriptions(self):
self.ui.tableWidgetSubscriptions.setRowCount(0)
shared.sqlLock.acquire()
@ -1529,6 +1596,7 @@ class MyForm(QtGui.QMainWindow):
newItem.setTextColor(QtGui.QColor(128, 128, 128))
self.ui.tableWidgetSubscriptions.setItem(0, 0, newItem)
newItem = QtGui.QTableWidgetItem(address)
newItem.setIcon(identiconize(address))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
if not enabled:
@ -1791,6 +1859,7 @@ class MyForm(QtGui.QMainWindow):
newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8'))
newItem.setToolTip(unicode(fromLabel, 'utf-8'))
newItem.setData(Qt.UserRole, str(fromAddress))
newItem.setIcon(identiconize(address))
self.ui.tableWidgetSent.setItem(0, 1, newItem)
newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)'))
newItem.setToolTip(unicode(subject, 'utf-8)'))
@ -1920,6 +1989,7 @@ class MyForm(QtGui.QMainWindow):
newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8'))
self.ui.tableWidgetAddressBook.setItem(0, 0, newItem)
newItem = QtGui.QTableWidgetItem(address)
newItem.setIcon(identiconize(address))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.tableWidgetAddressBook.setItem(0, 1, newItem)
@ -1949,6 +2019,7 @@ class MyForm(QtGui.QMainWindow):
newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8'))
self.ui.tableWidgetSubscriptions.setItem(0,0,newItem)
newItem = QtGui.QTableWidgetItem(address)
newItem.setIcon(identiconize(address))
newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled )
self.ui.tableWidgetSubscriptions.setItem(0,1,newItem)
self.ui.tableWidgetSubscriptions.setSortingEnabled(True)
@ -1998,6 +2069,7 @@ class MyForm(QtGui.QMainWindow):
newItem.setTextColor(QtGui.QColor(128, 128, 128))
self.ui.tableWidgetBlacklist.setItem(0, 0, newItem)
newItem = QtGui.QTableWidgetItem(address)
newItem.setIcon(identiconize(address))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
if not enabled:
@ -2971,6 +3043,7 @@ class MyForm(QtGui.QMainWindow):
self.ui.tableWidgetYourIdentities.setItem(
0, 0, newItem)
newItem = QtGui.QTableWidgetItem(address)
newItem.setIcon(identiconize(address))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
if shared.safeConfigGetBoolean(address, 'chan'):

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'settings.ui'
#
# Created: Sat Aug 24 09:19:58 2013
# Created: Sun Aug 25 18:09:38 2013
# by: PyQt4 UI code generator 4.10.2
#
# WARNING! All changes made in this file will be lost!

262
src/identicon.py Normal file
View File

@ -0,0 +1,262 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
identicon.py
identicon python implementation.
by Shin Adachi <shn@glucose.jp>
= usage =
== commandline ==
>>> python identicon.py [code]
== python ==
>>> import identicon
>>> identicon.render_identicon(code, size)
Return a PIL Image class instance which have generated identicon image.
```size``` specifies `patch size`. Generated image size is 3 * ```size```.
"""
# g
# PIL Modules
import Image
import ImageDraw
import ImagePath
import ImageColor
__all__ = ['render_identicon', 'IdenticonRendererBase']
class Matrix2D(list):
"""Matrix for Patch rotation"""
def __init__(self, initial=[0.] * 9):
assert isinstance(initial, list) and len(initial) == 9
list.__init__(self, initial)
def clear(self):
for i in xrange(9):
self[i] = 0.
def set_identity(self):
self.clear()
for i in xrange(3):
self[i] = 1.
def __str__(self):
return '[%s]' % ', '.join('%3.2f' % v for v in self)
def __mul__(self, other):
r = []
if isinstance(other, Matrix2D):
for y in xrange(3):
for x in xrange(3):
v = 0.0
for i in xrange(3):
v += (self[i * 3 + x] * other[y * 3 + i])
r.append(v)
else:
raise NotImplementedError
return Matrix2D(r)
def for_PIL(self):
return self[0:6]
@classmethod
def translate(kls, x, y):
return kls([1.0, 0.0, float(x),
0.0, 1.0, float(y),
0.0, 0.0, 1.0])
@classmethod
def scale(kls, x, y):
return kls([float(x), 0.0, 0.0,
0.0, float(y), 0.0,
0.0, 0.0, 1.0])
"""
# need `import math`
@classmethod
def rotate(kls, theta, pivot=None):
c = math.cos(theta)
s = math.sin(theta)
matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
if not pivot:
return matR
return kls.translate(-pivot[0], -pivot[1]) * matR *
kls.translate(*pivot)
"""
@classmethod
def rotateSquare(kls, theta, pivot=None):
theta = theta % 4
c = [1., 0., -1., 0.][theta]
s = [0., 1., 0., -1.][theta]
matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.])
if not pivot:
return matR
return kls.translate(-pivot[0], -pivot[1]) * matR * \
kls.translate(*pivot)
class IdenticonRendererBase(object):
PATH_SET = []
def __init__(self, code):
"""
@param code code for icon
"""
if not isinstance(code, int):
code = int(code)
self.code = code
def render(self, size):
"""
render identicon to PIL.Image
@param size identicon patchsize. (image size is 3 * [size])
@return PIL.Image
"""
# decode the code
middle, corner, side, foreColor, backColor = self.decode(self.code)
# make image
image = Image.new("RGB", (size * 3, size * 3))
draw = ImageDraw.Draw(image)
# fill background
draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0)
kwds = {
'draw': draw,
'size': size,
'foreColor': foreColor,
'backColor': backColor}
# middle patch
self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds)
# side patch
kwds['type'] = side[0]
for i in xrange(4):
pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i]
self.drawPatch(pos, side[2] + 1 + i, side[1], **kwds)
# corner patch
kwds['type'] = corner[0]
for i in xrange(4):
pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i]
self.drawPatch(pos, corner[2] + 1 + i, corner[1], **kwds)
return image
def drawPatch(self, pos, turn, invert, type, draw, size, foreColor,
backColor):
"""
@param size patch size
"""
path = self.PATH_SET[type]
if not path:
# blank patch
invert = not invert
path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)]
patch = ImagePath.Path(path)
if invert:
foreColor, backColor = backColor, foreColor
mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\
Matrix2D.translate(*pos) *\
Matrix2D.scale(size, size)
patch.transform(mat.for_PIL())
draw.rectangle((pos[0] * size, pos[1] * size, (pos[0] + 1) * size,
(pos[1] + 1) * size), fill=backColor)
draw.polygon(patch, fill=foreColor, outline=foreColor)
### virtual functions
def decode(self, code):
raise NotImplementedError
class DonRenderer(IdenticonRendererBase):
"""
Don Park's implementation of identicon
see : http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released
"""
PATH_SET = [
[(0, 0), (4, 0), (4, 4), (0, 4)], # 0
[(0, 0), (4, 0), (0, 4)],
[(2, 0), (4, 4), (0, 4)],
[(0, 0), (2, 0), (2, 4), (0, 4)],
[(2, 0), (4, 2), (2, 4), (0, 2)], # 4
[(0, 0), (4, 2), (4, 4), (2, 4)],
[(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)],
[(0, 0), (4, 2), (2, 4)],
[(1, 1), (3, 1), (3, 3), (1, 3)], # 8
[(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)],
[(0, 0), (2, 0), (2, 2), (0, 2)],
[(0, 2), (4, 2), (2, 4)],
[(2, 2), (4, 4), (0, 4)],
[(2, 0), (2, 2), (0, 2)],
[(0, 0), (2, 0), (0, 2)],
[]] # 15
MIDDLE_PATCH_SET = [0, 4, 8, 15]
# modify path set
for idx in xrange(len(PATH_SET)):
if PATH_SET[idx]:
p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx])
PATH_SET[idx] = p + p[:1]
def decode(self, code):
# decode the code
middleType = self.MIDDLE_PATCH_SET[code & 0x03]
middleInvert= (code >> 2) & 0x01
cornerType = (code >> 3) & 0x0F
cornerInvert= (code >> 7) & 0x01
cornerTurn = (code >> 8) & 0x03
sideType = (code >> 10) & 0x0F
sideInvert = (code >> 14) & 0x01
sideTurn = (code >> 15) & 0x03
# bug reported by Masato Hagiwara
# http://lilyx.net/iconlang-en/
# blue = (code >> 16) & 0x1F
# green = (code >> 21) & 0x1F
blue = (code >> 17) & 0x1F
green = (code >> 22) & 0x1F
red = (code >> 27) & 0x1F
foreColor = (red << 3, green << 3, blue << 3)
return (middleType, middleInvert, 0),\
(cornerType, cornerInvert, cornerTurn),\
(sideType, sideInvert, sideTurn),\
foreColor, ImageColor.getrgb('white')
def render_identicon(code, size, renderer=None):
if not renderer:
renderer = DonRenderer
return renderer(code).render(size)
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
print 'usage: python identicon.py [CODE]....'
raise SystemExit
for code in sys.argv[1:]:
if code.startswith('0x') or code.startswith('0X'):
code = int(code[2:], 16)
elif code.startswith('0'):
code = int(code[1:], 8)
else:
code = int(code)
icon = render_identicon(code, 24)
icon.save('%08x.png' % code, 'PNG')

11
src/identicon_py_license Normal file
View File

@ -0,0 +1,11 @@
identicon.py is Licesensed under FreeBSD License.
(http://www.freebsd.org/copyright/freebsd-license.html)
Copyright 1994-2009 Shin Adachi. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

409
src/pydenticon.py Normal file
View File

@ -0,0 +1,409 @@
"""
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