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