Files
@ 53682a76e7bb
Branch filter:
Location: pydenticon/pydenticon/__init__.py - annotation
53682a76e7bb
12.8 KiB
text/x-python
Noticket: Bumping version to development.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ede92b1326 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ab3e996c07 23ede92b1326 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a 23ede92b1326 93602620893a 93602620893a 93602620893a 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a 93602620893a 93602620893a 23ede92b1326 23ede92b1326 93602620893a 23ede92b1326 23ede92b1326 23ede92b1326 57a095aafd9b 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ab3e996c07 23ab3e996c07 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 e0cf5e9351ce e0cf5e9351ce 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a 93602620893a 93602620893a 93602620893a 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a 93602620893a 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a 23ede92b1326 23ede92b1326 93602620893a 93602620893a 93602620893a 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 23ede92b1326 93602620893a | # For digest operations.
import hashlib
# For saving the images from Pillow.
from io import BytesIO
# Pillow for Image processing.
from PIL import Image, ImageDraw
# For decoding hex values (works both for Python 2.7.x and Python 3.x).
import binascii
class Generator(object):
"""
Factory class that can be used for generating the identicons
deterministically based on hash of the passed data.
Resulting identicons are images of requested size with optional padding. The
identicon (without padding) consists out of M x N blocks, laid out in a
rectangle, where M is the number of blocks in each column, while N is number
of blocks in each row.
Each block is a smallself rectangle on its own, filled using the foreground or
background colour.
The foreground is picked randomly, based on the passed data, from the list
of foreground colours set during initialisation of the generator.
The blocks are always laid-out in such a way that the identicon will be
symterical by the Y axis. The center of symetry will be the central column
of blocks.
Simply put, the generated identicons are small symmetric mosaics with
optional padding.
"""
def __init__(self, rows, columns, digest=hashlib.md5, foreground=["#000000"], background="#ffffff"):
"""
Initialises an instance of identicon generator. The instance can be used
for creating identicons with differing image formats, sizes, and with
different padding.
Arguments:
rows - Number of block rows in an identicon.
columns - Number of block columns in an identicon.
digest - Digest class that should be used for the user's data. The
class should support accepting a single constructor argument for
passing the data on which the digest will be run. Instances of the
class should also support a single hexdigest() method that should
return a digest of passed data as a hex string. Default is
hashlib.md5. Selection of the digest will limit the maximum values
that can be set for rows and columns. Digest needs to be able to
generate (columns / 2 + columns % 2) * rows + 8 bits of entropy.
foreground - List of colours which should be used for drawing the
identicon. Each element should be a string of format supported by the
PIL.ImageColor module. Default is ["#000000"] (only black).
background - Colour (single) which should be used for background and
padding, represented as a string of format supported by the
PIL.ImageColor module. Default is "#ffffff" (white).
"""
# Check if the digest produces sufficient entropy for identicon
# generation.
entropy_provided = len(digest(b"test").hexdigest()) // 2 * 8
entropy_required = (columns // 2 + columns % 2) * rows + 8
if entropy_provided < entropy_required:
raise ValueError("Passed digest '%s' is not capable of providing %d bits of entropy" % (str(digest), entropy_required))
# Set the expected digest size. This is used later on to detect if
# passed data is a digest already or not.
self.digest_entropy = entropy_provided
self.rows = rows
self.columns = columns
self.foreground = foreground
self.background = background
self.digest = digest
def _get_bit(self, n, hash_bytes):
"""
Determines if the n-th bit of passed bytes is 1 or 0.
Arguments:
hash_bytes - List of hash byte values for which the n-th bit value
should be checked. Each element of the list should be an integer from
0 to 255.
Returns:
True if the bit is 1. False if the bit is 0.
"""
if hash_bytes[n // 8] >> int(8 - ((n % 8) + 1)) & 1 == 1:
return True
return False
def _generate_matrix(self, hash_bytes):
"""
Generates matrix that describes which blocks should be coloured.
Arguments:
hash_bytes - List of hash byte values for which the identicon is being
generated. Each element of the list should be an integer from 0 to
255.
Returns:
List of rows, where each element in a row is boolean. True means the
foreground colour should be used, False means a background colour
should be used.
"""
# Since the identicon needs to be symmetric, we'll need to work on half
# the columns (rounded-up), and reflect where necessary.
half_columns = self.columns // 2 + self.columns % 2
cells = self.rows * half_columns
# Initialise the matrix (list of rows) that will be returned.
matrix = [[False] * self.columns for _ in range(self.rows)]
# Process the cells one by one.
for cell in range(cells):
# If the bit from hash correpsonding to this cell is 1, mark the
# cell as foreground one. Do not use first byte (since that one is
# used for determining the foreground colour.
if self._get_bit(cell, hash_bytes[1:]):
# Determine the cell coordinates in matrix.
column = cell // self.columns
row = cell % self.rows
# Mark the cell and its reflection. Central column may get
# marked twice, but we don't care.
matrix[row][column] = True
matrix[row][self.columns - column - 1] = True
return matrix
def _data_to_digest_byte_list(self, data):
"""
Creates digest of data, returning it as a list where every element is a
single byte of digest (an integer between 0 and 255).
No digest will be calculated on the data if the passed data is already a
valid hex string representation of digest, and the passed value will be
used as digest in hex string format instead.
Arguments:
data - Raw data or hex string representation of existing digest for
which a list of one-byte digest values should be returned.
Returns:
List of integers where each element is between 0 and 255, and
repesents a single byte of a data digest.
"""
# If data seems to provide identical amount of entropy as digest, it
# could be a hex digest already.
if len(data) // 2 == self.digest_entropy // 8:
try:
binascii.unhexlify(data.encode('utf-8'))
digest = data.encode('utf-8')
# Handle Python 2.x exception.
except (TypeError):
digest = self.digest(data.encode('utf-8')).hexdigest()
# Handle Python 3.x exception.
except (binascii.Error):
digest = self.digest(data.encode('utf-8')).hexdigest()
else:
digest = self.digest(data.encode('utf-8')).hexdigest()
return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
def _generate_image(self, matrix, width, height, padding, foreground, background, image_format):
"""
Generates an identicon image in requested image format out of the passed
block matrix, with the requested width, height, padding, foreground
colour, background colour, and image format.
Arguments:
matrix - Matrix describing which blocks in the identicon should be
painted with foreground (background if inverted) colour.
width - Width of resulting identicon image in pixels.
height - Height of resulting identicon image in pixels.
padding - Tuple describing padding around the generated identicon. The
tuple should consist out of four values, where each value is the
number of pixels to use for padding. The order in tuple is: top,
bottom, left, right.
foreground - Colour which should be used for foreground (filled
blocks), represented as a string of format supported by the
PIL.ImageColor module.
background - Colour which should be used for background and padding,
represented as a string of format supported by the PIL.ImageColor
module.
image_format - Format to use for the image. Format needs to be
supported by the Pillow library.
Returns:
Identicon image in requested format, returned as a byte list.
"""
# Set-up a new image object, setting the background to provided value.
image = Image.new("RGBA", (width + padding[2] + padding[3], height + padding[0] + padding[1]), background)
# Set-up a draw image (for drawing the blocks).
draw = ImageDraw.Draw(image)
# Calculate the block widht and height.
block_width = width // self.columns
block_height = height // self.rows
# Go through all the elements of a matrix, and draw the rectangles.
for row, row_columns in enumerate(matrix):
for column, cell in enumerate(row_columns):
if cell:
# Set-up the coordinates for a block.
x1 = padding[2] + column * block_width
y1 = padding[0] + row * block_height
x2 = padding[2] + (column + 1) * block_width - 1
y2 = padding[0] + (row + 1) * block_height - 1
# Draw the rectangle.
draw.rectangle((x1, y1, x2, y2), fill=foreground)
# Set-up a stream where image will be saved.
stream = BytesIO()
# Save the image to stream.
try:
image.save(stream, format=image_format, optimize=True)
except KeyError:
raise ValueError("Pillow does not support requested image format: %s" % image_format)
image_raw = stream.getvalue()
stream.close()
# Return the resulting image.
return image_raw
def _generate_ascii(self, matrix, foreground, background):
"""
Generates an identicon "image" in the ASCII format. The image will just
output the matrix used to generate the identicon.
Arguments:
matrix - Matrix describing which blocks in the identicon should be
painted with foreground (background if inverted) colour.
foreground - Character which should be used for representing
foreground.
background - Character which should be used for representing
background.
Returns:
ASCII representation of an identicon image, where one block is one
character.
"""
return "\n".join(["".join([foreground if cell else background for cell in row]) for row in matrix])
def generate(self, data, width, height, padding=(0, 0, 0, 0), output_format="png", inverted=False):
"""
Generates an identicon image with requested width, height, padding, and
output format, optionally inverting the colours in the indeticon
(swapping background and foreground colours) if requested.
Arguments:
data - Hashed or raw data that will be used for generating the
identicon.
width - Width of resulting identicon image in pixels.
height - Height of resulting identicon image in pixels.
padding - Tuple describing padding around the generated identicon. The
tuple should consist out of four values, where each value is the
number of pixels to use for padding. The order in tuple is: top,
bottom, left, right.
output_format - Output format of resulting identicon image. Supported
formats are anything that is supported by Pillow, plus a special
"ascii" mode.
inverted - Specifies whether the block colours should be inverted or
not. Default is False.
Returns:
Byte representation of an identicon image.
"""
# Calculate the digest, and get byte list.
digest_byte_list = self._data_to_digest_byte_list(data)
# Create the matrix describing which block should be filled-in.
matrix = self._generate_matrix(digest_byte_list)
# Determine the background and foreground colours.
if output_format == "ascii":
foreground = "+"
background = "-"
else:
background = self.background
foreground = self.foreground[digest_byte_list[0] % len(self.foreground)]
# Swtich the colours if inverted image was requested.
if inverted:
foreground, background = background, foreground
# Generate the identicon in requested format.
if output_format == "ascii":
return self._generate_ascii(matrix, foreground, background)
else:
return self._generate_image(matrix, width, height, padding, foreground, background, output_format)
|