Dynamic images with Python, Flask and Cairo

This is one of a series of posts about various aspects of my DIY weather station project YAWS. An overview of the project and links to all related posts are here

Like many before me I have recently been working on a project to build an automated weather station using Arduino and Raspberry Pi hardware. Part of the project is to store the data in MySQL and present it in various ways on a web site. This has been and continues to be a great project, giving me opportunities to learn all sorts of things.

One challenge was how to generate dynamic png images from the data and server them up on a website without saving the file to disk along the way. In particular I wanted to do this using Python 2, the Cairo drawing library and Flask, all running on a Raspberry Pi visible on the web thanks to magic that is dynamic DNS.

Sorry, the Pi must be offline

Google and Stack Overflow helped a bit but I didn't find anything that succinctly pulled together all of the elements I needed. Fortunately all it took was a bit of exploring and experimentation to get to a simple solution. The key was that the Cairo surface write_to_png() method can write to a "file like object", such as a StringIO object. The steps are:

  1. Draw the image using a normal Cairo image surface.
  2. Use the surface write_to_png() method to write the image data to a StringIO object.
  3. Use Flask's make_response() method to turn the data into an HTTP response.
  4. Set the response type to "image/png" 
  5. Return the response.

The code looks something like this, called in response to the appropriate route in the Flask application:

    srf = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
    ctx = cairo.Context(srf)
    drawSomethingCool(ctx)
    flo = StringIO()
    srf.write_to_png(flo)
    resp = make_response(flo.getvalue())
    resp.content_type = "image/png"
    return resp

Assuming my gallant little Raspberry Pi is up and running, this post should include a couple of dynamically generated images based on current weather data from Orielton.

Sorry, the Pi must be offline

I hope this is useful. I will post about some other aspects of this project soon.