Grayvatar

http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=196&d=identicon&r=PG

The week before last I got to spend some time working on a prototype as part of a sekrit project at work. In order to give the prototype the air of not-quite-done, I did the whole thing in black and white. This worked great up until I added in Identicons to identify users, which added a bunch of brightly colored triangles and ruined the whole black-and-white aesthetic. I eventually ended up pulling a bunch of Identicons from Gravatar with wget and desaturated them with ImageMagik. It worked, but it was kind of gross.

Teh Codez

It occurred to me that it should be pretty straightforward to build a web app that implemented the Gravatar API and desaturate real Gravatars, so I took a crack at it:

import StringIO
import urllib, urllib2

import cherrypy
import Image

from paver.easy import path

URL = "http://www.gravatar.com/avatar/%s?%s"

class Grayvatar(object):
    @cherrypy.expose
    def default(self, *bits, **kwargs):
        if bits:
            cherrypy.response.headers["Content-Type"] = "image/png"
            url = URL % (bits[0], urllib.urlencode(kwargs))
            img = get_img(url).convert("L")
            return img_as_str(img)

        pwd = path(__file__).abspath().parent
        return (pwd / "how_it_works.html").text()

def get_img(url):
    sock = urllib2.urlopen(url)
    return Image.open(StringIO.StringIO(sock.read()))

def img_as_str(img):
    sock = StringIO.StringIO()
    img.save(sock, "png")
    return sock.getvalue()

if __name__ == "__main__":
    cherrypy.quickstart(Grayvatar())

I think it's awesome that, thanks to CherrPy and PIL, I could put together a web app that does all that 30 odd lines (including imports), but it's less awesome that so many of those lines are shuffling data in and out of a StringIO.

mod_wsgi

I set up an instance at: http://aaron.maenpaa.ca/grayvatar using Apache and mod_wsgi. I used a lightly adapted version of the the wsgi config from the CherryPy wiki.

import sys
sys.stdout = sys.stderr

import atexit
import threading
import cherrypy

sys.path = ['/home/aaron/code/grayvatar'] + sys.path

import grayvatar

cherrypy.config.update({
    'environment': 'embedded',
    'log.screen': True,
})

if cherrypy.__version__.startswith('3.0') and cherrypy.engine.state == 0:
    cherrypy.engine.start(blocking=False)
    atexit.register(cherrypy.engine.stop)

application = cherrypy.Application(grayvatar.Grayvatar(), script_name=None)

The most important change was adding 'log.screen': True to the config. This sends errors to stderr, which eventually makes it's way to /var/log/apache2/error.log which makes it possible to figure out what's going on when things don't go right.

How It Works

Just like Gravatar. The base URL is http://aaron.maenpaa.ca/grayvatar/. You calculate the MD5 hash of your email address, append it to the base URL and then add any query parameters that Gravatar supports. That gets you a URL like this: http://aaron.maenpaa.ca/grayvatar/634329486bd326c395c8b3539c035139?s=256&d=identicon&r=PG

It fetchs the appropriate Gravatar, desaturates and returns it. Keep in mind, this is a toy: It doesn't actually respect the cache headers Gravatar sends and worse, it doesn't even pass them along to the client.