A simple cairo draw queue using closures

Wed 12 May 2010
By stu

Often it's useful to be able to store up drawing commands so you can use them later somewhere else (or even just pass them to another thread).

This is a simple drawing model, implemented in cairo, hopefully somebody will find it useful.

Queue

``` {: .brush:python} class DrawQueue: ''' A list of draw commands, stored as callables that, are passed a set of parameters to draw on from the canvas implementation. ''' def init(self, render_callables = None): self.render_callables = render_callables or deque()

def append(self, render_callable):
    '''
    Add a render callable to the queue
    '''
    self.render_callables.append(render_callable)

def render(self, cairo_ctx):
    '''
    Call all the render callables with cairo_ctx
    '''
    for render_callable in self.render_callables:
        render_callable(cairo_ctx)

```

The queue just accepts callables (any old function), and calls them when you call render, passing them a cairo context you pass in.

To get useful functions you can call closure functions like these:

``` {: .brush:python} def paint_closure(): def paint(ctx): ctx.paint() return paint

def fill_closure(): def fill(ctx): ctx.fill() return fill

def set_source_rgb_closure(r, g, b): def set_source_rgb(ctx): ctx.set_source_rgb(r, g, b) return set_source_rgb

def moveto_closure(x, y): def moveto(ctx): ctx.move_to(x, y) return moveto

def rectangle_closure(x, y, w, h): def rectangle(ctx): ctx.rectangle(x, y, w, h) return rectangle ```

Adding commands to the queue is simple:

{: .brush:python} dq = DrawQueue() dq.append(set_source_rgb_closure(1, 1, 1)) dq.append(paint_closure()) dq.append(moveto_closure(10, 0)) dq.append(rectangle_closure(0, 0, 20, 20)) dq.append(set_source_rgb_closure(0, 0, 0)) dq.append(fill_closure())

This is the same drawing model I'm using in my branch of shoebot, I'm hoping to expand it to be multithreaded; while a foreground thread adds commands a background thread is executing them.

Here it is all put together in a simple example to draw a black rectangle

``` {: .brush:python} from collections import deque import cairo

class DrawQueue: ''' A list of draw commands, stored as callables that, are passed a set of parameters to draw on from the canvas implementation. ''' def init(self, render_callables = None): self.render_callables = render_callables or deque()

def append(self, render_callable):
    '''
    Add a render callable to the queue
    '''
    self.render_callables.append(render_callable)

def render(self, cairo_ctx):
    '''
    Call all the render callables with cairo_ctx
    '''
    for render_callable in self.render_callables:
        render_callable(cairo_ctx)

drawing closures

def paint_closure(): def paint(ctx): ctx.paint() return paint

def fill_closure(): def fill(ctx): ctx.fill() return fill

def set_source_rgb_closure(r, g, b): def set_source_rgb(ctx): ctx.set_source_rgb(r, g, b) return set_source_rgb

def moveto_closure(x, y): def moveto(ctx): ctx.move_to(x, y) return moveto

def rectangle_closure(x, y, w, h): def rectangle(ctx): ctx.rectangle(x, y, w, h) return rectangle

/drawing closures

dq = DrawQueue()

Add some commands to the drawing queue

dq.append(set_source_rgb_closure(1, 1, 1)) dq.append(paint_closure()) dq.append(moveto_closure(10, 0)) dq.append(rectangle_closure(0, 0, 20, 20)) dq.append(set_source_rgb_closure(0, 0, 0)) dq.append(fill_closure())

Create a surface and context

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 200) ctx = cairo.Context(surface)

run defered rendering

dq.render(ctx)

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