Changeset - 5df4ce2fcaa9
[Not reviewed]
0 2 5
Branko Majic (branko) - 10 years ago 2013-11-27 23:15:12
branko@majic.rs
PYD-2: Added the introductory (about) chapter. Added description of the algorithm, together with example and information about limitations. Added automatically-generated API reference. Configured Sphinx to use parent directory for finding library modules. Added basic information to the index page, including website links. Added installation instructions. Added usage instructions with a couple of examples.
7 files changed with 426 insertions and 7 deletions:
0 comments (0 inline, 0 general)
docs/about.rst
Show inline comments
 
new file 100644
 
About Pydenticon
 
================
 

	
 
Pydenticon is a small utility library that can be used for deterministically
 
generating identicons based on the hash of provided data.
 

	
 
The implementation is a port of the Sigil identicon implementation from:
 

	
 
* https://github.com/cupcake/sigil/
 

	
 
Why was this library created?
 
-----------------------------
 

	
 
A number of web-based applications written in Python have a need for visually
 
differentiating between users by using avatars for each one of them.
 

	
 
This functionality is particularly popular with comment-posting since it
 
increases the readability of threads.
 

	
 
The problem is that lots of those applications need to allow anonymous users to
 
post their comments as well. Since anonymous users cannot set the avatar for
 
themselves, usually a random avatar is created for them instead.
 

	
 
There is a number of free (as in free beer) services out there that allow web
 
application developers to create such avatars. Unfortunately, this usually means
 
that the users visiting websites based on those applications are leaking
 
information about their browsing habits etc to these third-party providers.
 

	
 
Pydenticon was written in order to resolve such an issue for one of the
 
application (Django Blog Zinnia, in particular), and to allow the author to set
 
up his own avatar/identicon service.
 

	
 
Features
 
--------
 

	
 
Pydenticon has the following features:
 

	
 
