From 5df4ce2fcaa9aeb52379105aaa4fdedd0219475e 2013-11-27 23:15:12 From: Branko Majic Date: 2013-11-27 23:15:12 Subject: [PATCH] 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. --- diff --git a/docs/about.rst b/docs/about.rst new file mode 100644 index 0000000000000000000000000000000000000000..0107221c4fa86dd27c9abbd11402fda7d0dfe87c --- /dev/null +++ b/docs/about.rst @@ -0,0 +1,53 @@ +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). + diff --git a/docs/algorithm.rst b/docs/algorithm.rst new file mode 100644 index 0000000000000000000000000000000000000000..cc65fc46f00d5cbd069f5153a0821944fb0240b9 --- /dev/null +++ b/docs/algorithm.rst @@ -0,0 +1,154 @@ +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. + diff --git a/docs/apireference.rst b/docs/apireference.rst new file mode 100644 index 0000000000000000000000000000000000000000..7e62265bad4948c7b64801adea5ce2c083b59449 --- /dev/null +++ b/docs/apireference.rst @@ -0,0 +1,6 @@ +API Reference +============= + +.. automodule:: pydenticon + :members: + diff --git a/docs/conf.py b/docs/conf.py index 7567b0bc2c77a10ac498182513479ac588aca51b..665a5fdf49052f2383d4df7a7212615dcd41c2ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 ----------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index e4f9ba64fcc0522359a4f29169f711837b41c2e4..539c3e3bb2e7b766f683866605f6079243cd7cbc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,17 +1,36 @@ -.. 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 ================== diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000000000000000000000000000000000000..801cce278aa37052c1e92c7de766f32ab8f06caf --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,34 @@ +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 +`_, 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. + diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000000000000000000000000000000000000..16b528a0c231d2f34e3396a432c2b1cfffbf6ae1 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,152 @@ +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) +