Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterised aspects of the identicon #2

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
0.2.0
-----
- Added parameter for input_hash_str (Allows the hash to be calculated via other means other than the default MD5)
- Added parameter for image size
- Added parameter for padding
- Added parameter for background color
- Added parameter for foreground color
- Added parameter for corner radius
- Added parameter for image type
- Color is now chosen using HLS (The Hue is varied, while lightness and saturation remains constant)
- Added parameter for lightness
- Added parameter for saturation
- Allowed dyanmic background color and static foreground color

0.1.1
-----

Expand Down
101 changes: 60 additions & 41 deletions Identicon/__init__.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,69 @@
# -*- coding:utf-8 -*-
__version__='0.1.1'

import io
import math
import hashlib
import colorsys
from PIL import Image, ImageDraw

BACKGROUND_COLOR = (244, 244, 244)
DEFAULT_BACKGROUND_COLOR = (244, 244, 244)
DEFAULT_PADDING = 20
DEFAULT_SIZE = 290
DEFAULT_IMAGE_TYPE = 'PNG'
DEFAULT_LIGHTNESS = 0.5
DEFAULT_SATURATION = 0.7

def render(code):
hex_list = _to_hash_hex_list(code)
color = _extract_color(hex_list)
grid = _build_grid(hex_list)
def render(input_str, input_hash_str=None, size=DEFAULT_SIZE, padding=DEFAULT_PADDING, background_color=DEFAULT_BACKGROUND_COLOR, foreground_color=None, lightness=DEFAULT_LIGHTNESS, saturation=DEFAULT_SATURATION, corner_radius=None, image_type=DEFAULT_IMAGE_TYPE):

# Generate colors
hex_str = input_hash_str if input_hash_str else _to_hash_hex_str(input_str)
generated_color = _extract_color(hex_str, lightness, saturation)
background_color = generated_color if background_color is None else background_color
foreground_color = generated_color if foreground_color is None else foreground_color

# Generate blocks
number_of_blocks = 5 # varying the number of blocks is currently unsupported
block_size = (size-2*padding)/number_of_blocks

grid = _build_grid(hex_str, number_of_blocks)
flatten_grid = _flat_to_list(grid)
pixels = _set_pixels(flatten_grid)
identicon_im = _draw_identicon(color, flatten_grid, pixels)
pixels = _set_pixels(flatten_grid, number_of_blocks, block_size, padding)
identicon_im = _draw_identicon(background_color, foreground_color, size, flatten_grid, pixels)

# Add radius crop
if corner_radius is not None:
identicon_im = _crop_coner_round(identicon_im, corner_radius)

# Generate byte array
identicon_byte_arr = io.BytesIO()
identicon_im.save(identicon_byte_arr, format='PNG')
identicon_im.save(identicon_byte_arr, format=image_type)
identicon_byte_arr = identicon_byte_arr.getvalue()

return identicon_byte_arr

def _to_hash_hex_list(code):
def _to_hash_hex_str(input_str):
# TODO: Choose hash scheme
hash = hashlib.md5(code.encode('utf8'))
hash = hashlib.md5(input_str.encode('utf8'))

return hash.hexdigest()

def _extract_color(hex_list):
r,g,b =tuple(hex_list[i:i+2]
for i in range(0, 2*3, 2))

return f'#{r}{g}{b}'

