October 9, 2009 will

Turning website favicons in to 3D

The feedback I recieved from Reddit about locidesktop.com (my hobby project) was encouraging.

If you would like to join the beta program for locidesktop, please leave a comment below…

One of the comments pointed out that although there is a large choice of icons available, there isn't always a clear recognizable image for each site, and it would be nice if locidesktop would use ‘favicons’. I had considered using favicons previously, but rejected the idea because they are just 16x16 pixels in size, and I wanted to use large images for icons.

I didn't want blurry scaled icons either, and I may have abandoned the idea if a Reddit user hadn't pointed me at this which recommended embracing the pixelated look of favicons for use at desktop icons. I figured I could take this idea a step further and render 3D images from any given 16x16 image, using a combination of Python, Mako templates and Povray – the same combination of technologies I used for my (now defunct) 3D pie chart project.

3D favicons

Three dimensional favicons!

I knocked up the scripts to pass the time on a long train journey recently. The results were encouraging! The image insert shows a few of the icons I generate with the first pass at this. Some turn out better than others, but they would all work as recognizable icons.

The Code

The code is not too complex, and consists of a single Python script that uses Python Image Library to read an image and scan each pixel. The results of the image scanning process are then passed to a Mako template, which produces a Povray scene file.

#!/usr/bin/env python

import Image
from math import *

class Col(object):

    def __init__(self, r, g, b, a=1.0):
        self.r = r
        self.g = g
        self.b = b
        self.a = a

class ImageScan(object):

    def __init__(self, image):

        self.image = image

    def scan(self):

        w, h = self.image.size
        step_w = 1.0 / w
        step_h = 1.0 / h
        self.pixel_x_size = step_w
        self.pixel_y_size = step_h

        im = self.image
        getpixel = im.getpixel

        pixels = []

        for y in range(h):

            row = []
            for x in range(w):

                c = getpixel( (x, h-1-y) )

                # Skip transparent, and almost transparent pixels
                if c[-1] > 32:
                    c = Col( *(i/255.0 for i in c) )
                    ix = x * step_w
                    iy = y * step_h
                    row.append( (ix, iy, c) )


        self.pixels = pixels

    def __iter__(self):
        return iter(self.pixels)

if __name__ == "__main__":

    import sys
    filename = sys.argv[1]

    image = Image.open(filename)
    image = image.convert('RGBA')

    ims = ImageScan(image)

    td = {}
    td['image'] = ims

    from mako.template import Template

    output_filename = filename.split('.')[0] + '_favicon.png'

    mytemplate = Template(filename='favicon.pov')
    open('out.pov', 'w').write(mytemplate.render(**td))

    import os
    os.system('povray out.pov -O%s +A0.1 -w128 -h128 +UA +R8' % output_filename)
    print "Output", output_filename

Povray scene files are kind of like a half-way point between data and a programming language; there are variables and expressions and some control structures, but when you combine them with a templating system, like Mako, it creates a remarkably flexible system where 3D images can be programatically generated.

The favicon template loops through the scanned image data passed to it by the script. This data contains the location and colour of each visible pixel, which the template uses to generate a coloured cubiod in the scene, per pixel. These cuboids combine to create a three dimensional version of the original icon image.

#version 3.6;

#declare pixel_finish =
  finish {
    ambient rgb <1.0,1.0,1.0>*.1
    brilliance  2.54
    crand       0.000
    diffuse     0.813
    metallic    1.5000
    phong       2.093
    phong_size  1.000
    specular    0.425
    roughness   0.001

#declare Camera0 =
camera {
  location <2.173,1.520+1,5.820>/2
  up y
  right -1.000*x
  angle 22.000
  sky <-0.083,0.971,-0.223>
  look_at < 0, 0, 0 >

light_source {
  < 2, 4, 10 >, color rgb <1, 1, 1>*.9

light_source {
    <12, 1, -1.5>, color rgb <.3, .3, .6>

light_source {
    <0, 10, -1.5>, color rgb <.6, .3, .3>

union {
% for row in image:
% for x, y, c in row:
box {
    <${x-.5}, ${y-.5}, -0.5> , <${x-.5 + image.pixel_x_size}, ${y-.5 + image.pixel_y_size}, 0.5>
    texture {
        pigment { color rgbft <${c.r}, ${c.g}, ${c.b}, 0, 0.0> }
        finish { pixel_finish }
    scale <1, 1, 0.4>
% endfor
% endfor

camera{ Camera0 }

Because it is such a simple scene, Povray takes just a second to render a 128x128 image. So quick that it may be practical to render these image on-the-fly when required. Or I may just render a few thousand favicons for popular sites and upload them.

Hopefully you will see these 3D favicons on locidesktop.com in the coming weeks…

Use Markdown for formatting
*Italic* **Bold** `inline code` Links to [Google](http://www.google.com) > This is a quote > ```python import this ```
your comment will be previewed here
I like it. I don't understand it, but I sure do like it.
Jos Hirth
You really should handle alpha correctly. The resulting translucent boxes should look pretty nice. Well, at least they will look better than these incorrect opaque ones.
Will McGugan
Jos, I actually tried mapping the image alpha to transparency, but it didn't look good, because you could see through the translucent ‘pixels’ and see edges and other pixels, which made the resulting image very noisy.
It's a nice hack, but, sorry for being honest, looks like crap. :D
It may take me some time to understand it …..however it looks cool !
make semi-transparent pixels fully opaque, but not as deep (i.e. 50% opaque pixel would end at half depth of the icon).