* Compatible with Sigil implementation (https://github.com/cupcake/sigil/) if
 
  set-up with right parameters.
 
* Creates vertically symmetrical identicons of any rectangular shape and size.
 
* Uses digests of passed data for generating the identicons.
 
  * Automatically detects if passed data is hashed already or not.
 
  * Custom digest implementations can be passed to identicon generator (defaults
 
  to 'MD5').
 
* Support for multiple image formats.
 
  * PNG
 
  * ASCII
 
* Foreground colour picked from user-provided list.
 
* Background colour set by the user.
 
* Ability to invert foreground and background colour in the generated identicon.
 
* Customisable padding around generated identicon using the background colour
 
  (foreground if inverted identicon was requested).
 

	
docs/algorithm.rst
Show inline comments
 
new file 100644
 
Algorithm
 
=========
 

	
 
A generated identicon can be described as one big rectangle divided into ``rows
 
x columns`` rectangle blocks of equal size, where each block can be filled with
 
the foreground colour or the background colour. Additionally, the whole
 
identicon is symmetrical to the central vertical axis, making it much more
 
aesthetically pleasing.
 

	
 
The algorithm used for generating the identicon is fairly simple. The input
 
arguments that determine what the identicon will look like are:
 

	
 
* Size of identicon in blocks (``rows x columns``).
 
* Algorithm used to create digests out of user-provided data.
 
* List of colours used for foreground fill (foreground colours). This list will
 
  be referred to as ``foreground_list``.
 
* Single colour used for background fill (background colour). This colour wil be
 
  referred to as ``background``.
 
* Whether the foreground and background colours should be inverted (swapped) or
 
  not.
 
* Data passed to be used for digest.
 

	
 
The first step is to generate a *digest* out of the passed data using the
 
selected digest algorithm. This digest is then split into two parts:
 

	
 
* The first byte of digest (``f``, for foreground) is used for determining the
 
  foreground colour.
 
* The remaining portion of digest (``l``, for layout) is used for determining
 
  which blocks of identicon will be filled using foreground and background
 
  colours.
 

	
 
In order to select a ``foreground`` colour, the algorithm will try to determine
 
the index of the colour in the ``foreground_list`` by doing modulo division of
 
the first byte's integer value with number of colours in
 
``foreground_list``::
 

	
 
  foreground = foreground_list[int(f) % len(foreground_list)]
 

	
 
The layout of blocks (which block gets filled with foreground colour, and which
 
block gets filled with background colour) is determined by the bit values of
 
remaining portion of digest (``l``). This remaining portion of digest can also
 
be seen as a list of bits. The bit positions would range from ``0`` to ``b``
 
(where the size of ``b`` would depend on the digest algoirthm that was picked).
 

	
 
Since the identicon needs to be symmetrical, the number of blocks for which the
 
fill colour needs to be calculated is equal to ``rows * (columns / 2 + columns %
 
2)``. I.e. the block matrix is split in half vertically (if number of columns is
 
odd, the middle column is included as well).
 

	
 
Those blocks can then be marked with whole numbers from ``0`` to ``c`` (where
 
``c`` would be equal to the above formula - ``rows * (columns / 2 + columns %
 
2)``). Number ``0`` would correspond to first block of the first half-row, ``1``
 
to the first block of the second row, ``2`` to the first block of the third row,
 
and so on to the first block of the last half-row. Then the blocks in the next
 
column would be indexed with numbers in a similar (incremental) way.
 

	
 
With these two numbering methods in place (for digest bits and blocks of
 
half-matrix), every block is assigned a bit that has the same position number.
 

	
 
If no inversion of foreground and background colours was requested, bit value of
 
``1`` for a cell would mean the block should be filled with foreground colour,
 
while value of ``0`` would mean the block should be filled with background
 
colour.
 

	
 
If an inverted identicon was requested, then ``1`` would correspond to
 
background colour fill, and ``0`` would correspond to foreground colour fill.
 

	
 
Examples
 
--------
 

	
 
An identicon should be created with the following parameters:
 

	
 
* Size of identicon in blocks is ``5 x 5`` (a square).
 
* Digest algorithm is *MD5*.
 
* Five colours are used for identicon foreground (``0`` through ``4``).
 
* Some background colour is selected (marked as ``b``).
 
* Foreground and background colours are not to be inverted (swapped).
 
* Data used for digest is ``branko``.
 

	
 
MD5 digest for data (``branko``) would be (reperesented as hex value) equal to
 
``d41c0e80c44173dcf7575745bdddb704``.
 

	
 
In other words, 16 bytes would be present with the following hex values::
 

	
 
  d4 1c 0e 80 c4 41 73 dc f7 57 57 45 bd dd b7 04
 

	
 
Following the algorithm, the first byte (``d4``) is used to determine which
 
foreground colour to use. ``d4`` is equal to ``212`` in decimal format. Divided
 
by modulo ``5`` (number of foreground colours), the resulting index of
 
foreground colour is ``2`` (third colour in the foreground list).
 

	
 
The remaining 15 bytes will be used for figuring out the layout. The
 
representation of those bytes in binary format would look like this (5 bytes per
 
row)::
 

	
 
  00011100 00001110 10000000 11000100 01000001
 
  01110011 11011100 11110111 01010111 01010111
 
  01000101 10111101 11011101 10110111 00000100
 

	
 
Since identicon consits out of 5 columns and 5 rows, the number of bits that's
 
needed from ``l`` for the layout would be ``5 * (5 / 2 + 5 % 2) == 15``. This
 
means that the following bits will determine the layout of identicon (whole
 
first byte, and 7 bits of the second byte)::
 

	
 
  00011100 0000111
 

	
 
The half-matrix would therefore end-up looking like this (5 bits per column for
 
5 blocks per column)::
 

	
 
  010
 
  000
 
  001
 
  101
 
  101
 

	
 
The requested identicon is supposed to have 5 block columns, so a reflection
 
will be applied to the first and second column, with third column as center of
 
the symmetry. This would result in the following ideticon matrix::
 

	
 
  01010
 
  00000
 
  00100
 
  10101
 
  10101
 

	
 
Since no inversion was requested, ``1`` would correspond to calculated
 
foreground colour, while ``0`` would correspond to provided background colour.
 

	
 
Limitations
 
-----------
 

	
 
There's some practical limitations to the algorithm described above.
 

	
 
The first limitation is the maximum number of different foreground colours that
 
can be used for identicon generation. Since a single byte (which is used to
 
determining the colour) can represent 256 values (between 0 and 255), there can
 
be no more than 256 colours passed to be used for foreground of the
 
identicon. Any extra colours passed above that count would simply be ignored.
 

	
 
The second limitation is that the maximum dimensions (in blocks) of a generated
 
identicon depend on digest algorithm used. In order for a digest algorithm to be
 
able to satisfy requirements of producing an identcion with ``rows`` number of
 
rows, and ``columns`` number of columns (in blocks), it must be able to produce at
 
least the following number of bits (i.e. the number of bits equal to the number
 
of blocks in the half-matrix)::
 

	
 
  rows * (columns / 2 + columns % 2) + 8
 

	
 
The expression is the result of vertical symmetry of identicon.  Only the
 
columns up to, and including, the middle one middle one (``(columns / 2 + colums
 
% 2)``) need to be processed, with every one of those columns having ``row``
 
rows (``rows *``). Finally, an extra 8 bits (1 byte) are necessary for
 
determining the foreground colour.
 

	
docs/apireference.rst
Show inline comments
 
new file 100644
 
API Reference
 
=============
 

	
 
.. automodule:: pydenticon
 
   :members:
 

	
docs/conf.py
Show inline comments
 
@@ -17,6 +17,7 @@ import sys, os
 
# add these directories to sys.path here. If the directory is relative to the
 
# documentation root, use os.path.abspath to make it absolute, like shown here.
 
#sys.path.insert(0, os.path.abspath('.'))
 
sys.path.insert(0, os.path.abspath('..'))
 

	
 
# -- General configuration -----------------------------------------------------
 

	
docs/index.rst
Show inline comments
 
.. Pydenticon documentation master file, created by
 
   sphinx-quickstart on Mon Nov 25 23:13:33 2013.
 
   You can adapt this file completely to your liking, but it should at least
 
   contain the root `toctree` directive.
 
Pydenticon documentation
 
========================
 

	
 
Welcome to Pydenticon's documentation!
 
======================================
 
Pydenticon is a small utility library that can be used for deterministically
 
generating identicons based on the hash of provided data.
 

	
 
The implementation is a port of the Sigil identicon implementation from:
 

	
 
* https://github.com/cupcake/sigil/
 

	
 
Support
 
-------
 

	
 
In case of problems with the library, please do not hestitate to contact the
 
author at **pydenticon (at) majic.rs**. The library itself is hosted on Github,
 
and on author's own websitea:
 

	
 
* https://github.com/azaghal/pydenticon
 
* https://code.majic.rs/pydenticon
 
* https://projects.majic.rs/pydenticon
 

	
 
Contents:
 

	
 
.. toctree::
 
   :maxdepth: 2
 

	
 

	
 
   about
 
   installation
 
   usage
 
   algorithm
 
   security
 
   apireference
 
   releasenotes
 

	
 
Indices and tables
 
==================
docs/installation.rst
Show inline comments
 
new file 100644
 
Installation
 
============
 

	
 
Pydenticon can be installed through one of the following methods:
 

	
 
* Using *pip*, which is the easiest and recommended way for production websites.
 
* Manually, by copying the necessary files and installing the dependencies.
 

	
 
Requirements
 
------------
 

	
 
The main external requirement for Pydenticon is `Pillow
 
<http://python-imaging.github.io/>`_, which is used for generating the images.
 

	
 
Using pip
 
---------
 

	
 
In order to install latest stable release of Pydenticon using *pip*, run the
 
following command::
 

	
 
  pip install pydenticon
 

	
 
In order to install the latest development version of Pydenticon from Github,
 
use the following command::
 

	
 
  pip install -e git+https://github.com/azaghal/pydenticon#egg=pydenticon
 

	
 
Manual installation
 
-------------------
 

	
 
If you wish to install Pydenticon manually, make sure that its dependencies have
 
been met first, and then simply copy the ``pydenticon`` directory (that contains
 
the ``__init__.py`` file) somewhere on the Python path.
 

	
docs/usage.rst
Show inline comments
 
new file 100644
 
Usage
 
=====
 

	
 
Pydenticon provides simple and straightforward interface for setting-up the
 
identicon generator, and for generating the identicons.
 

	
 
Instantiating a generator
 
-------------------------
 

	
 
The starting point is to create a generator instance. Generator implements
 
interface that can be used for generating the identicons.
 

	
 
In its simplest form, the generator instances needs to be passed only the size
 
of identicon in blocks (first parameter is width, second is height)::
 

	
 
  # Import the library.
 
  import pydenticon
 

	
 
  # Instantiate a generator that will create 5x5 block identicons.
 
  generator = pydenticon.Generator(5, 5)
 

	
 
The above example will instantiate a generator that can be used for producing
 
identicons which are 5x5 blocks in size, using the default values for digest
 
(*MD5*), foreground colour (*black*), and background colour (*white*).
 

	
 
Alternatively, you may choose to pass in a different digest algorithm, and
 
foreground and background colours::
 

	
 
  # Import the libraries.
 
  import pydenticon
 
  import hashlib
 

	
 
  # Set-up a list of foreground colours (taken from Sigil).
 
  foreground = [ "rgb(45,79,255)",
 
                 "rgb(254,180,44)",
 
                 "rgb(226,121,234)",
 
                 "rgb(30,179,253)",
 
                 "rgb(232,77,65)",
 
                 "rgb(49,203,115)",
 
                 "rgb(141,69,170)" ] 
 

	
 
  # Set-up a background colour (taken from Sigil).
 
  background = "rgb(224,224,224)"
 

	
 
  # Instantiate a generator that will create 5x5 block identicons using SHA1
 
  # digest.
 
  generator = pydenticon.Generator(5, 5, digest=hashlib.sha1,
 
                                   foreground=foreground, background=background)
 

	
 
Generating identicons
 
---------------------
 

	
 
With generator initialised, it's now possible to use it to create the
 
identicons.
 

	
 
The most basic example would be creating an identicon using default padding (no
 
padding) and output format ("png"), without inverting the colours (which is also
 
the default)::
 

	
 
  # Generate a 240x240 PNG identicon.
 
  identicon = generator.generate("john.doe@example.com", 240, 240)
 

	
 
The result of the ``generate()`` method will be a raw representation of an
 
identicon image in requested format that can be written-out to file, sent back
 
as an HTTP response etc.
 

	
 
Usually it can be nice to have some padding around the generated identicon in
 
order to make it stand-out better, or maybe to invert the colours. This can be
 
done with::
 

	
 
  # Set-up the padding (top, bottom, left, right) in pixels.
 
  padding = (20, 20, 20, 20)
 

	
 
  # Generate a 200x200 identicon with padding around it, and invert the
 
  # background/foreground colours.
 
  identicon = generator.generate("john.doe@example.com", 200, 200,
 
                                 padding=padding, inverted=True)
 

	
 
Finally, the resulting identicons can be in different formats::
 

	
 
  # Create identicon in PNG format.
 
  identicon_png = generator.generate("john.doe@example.com", 200, 200,
 
                                     output_format="png")
 
  # Create identicon in ASCII format.
 
  identicon_ascii = generator.generate("john.doe@example.com", 200, 200,
 
                                       output_format="png")
 

	
 
Using the generated identicons
 
------------------------------
 

	
 
Of course, just generating the identicons is not that fun. They usually need
 
either to be stored somewhere on disk, or maybe streamed back to the user via
 
HTTP response. Since the generate function returns raw data, this is quite easy
 
to achieve::
 

	
 
  # Generate same identicon in two different formats.
 
  identicon_png = generator.generate("john.doe@example.com", 200, 200,
 
                                     output_format="png")
 
  identicon_ascii = generator.generate("john.doe@example.com", 200, 200,
 
                                       output_format="png")
 

	
 
  # Identicon can be easily saved to a file.
 
  f = open("sample.png", "w")
 
  f.write(identicon_png)
 
  f.close()
 

	
 
  # ASCII identicon can be printed-out to console directly.
 
  print identicon_ascii
 

	
 
Full example
 
------------
 

	
 
Finally, here is a full example that will create a number of identicons and
 
output them in PNG format to local directory::
 

	
 
  #!/usr/bin/env python
 

	
 
  # Import the libraries.
 
  import pydenticon
 
  import hashlib
 

	
 
  # Set-up some test data.
 
  users = ["alice", "bob", "eve", "dave"]
 

	
 
  # Set-up a list of foreground colours (taken from Sigil).
 
  foreground = [ "rgb(45,79,255)",
 
                 "rgb(254,180,44)",
 
                 "rgb(226,121,234)",
 
                 "rgb(30,179,253)",
 
                 "rgb(232,77,65)",
 
                 "rgb(49,203,115)",
 
                 "rgb(141,69,170)" ] 
 

	
 
  # Set-up a background colour (taken from Sigil).
 
  background = "rgb(224,224,224)"
 

	
 
  # Set-up the padding (top, bottom, left, right) in pixels.
 
  padding = (20, 20, 20, 20)
 

	
 
  # Instantiate a generator that will create 5x5 block identicons using SHA1
 
  # digest.
 
  generator = pydenticon.Generator(5, 5, foreground=foreground,
 
                                   background=background)
 

	
 
  for user in users:
 
    identicon = generator.generate(user, 200, 200, padding=padding,
 
                                   output_format="png")
 

	
 
    filename = user + ".png"
 
    with open(filename, "w") as f:
 
        f.write(identicon)
 

	
0 comments (0 inline, 0 general)