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') ```