Make inline images to improve performance (with django template tags)

Despite we have now big connections and broadband lines, it’s still too long. So, as developpers, we have a role to play in web-performances.

Today, I’ll show you a snippet I have written a couple of weeks ago, to encode images in base64 on-the-fly.

Click here to go directly to the django part

Why encode images ?

You can b64-encode images to reduce your numbers of HTTP requests. A request has an average response time of 200ms and most of the browsers let you open 2 HTTP connections at the same time. Great famous tools/patterns, like YSLOW and Page speed recommend to reduce the number of HTTP request on a page.

There are various school on how to handle the image case, I know 2 :

Using css-sprites:

It’s a technique which consist to combine all you site images in one image, and then shift the visible area to the required coordonates of the big image.

How I do that ?

You can make your own big sprite by collecting all your site images and then pasting them on a big png or you can use sprites generators (Spritegen,, google).

After that you use the background-position attribute on a tag instead of writting a <img /> tag.  Which gives you pretty much this:

.my-bg-class  {background-image:url('../images/my_sprite.png')}
.info-icon    {background-position:0px -50px}
.warning-icon {background-position:0px -60px}
.error-icon   {background-position:0px -70px}

<div class="my-bg-class info-icon"></div>
<div class="my-bg-class warning-icon"></div>
<div class="my-bg-class error-icon"></div>

So, for displaying 3 icons, you’ll make only one HTTP request and then css handle the rest ! Just for 3 icons you can gain maybe 500ms !

Is that good to use css-sprites ?

Most of time yes, but there are some disadvantages of this technique. When number of images grows the sprite grows at the same time and even if sprite let you make only one HTTP request you’ll to have a big one !


Using image base64 encoded

How i do that ?

There are 2 ways to do that (at least I know):

inlining the src in the <img /> tag (for reading purposes I return to the line but your b64 code has to be in one line without “\n”) :

<img src="data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcp
BsreNJrqEiTySQcDpk9iOea7GxtI7Gzjt40VQo52jgnua4HRp1Pi9L8RBYo1TTEQHJUbQc/99GvRqvLqFOClKMm/X9CcVdNRasf/2Q==" alt=":'(" />

Or in your css:

.chocobo { background :url(data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBw

Special attention on web browser support :

Base64 encoded images (and data URIs more generally)  are supported in:

  • Firefox 2+
  • Safari – all versions
  • Google Chrome – all versions
  • Opera 7.2+
  • Internet Explorer 8+

For IE < 8, you need to interest yourself in the MHTML alternative.

Is that good to b64-encode images ?

It depends on your needs, but it can be usefull if you haven’t to support old browsers and if you have small images. Base64 is pretty verbose and big (~30% bigger) but you haven’t to make a HTTP request and if you gzip your css file it is pretty much the same than a regular image.



Using image base64 encoded in django

As I said previously, I have written a django template tag which do the work of auto-inlining your images stored in your media directory and make a cached version of the image on the server.

You need to have:

  • PIL installed
  • A writable dir for cached version (in the code I used /tmp)



# -*- coding: utf-8 -*-
    django templates tags for images
    @author: R. BECK
    @version: 0.1

#from __future__ import with_statement #Python 2.5

import base64
import os
import Image
from re import compile as compile_re
    from cStringIO import StringIO
except ImportError:
    from StringIO  import StringIO

from django.conf     import settings
from django.template import TemplateSyntaxError, Node, Library, Template

register = Library()

MEDIA_ROOT     = settings.MEDIA_ROOT
CACHE_DIR      = "/tmp"

templatevar_re = compile_re("\{\{(.+)\}\}")
media_re       = compile_re("\{\{MEDIA_ROOT|MEDIA_URL\}\}")

class EmbeddedImgNode(Node):
    """Image node parser for rendering html inline base64 image"""

    def __init__(self, attributes):
        self.attrs = {}
        attrs = self.attrs
        for attr_value in attributes:
                attr, value = attr_value.split('=', 1)
            except ValueError, val_err:
                raise TemplateSyntaxError(u"Syntax Error :", val_err)
            attrs[attr] = value

        src = attrs.get('src')
        if not src:
            raise TemplateSyntaxError(u"You have to specify a non-empty src \

    def _encode_img(self, file_path):
        """Returns image base64 string representation and makes a cache file"""
        filename   = file_path.rpartition(os.sep)[2]
        need_cache = True
        content    = ""

        cache_file = "%s_cache" % os.path.join(CACHE_DIR, filename)

            with open(cache_file, 'r') as cached_file:
                content =
            need_cache = False
        except IOError:

        if need_cache:
                image = open(file_path, 'r')
                out   = StringIO()
                base64.encode(image, out)
                content = out.getvalue().replace('\n', '')
            except IOError:
                    with open(cache_file, 'w+') as cached_file:
                except IOError:
        return content

    def _render_img(self):
        """Prepare image attributes"""
        attrs = self.attrs
        attrs_get = attrs.get
        src    = attrs_get('src')
        height = attrs_get('height')
        width  = attrs_get('width')

            src = src.replace('{{MEDIA_ROOT}}', '') \
                     .replace('{{MEDIA_URL}}', '')

        src = src.replace('"', '')
        src = os.path.join(MEDIA_ROOT, src)

            img     =
            _width, _height    = img.size
            _format = 'image/%s' % img.format

            if not height:
                attrs['height'] = '%spx' % _height

            if not width:
                attrs['width'] = '%spx' % _width

            b64encoded = self._encode_img(img.filename)

            attrs['src'] = "data:%s;base64,%s" % (_format, b64encoded)
        except IOError:
            attrs['src'] = ""

    def render(self, context):

        attrs  = self.attrs
        search =

        for k, v in attrs.iteritems():
            if search(v):
                attrs[k] = Template(v).render(context)

        return """<img %s/>""" % ' '.join(['%s=%s' % (k, v if v else '""')
                                             for k, v in attrs.iteritems()])

def do_embedded_img(parser, token):
    return EmbeddedImgNode(token.split_contents()[1:])

How to use it:

  • Put the in templatetags dir of one of your installed app
  • In your template load it

You can use context variables in the template tag.

Your template.html:

{% load image_tags %}
{% embedded_img src="/media/images/002.gif" height="200px" width="200px" alt="{{my_var}}" %}
{% embedded_img src="{{MEDIA_URL}}upload/images/001.jpg" height="200px" width="200px" alt="lala" %}
{% embedded_img src="upload/images/logo_corp_264.png" alt="corp" %}
{% embedded_img src="{{MEDIA_URL}}upload/images/001.jpg" width="200px" alt="lala" %}
{% embedded_img src="{{MEDIA_URL}}upload/images/001.jpg" height="200px" width="200px" alt="lala" %}
{% embedded_img src="{{MEDIA_URL}}upload/images/001.jpg" %}

That’s it ! The template tag generates all for you ! You can inline all images you want in your HTML, even I recommend you to inline only small images.

If you find any bug in my code, or if you think there are some optimisations possible don’t hesitate to comment it !

Those snippets are now available at:

This entry was posted in django, python, web. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s