Cairo: Surface for recording commands and playback

Thu 13 May 2010
By stu

An update on my latest cairo adventures...

When cairo 1.10 comes out we'll get a RecordingSurface so you can record commands and play them back to another surface, but how to do something similar now ?

Skip to the end if you just want the code, explanation of how I got there ahead:

My first advice was to try using PDFSurface, and set the file object to None.


Record a red rectangle onto a surface.

Create another surface and draw a background on it

Draw the first surface onto this surface

from cairo import PDFSurface, ImageSurface, FORMAT_ARGB32, Context

recording = PDFSurface(None, 200, 200) target = ImageSurface(FORMAT_ARGB32, 200, 200)

Record a red rectangle

cr = Context(recording) cr.set_source_rgb(1.0, 0.0, 1.0) cr.rectangle(20, 20, 10, 10)

cr.fill_preserve() cr.set_source_rgb(0.0, 0.0, 1.0) cr.stroke()

target_context = Context(target)

Draw background

target_context.set_source_rgb(1.0, 1.0, 0) target_context.paint()

Replay recording to target

target_context.set_source_surface(recording) target_context.paint()

target.write_to_png('output.png') ```

That seems to work, except when I tried in Windows, when it complained that the file object was wrong.

OK, we can work round that:

{: .brush:python} def RecordingSurface(w, h): if == 'nt': fobj = 'nul' else: fobj = None return PDFSurface(fobj, w, h)

This seems to be working, but my animation seemed slow... time for some benchmarking; I rendered 1000 frames and got a rough wall clock time:

Hmm... perhaps SVGSurface will be quicker:

This is much better, 20 seconds difference just by changing what kind of surface is returned!

Animation not smooth though

The animation still seemed jerky it occured to me that when the surfaces are disposed they will attempt to out their content to the file object !

Luckily, get_similar_surface() comes to the rescue; it returns a surface not associated with a file object. Using this the original surface can be kept around forever, and never output.

Wallclock time:
50 seconds!

And here it is:

``` {: .brush:python} _svg_surface = None def RecordingSurface(*size): ''' We don't have RecordingSurfaces until cairo 1.10, so this kludge is used

SVGSurfaces are created, but to stop them ever attempting to output, they
are kept in a dict.

When a surface is needed, create_similar is called to get a Surface from
the SVGSurface of the same size
global _svg_surface
if == 'nt':
    fobj = 'nul'
    fobj = None
if _svg_surface is None:
    _svg_surface = SVGSurface(fobj, 0, 0)
return _svg_surface.create_similar(cairo.CONTENT_COLOR_ALPHA, *size)


This is really useful, you can record commands and play them back to other surfaces.