From 240e9b5b58e156d32bec2caa373e9d3889971cc1 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Mon, 16 Sep 2013 21:08:55 +0200 Subject: [PATCH] Updated to work with QPixmap instead of PIL! The original source is licensed under a BSD-License, so it should be fine to use. --- src/bitmessageqt/__init__.py | 61 ++--- src/identicon_py_license | 11 - src/pydenticon.py | 409 ---------------------------- src/{identicon.py => qidenticon.py} | 122 ++++++--- 4 files changed, 119 insertions(+), 484 deletions(-) delete mode 100644 src/identicon_py_license delete mode 100644 src/pydenticon.py rename src/{identicon.py => qidenticon.py} (56%) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 23bbf8cd..1ceca0eb 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -47,20 +47,6 @@ 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 -# 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: @@ -74,7 +60,8 @@ def identiconize(address): if youdontwantidenticons == True: idcon = QtGui.QIcon() return idcon - + size = 3 + str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: idcon = QtGui.QIcon(":/newPrefix/images/can-icon-24px.png") @@ -86,25 +73,37 @@ def identiconize(address): # 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 - if True: # identicon.py + identicon_lib = 'qidenticon' + if identicon_lib == 'qidenticon': + # originally by: + # :Author:Shin Adachi + # Licesensed under FreeBSD License. + # stripped from PIL and uses QT instead + import qidenticon + + import hashlib hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest() - idcon_render = identicon.render_identicon(int(hash, 16), 48) - image = idcon_render - # http://qt-project.org/forums/viewthread/5866 - # from PIL import Image - # from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication - else: # pydenticon.py + image = qidenticon.render_identicon(int(hash, 16), 48) + idcon = QtGui.QIcon() + idcon.addPixmap(image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + return idcon + + elif identicon_lib == 'pydenticon': # pydenticon.py + # print identicon_lib + # load another identicon code + # http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py + from pydenticon import Pydenticon + # GPLv3 !!! 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 + # 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): diff --git a/src/identicon_py_license b/src/identicon_py_license deleted file mode 100644 index e6e964fb..00000000 --- a/src/identicon_py_license +++ /dev/null @@ -1,11 +0,0 @@ -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. diff --git a/src/pydenticon.py b/src/pydenticon.py deleted file mode 100644 index 57076093..00000000 --- a/src/pydenticon.py +++ /dev/null @@ -1,409 +0,0 @@ -""" -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 \ No newline at end of file diff --git a/src/identicon.py b/src/qidenticon.py similarity index 56% rename from src/identicon.py rename to src/qidenticon.py index fae5a250..5d77aeb6 100644 --- a/src/identicon.py +++ b/src/qidenticon.py @@ -1,6 +1,40 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- + +### +# qidenticon.py is Licesensed under FreeBSD License. +# (http://www.freebsd.org/copyright/freebsd-license.html) +# +# Copyright 2013 "Sendiulo". 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. +### + +### +# 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. +### + """ +qidenticon.py +identicon python implementation with QPixmap output +by sendiulo + +based on identicon.py identicon python implementation. by Shin Adachi @@ -11,19 +45,17 @@ by Shin Adachi >>> python identicon.py [code] == python == ->>> import identicon ->>> identicon.render_identicon(code, size) +>>> import qtidenticon +>>> qtidenticon.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 +# we probably don't need all of them, but i don't want to check now +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import * +from PyQt4.QtGui import * __all__ = ['render_identicon', 'IdenticonRendererBase'] @@ -114,45 +146,46 @@ class IdenticonRendererBase(object): def render(self, size): """ - render identicon to PIL.Image + render identicon to QPixmap @param size identicon patchsize. (image size is 3 * [size]) - @return PIL.Image + @return QPixmap """ # 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) + # make image + image = QPixmap(QSize(size * 3, size * 3)) # fill background - draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0) + image.fill(QtGui.QColor(0,0,0)) kwds = { - 'draw': draw, + 'image': image, 'size': size, 'foreColor': foreColor, 'backColor': backColor} + # middle patch - self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds) - + image = self.drawPatchQt((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) - + image = self.drawPatchQt(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) + image = self.drawPatchQt(pos, corner[2] + 1 + i, corner[1], **kwds) return image - def drawPatch(self, pos, turn, invert, type, draw, size, foreColor, + + def drawPatchQt(self, pos, turn, invert, type, image, size, foreColor, backColor): """ @param size patch size @@ -162,24 +195,43 @@ class IdenticonRendererBase(object): # blank patch invert = not invert path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)] - patch = ImagePath.Path(path) + + + polygon = QPolygonF([QPointF(x*size,y*size) for x,y in path]) + + rot = turn % 4 + rot_trans = [QPointF(0.,0.), QPointF(size, 0.), QPointF(size, size), QPointF(0., size)] + rotation = [0,90,180,270] + # print rotation[rot], rot_trans[rot], turn + pen_color = QtGui.QColor(255, 255, 255, 0) + pen = QtGui.QPen(pen_color, Qt.SolidPattern) + foreBrush = QtGui.QBrush(foreColor, Qt.SolidPattern) + backBrush = QtGui.QBrush(backColor, Qt.SolidPattern) if invert: - foreColor, backColor = backColor, foreColor + foreBrush, backBrush = backBrush, foreBrush - mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\ - Matrix2D.translate(*pos) *\ - Matrix2D.scale(size, size) + painter = QPainter() + painter.begin(image) + painter.setPen(pen) - 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) + painter.translate(pos[0]*size, pos[1]*size) + painter.translate(rot_trans[rot]) + painter.rotate(rotation[rot]) + + painter.setBrush(backBrush) + painter.drawRect(0,0, size, size) + + painter.setBrush(foreBrush) + painter.drawPolygon(polygon, Qt.WindingFill) + + painter.end() + + return image ### virtual functions def decode(self, code): raise NotImplementedError - - + class DonRenderer(IdenticonRendererBase): """ Don Park's implementation of identicon @@ -230,11 +282,14 @@ class DonRenderer(IdenticonRendererBase): red = (code >> 27) & 0x1F foreColor = (red << 3, green << 3, blue << 3) + foreColor = QtGui.QColor(*foreColor) + + bgcolor = QtGui.QColor(255,255,255) return (middleType, middleInvert, 0),\ (cornerType, cornerInvert, cornerTurn),\ (sideType, sideInvert, sideTurn),\ - foreColor, ImageColor.getrgb('white') + foreColor, bgcolor def render_identicon(code, size, renderer=None): @@ -258,5 +313,6 @@ if __name__ == '__main__': else: code = int(code) + app = Qt.QApplication(sys.argv) icon = render_identicon(code, 24) icon.save('%08x.png' % code, 'PNG')