This repository has been archived on 2024-12-19. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2024-12-19/src/qidenticon.py

277 lines
9.3 KiB
Python

###
# qidenticon.py is Licesensed under FreeBSD License.
# (http://www.freebsd.org/copyright/freebsd-license.html)
#
# Copyright 1994-2009 Shin Adachi. All rights reserved.
# Copyright 2013 "Sendiulo". All rights reserved.
# Copyright 2018-2021 The Bitmessage Developers. 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.
###
# pylint: disable=too-many-locals,too-many-arguments,too-many-function-args
"""
Usage
-----
>>> import qidenticon
>>> qidenticon.render_identicon(code, size)
Returns an instance of :class:`QPixmap` which have generated identicon image.
``size`` specifies `patch size`. Generated image size is 3 * ``size``.
"""
from six.moves import range
try:
from PyQt5 import QtCore, QtGui
except (ImportError, RuntimeError):
from PyQt4 import QtCore, QtGui
class IdenticonRendererBase(object):
"""Encapsulate methods around rendering identicons"""
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, twoColor, opacity, penwidth):
"""
render identicon to QPixmap
:param size: identicon patchsize. (image size is 3 * [size])
:returns: :class:`QPixmap`
"""
# decode the code
middle, corner, side, foreColor, secondColor, swap_cross = \
self.decode(self.code, twoColor)
# make image
image = QtGui.QPixmap(
QtCore.QSize(size * 3 + penwidth, size * 3 + penwidth))
# fill background
backColor = QtGui.QColor(255, 255, 255, opacity)
image.fill(backColor)
kwds = {
'image': image,
'size': size,
'foreColor': foreColor if swap_cross else secondColor,
'penwidth': penwidth,
'backColor': backColor}
# middle patch
image = self.drawPatchQt(
(1, 1), middle[2], middle[1], middle[0], **kwds)
# side patch
kwds['foreColor'] = foreColor
kwds['patch_type'] = side[0]
for i in range(4):
pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i]
image = self.drawPatchQt(pos, side[2] + 1 + i, side[1], **kwds)
# corner patch
kwds['foreColor'] = secondColor
kwds['patch_type'] = corner[0]
for i in range(4):
pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i]
image = self.drawPatchQt(pos, corner[2] + 1 + i, corner[1], **kwds)
return image
def drawPatchQt(
self, pos, turn, invert, patch_type, image, size, foreColor,
backColor, penwidth): # pylint: disable=unused-argument
"""
:param size: patch size
"""
path = self.PATH_SET[patch_type]
if not path:
# blank patch
invert = not invert
path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)]
polygon = QtGui.QPolygonF([
QtCore.QPointF(x * size, y * size) for x, y in path])
rot = turn % 4
rect = [
QtCore.QPointF(0., 0.), QtCore.QPointF(size, 0.),
QtCore.QPointF(size, size), QtCore.QPointF(0., size)]
rotation = [0, 90, 180, 270]
nopen = QtGui.QPen(foreColor, QtCore.Qt.NoPen)
foreBrush = QtGui.QBrush(foreColor, QtCore.Qt.SolidPattern)
if penwidth > 0:
pen_color = QtGui.QColor(255, 255, 255)
pen = QtGui.QPen(pen_color, QtCore.Qt.SolidPattern)
pen.setWidth(penwidth)
painter = QtGui.QPainter()
painter.begin(image)
painter.setPen(nopen)
painter.translate(
pos[0] * size + penwidth / 2, pos[1] * size + penwidth / 2)
painter.translate(rect[rot])
painter.rotate(rotation[rot])
if invert:
# subtract the actual polygon from a rectangle to invert it
poly_rect = QtGui.QPolygonF(rect)
polygon = poly_rect.subtracted(polygon)
painter.setBrush(foreBrush)
if penwidth > 0:
# draw the borders
painter.setPen(pen)
painter.drawPolygon(polygon, QtCore.Qt.WindingFill)
# draw the fill
painter.setPen(nopen)
painter.drawPolygon(polygon, QtCore.Qt.WindingFill)
painter.end()
return image
def decode(self, code, twoColor):
"""virtual functions"""
raise NotImplementedError
class DonRenderer(IdenticonRendererBase):
"""
Don Park's implementation of identicon, see:
https://blog.docuverse.com/2007/01/18/identicon-updated-and-source-released
"""
PATH_SET = [
# [0] full square:
[(0, 0), (4, 0), (4, 4), (0, 4)],
# [1] right-angled triangle pointing top-left:
[(0, 0), (4, 0), (0, 4)],
# [2] upwardy triangle:
[(2, 0), (4, 4), (0, 4)],
# [3] left half of square, standing rectangle:
[(0, 0), (2, 0), (2, 4), (0, 4)],
# [4] square standing on diagonale:
[(2, 0), (4, 2), (2, 4), (0, 2)],
# [5] kite pointing topleft:
[(0, 0), (4, 2), (4, 4), (2, 4)],
# [6] Sierpinski triangle, fractal triangles:
[(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)],
# [7] sharp angled lefttop pointing triangle:
[(0, 0), (4, 2), (2, 4)],
# [8] small centered square:
[(1, 1), (3, 1), (3, 3), (1, 3)],
# [9] two small triangles:
[(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)],
# [10] small topleft square:
[(0, 0), (2, 0), (2, 2), (0, 2)],
# [11] downpointing right-angled triangle on bottom:
[(0, 2), (4, 2), (2, 4)],
# [12] uppointing right-angled triangle on bottom:
[(2, 2), (4, 4), (0, 4)],
# [13] small rightbottom pointing right-angled triangle on topleft:
[(2, 0), (2, 2), (0, 2)],
# [14] small lefttop pointing right-angled triangle on topleft:
[(0, 0), (2, 0), (0, 2)],
# [15] empty:
[]]
# get the [0] full square, [4] square standing on diagonale,
# [8] small centered square, or [15] empty tile:
MIDDLE_PATCH_SET = [0, 4, 8, 15]
# modify path set
for idx, path in enumerate(PATH_SET):
if path:
p = [(vec[0] / 4.0, vec[1] / 4.0) for vec in path]
PATH_SET[idx] = p + p[:1]
def decode(self, code, twoColor):
"""decode the code"""
shift = 0
middleType = (code >> shift) & 0x03
shift += 2
middleInvert = (code >> shift) & 0x01
shift += 1
cornerType = (code >> shift) & 0x0F
shift += 4
cornerInvert = (code >> shift) & 0x01
shift += 1
cornerTurn = (code >> shift) & 0x03
shift += 2
sideType = (code >> shift) & 0x0F
shift += 4
sideInvert = (code >> shift) & 0x01
shift += 1
sideTurn = (code >> shift) & 0x03
shift += 2
blue = (code >> shift) & 0x1F
shift += 5
green = (code >> shift) & 0x1F
shift += 5
red = (code >> shift) & 0x1F
shift += 5
second_blue = (code >> shift) & 0x1F
shift += 5
second_green = (code >> shift) & 0x1F
shift += 5
second_red = (code >> shift) & 0x1F
shift += 1
swap_cross = (code >> shift) & 0x01
middleType = self.MIDDLE_PATCH_SET[middleType]
foreColor = (red << 3, green << 3, blue << 3)
foreColor = QtGui.QColor(*foreColor)
if twoColor:
secondColor = (
second_blue << 3, second_green << 3, second_red << 3)
secondColor = QtGui.QColor(*secondColor)
else:
secondColor = foreColor
return (middleType, middleInvert, 0),\
(cornerType, cornerInvert, cornerTurn),\
(sideType, sideInvert, sideTurn),\
foreColor, secondColor, swap_cross
def render_identicon(
code, size, twoColor=False, opacity=255, penwidth=0, renderer=None):
"""Render an image"""
if not renderer:
renderer = DonRenderer
return renderer(code).render(size, twoColor, opacity, penwidth)