Jacob Kaplan-Moss

Improved text image view

I wrote this post in 2006, more than 18 years ago. It may be very out of date, partially or totally incorrect. I may even no longer agree with this, or might approach things differently if I wrote this post today. I rarely edit posts after writing them, but if I have there'll be a note at the bottom about what I changed and why. If something in this post is actively harmful or dangerous please get in touch and I'll fix it.

I just found this in my django-ego-feed: 23 excuses: Simple Django View for Dynamic Text Replacement

I’ve been using something similar to generate the titles for the site (look at the title above for an example), so I’m pretty familiar with the technique.

Andrew’s code over there is pretty good, but I’ve got a few improvements he and you might be interested in:

  • The business to writing to a temp file is ugly and will break as soon as you get two simultaneous requests. The right way to do this is by remembering that an HTTPResponse exposes a file-like interface, so you can write the image directly to it:

    response = HTTPResponse(mimetype="image/png")
    im.save(response, "PNG")
    
  • You’ll save on bandwidth and processor time if you add in E-tag support. This’ll let browsers cache your image locally and save you from generating and sending it each time.

    This is actually pretty easy to do. At the start of the view, calculate the E-tag and check it against the If-None-Match header:

    etag = md5.new(header + fontalias).hexdigest()
    if request.META.get("HTTP_IF_NONE_MATCH") == etag:
        return HttpResponseNotModified()
    

    Then, just before you return the response, insert the E-tag header:

    response["e-tag"] = etag
    
  • As long as we’re being pedantic about HTTP, let’s return a 403 Forbidden for a bad font file (instead of a 200 OK)

Here’s the complete view_header() code with my modifications:

import md5
from django.conf import settings
from django.http import (HttpResponse,
                         HttpResponseNotModified,
                         HTTPResponseForbidden)
import Image, ImageFont, ImageDraw

def view_header(request, fontalias):
    try:
        fontfile = settings.DYNAMIC_FONT_ALIASES[fontalias]
    except:
        return HttpResponseForbidden("font alias not supported")

    if request.GET.has_key('text'):
        header = request.GET['text']
    else:
        header = 'Hello world'

    etag = md5.new(header + fontalias).hexdigest()
    if request.META.get("HTTP_IF_NONE_MATCH") == etag:
        return HttpResponseNotModified()

    imf = ImageFont.truetype(fontfile, 20)
    size = imf.getsize(header)
    im = Image.new("RGB", size)
    draw = ImageDraw.Draw(im)
    draw.text((0, 0), header, font=imf)

    response = HTTPResponse(mimetype="image/png")
    im.save(response, "PNG")
    response["e-tag"] = etag
    return response

Enjoy!