def _set_pixels(flatten_grid):
# len(list) should be a squared of integer value
# Caculate pixels
pixels = []
for i, val in enumerate(flatten_grid):
x = int(i%5 * 50) + 20
y = int(i//5 * 50) + 20

top_left = (x, y)
bottom_right = (x + 50, y + 50)

pixels.append([top_left, bottom_right])
def _extract_color(hex_str, lightness, saturation):
hue = (int(hex_str[-7:], 16) / float(0xfffffff))

return pixels
r,g,b = (int(v*255) for v in colorsys.hls_to_rgb(hue, lightness, saturation))
return '#{:02x}{:02x}{:02x}'.format(r, g, b)

def _build_grid(hex_list):
# Tailing hex_list to rear 15 bytes
hex_list_tail = hex_list[2:]
def _build_grid(hex_str, number_of_blocks):
# Tailing hex_str to rear 15 bytes
hex_str_tail = hex_str[2:]

# Make 3x5 gird, half of the symmetric grid(left side)
hex_half_grid = [[hex_list_tail[col:col+2] for col in range(row, row+2*3, 2)]
for row in range(0, 2*3*5, 2*3)]
half_number_of_blocks = int(math.ceil(number_of_blocks/2.0))
hex_half_grid = [[hex_str_tail[col:col+2] for col in range(row, row+2*half_number_of_blocks, 2)]
for row in range(0, 2*half_number_of_blocks*number_of_blocks, 2*half_number_of_blocks)]

hex_grid = _mirror_row(hex_half_grid)

int_grid = [list(map(lambda e: int(e ,base=16), row)) for row in hex_grid]

# TODO: Using more entropies, should be deprecated
Expand All @@ -77,14 +83,27 @@ def _flat_to_list(nested_list):

return flatten_list

def _draw_identicon(color, grid_list, pixels):
identicon_im = Image.new('RGB', (50*5+20*2, 50*5+20*2), BACKGROUND_COLOR)
def _set_pixels(flatten_grid, number_of_blocks, block_size, padding):
# len(list) should be a squared of integer value
# Caculate pixels
pixels = []
for i, val in enumerate(flatten_grid):
x = int(i%number_of_blocks * block_size) + padding
y = int(i//number_of_blocks * block_size) + padding

top_left = (x, y)
bottom_right = (x + block_size, y + block_size)

pixels.append([top_left, bottom_right])

return pixels

def _draw_identicon(background, foreground, size, grid_list, pixels):
identicon_im = Image.new('RGB', (size, size), background)
draw = ImageDraw.Draw(identicon_im)
for grid, pixel in zip(grid_list, pixels):
if grid != 0: # for not zero
draw.rectangle(pixel, fill=color)

identicon_im = _crop_coner_round(identicon_im, 50)
draw.rectangle(pixel, fill=foreground)

return identicon_im

Expand Down
4 changes: 1 addition & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from setuptools import setup, find_packages
from os.path import isfile

import Identicon

def read(*names):
values = dict()
extensions = ['.txt', '.rst']
Expand All @@ -25,7 +23,7 @@ def read(*names):

setup(
name='Identicon',
version=Identicon.__version__,
version='0.2.0',
description='A Python library for generating Github-like identicons',
long_description=long_description,
keywords='identicon image profile render github',
Expand Down
14 changes: 7 additions & 7 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ def random_code(self, n):
return ''.join(random.choices(string.printable, k=n))

# Tests
def test_to_hash_hex_list_return_32bytes(self):
self.assertEqual(len(Identicon._to_hash_hex_list('some_code')), 32)
self.assertEqual(len(Identicon._to_hash_hex_list('other_code')), 32)
def test_to_hash_hex_str_return_32bytes(self):
self.assertEqual(len(Identicon._to_hash_hex_str('some_code')), 32)
self.assertEqual(len(Identicon._to_hash_hex_str('other_code')), 32)

def test_extract_color_return_hex_rgb_string(self):
hex_list = Identicon._to_hash_hex_list('test_extract_color')
hex_list = Identicon._to_hash_hex_str('test_extract_color')

self.assertRegex(Identicon._extract_color(hex_list), r'^#[0-9a-fA-F]{6}$')
self.assertRegex(Identicon._extract_color(hex_list, 0.5, 0.7), r'^#[0-9a-fA-F]{6}$')

def test_build_grid_return_5x5_list(self):
#dummy_hex_list = str(0x0123456789abcdef)
dummy_hex_list = hashlib.md5('dummy'.encode('utf8')).hexdigest()

grid = Identicon._build_grid(dummy_hex_list)
grid = Identicon._build_grid(dummy_hex_list, 5)

self.assertEqual(len(grid), 5)
for i in range(5):
Expand Down Expand Up @@ -64,7 +64,7 @@ def test_background_color_of_returned_identicon_is_correct(self):
rgb_image = image.convert('RGB')
background_color = rgb_image.getpixel((19,19))

self.assertEqual(Identicon.BACKGROUND_COLOR, background_color)
self.assertEqual(Identicon.DEFAULT_BACKGROUND_COLOR, background_color)



Expand Down