This small notebook implements a generator of small square icons like the ones in GitHub, as implemented by identicon.js.
import random
import numpy as np
from matplotlib.colors import hsv_to_rgb
from PIL import Image as PILImage, ImageDraw as PILImageDraw
%load_ext watermark
%watermark
%watermark -a "Lilian Besson (Naereen)" -p numpy,matplotlib,PIL
def identicon(hashval=None,
size=256,
margin=0.11,
foreground=(255,0,0,255),
background=(240,240,240,255),
saturation=0.7,
brightness=0.5,
xsym=True,
):
if hashval is not None:
if isinstance(hashval, str):
hashval = abs(hash(hashval))
random.seed(int(hashval))
hue = random.random()
foreground = tuple(np.array(hsv_to_rgb([hue, saturation, brightness]) * 256, dtype=int))
border = int(size * margin)
ampl = size - 2 * border
def cropfirst(x):
return max(border, min(border + x, size - border))
def cropsecond(x):
return max(border, min(size - border - x, size - border))
# make a blank image
im = PILImage.new('RGBA', (size,size), background)
# get a drawing context
d = PILImageDraw.Draw(im)
def rect(xy):
x1, y1, x2, y2 = xy
r1 = (cropfirst(x1), cropfirst(y1), cropfirst(x2), cropfirst(y2))
if xsym:
r2 = (cropsecond(x1), cropfirst(y1), cropsecond(x2), cropfirst(y2))
else:
r2 = (cropfirst(x1), cropsecond(y1), cropfirst(x2), cropsecond(y2))
d.rectangle(r1, fill=foreground)
d.rectangle(r2, fill=foreground)
nbrectangle = random.randint(4, 8)
for i in range(nbrectangle):
x1, y1 = [random.randint(0, ampl) for _ in range(2)]
dx, dy = [random.randint(ampl // 8, ampl // 2) for _ in range(2)]
dirx, diry = [random.choice([+1, -1]) for _ in range(2)]
rect((x1, y1, x1 + dirx * dx, y1 + diry * dy))
return im
im = identicon()
im
from itertools import product
def identicon2(hashval=None,
size=256,
nbsquares=5,
margin=0.09,
foreground=(255,0,0,255),
background=(240,240,240,255),
saturation=0.7,
brightness=0.5,
xsym=True,
):
if hashval is not None:
if isinstance(hashval, str):
hashval = abs(hash(hashval))
random.seed(int(hashval) % 1<<28)
np.random.seed(int(hashval) % 1<<28)
hue = random.random()
foreground = tuple(np.array(hsv_to_rgb([hue, saturation, brightness]) * 256, dtype=int))
border = int(size * margin)
ampl = size - 2 * border
size_square = ampl // nbsquares
def cropfirst(x):
return max(border, min(border + x, size - border))
def cropsecond(x):
return max(border, min(size - border - x, size - border))
# make a blank image
im = PILImage.new('RGBA', (size,size), background)
# get a drawing context
d = PILImageDraw.Draw(im)
def rect(xy, xsym=xsym):
x1, y1, x2, y2 = xy
r1 = (cropfirst(x1), cropfirst(y1), cropfirst(x2), cropfirst(y2))
if xsym:
r2 = (cropsecond(x1), cropfirst(y1), cropsecond(x2), cropfirst(y2))
else:
r2 = (cropfirst(x1), cropsecond(y1), cropfirst(x2), cropsecond(y2))
d.rectangle(r1, fill=foreground)
d.rectangle(r2, fill=foreground)
nbrectangle = random.randint(5, nbsquares * (nbsquares // 2 + 1) - 2)
possible_coordinates = [(x, y) for (x, y) in product(range(nbsquares), range(nbsquares // 2 + 1))]
for i in np.random.choice(len(possible_coordinates), size=nbrectangle, replace=False):
y, x = possible_coordinates[i]
rect((x * size_square, y * size_square, (x+1) * size_square, (y+1) * size_square))
return im
im = identicon2()
im
This second implementation is better: it is deterministic, when given a string or a hash:
for _ in range(2):
im = identicon2("Naereen")
im
And every parameter can be changed and tuned.
for _ in range(10):
im = identicon2(size=128)
im
mylist = [identicon2() for _ in range(10)]
Let's use this technique
import base64
from IPython.display import Image
from IPython.display import HTML
def tag_from_data(data, size='100%'):
return (
'''<img
style="display:inline;
width:{1};
max-width:400px;
padding:10px;
margin-top:14px"
src="data:image/png;base64,{0}"
/>
''').format(''.join(base64.encodebytes(data).decode().split('\n')), size)
import io
def im2bytes(im):
buffer = io.BytesIO()
im.save(buffer, format="png")
return buffer.getvalue()
HTML(tag_from_data(im2bytes(im)))
class VignetteList:
def __init__(self, *images, size=None):
self.images = images
self.size = size
def _repr_html_(self):
return '<p>'+','.join(tag_from_data(im2bytes(im), self.size) for im in self.images)+'</p>'
VignetteList(*mylist, size='200px')
def tag_from_data_II(data, size='100%'):
return '''<img
style="
display:inline;
width:{1};
max-width:400px;
padding:10px;
margin-top:14px"
onMouseOver="this.style['box-shadow']='5px 5px 30px 0px rgba(163,163,163,1)'"
onMouseOut="this.style['box-shadow']=''"
src="data:image/png;base64,{0}"
/>'''.format(''.join(base64.encodebytes(data).decode().split('\n')), size)
def html_list_formatter(ll):
html = get_ipython().display_formatter.formatters['text/html']
reps = []
for o in ll:
if isinstance(o, Image):
reps.append(tag_from_data_II(o.data, '200px') )
elif isinstance(im, PILImage.Image):
reps.append(tag_from_data_II(im2bytes(o), '200px') )
else:
h = html(o)
if h:
reps.append(h)
else:
reps.append(repr(o)+'')
return '<span>['+','.join(reps)+']</span>'
ipython = get_ipython()
html = ipython.display_formatter.formatters['text/html']
html.for_type(list, html_list_formatter)
mylist
That's it for today, folks! See on my GitHub for more notebooks