2D-ENGINE(CARIO)--------REAL STUFF FOR REAL HACKERS
Posted: Wed Jul 02, 2008 7:15 am











2D ENGINE(CAIRO)













for real hackers who want to learn real stuff
Cairo is a 2D graphics library with support for multiple output devices. Currently supported
output targets include the X Window System, Quartz, Win32, image buffers, PostScript, PDF,
and SVG file output. Experimental backends include OpenGL (through glitz), XCB, BeOS, OS/2,
and DirectFB.
Cairo is designed to produce consistent output on all output media while taking advantage of
display hardware acceleration when available (eg. through the X Render Extension).
The cairo API provides operations similar to the drawing operators of PostScript and PDF.
Operations in cairo including stroking and filling cubic Bézier splines, transforming and
compositing translucent images, and antialiased text rendering. All drawing operations can
be transformed by any affine transformation (scale, rotation, shear, etc.)
Cairo is implemented as a library written in the C programming language, but bindings are
available for several different programming languages.
Cairo is free software and is available to be redistributed and/or modified under the terms
of either the GNU Lesser General Public License (LGPL) version 2.1 or the Mozilla Public
License (MPL) version 1.1 at your option.
Scrumptious recipes to spice up your applications
Rounded rectangles
Using Win32 Quickstart
Rendering SVG using librsvg in Python
Converting cairo code from C to Python or Haskell and back
Load jpg/png/gif/bmp/etc into CairoContext with help from gdk-pixbuf in Pycairo/Pygtk
Basic matrix transformation reminder
Python: Converting PIL images to Cairo surfaces and back
Loading fonts using FreeType for cairo use in Python
A description of compositing operators in Cairo
How to:A quick framework for testing cairo from Python (And demos masking)
How to:Open a simple SVG file without Pyrsvg
How to:Using path data to create a Hit Area
How to:Using PyCairo and Pyrsvg to handle SVG graphics
How to:Do timeout animation (Also shows rotation around a point)
How to:Render PDF and PostScript files with cairo
How to use librsvg from Python
It finally took me an entire installation of a new O/S and "locate .py | grep rsvg"(*) on my
drive to find what I could not find via google : how to open an SVG file and draw it from
PyCairo.
I had to upgrade my O/S (to Gnu/Linux Kubuntu 7.10) in order to get the latest
Python-gnome2-* packages for, buried in there, is a Python wrapper for librsvg. I don't know
why it's not a stand-alone wrapper like PyCairo, but that's the way it is.
The first demo is directly from the example I found on my drive.
(*) You can find this demo in /usr/share/doc/python-gnome2-desktop/examples/rsvg on *buntu
Gutsy 7.10
#!/usr/bin/env python
import sys
import cairo
import rsvg
import gtk
BORDER_WIDTH = 10
def delete_cb(win, event):
gtk.main_quit()
def expose_cairo(win, event, svg):
x, y, w, h = win.allocation
cr = win.window.cairo_create()
cr.set_source_color(win.style.fg[win.state])
cr.rectangle(BORDER_WIDTH, BORDER_WIDTH,
w - 2*BORDER_WIDTH, h - 2*BORDER_WIDTH)
cr.set_line_width(5.0)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
cr.stroke()
if svg != None:
matrix = cairo.Matrix(3,0,0,3,0, 0)
#cairo.Matrix.rotate( matrix, prop.rot )
cr.transform (matrix)
svg.render_cairo(cr)
return True
def main():
win = gtk.Window ()
win.connect("delete-event", delete_cb)
svg = None
if (len (sys.argv) > 1):
svg = rsvg.Handle(file=sys.argv[1])
else:
raise SystemExit("need svg file")
win.connect("expose-event", expose_cairo, svg)
print svg.props.width, svg.props.height, svg.props.em, svg.props.ex
win.show_all()
win.connect("destroy", lambda w: gtk.main_quit())
gtk.main()
if __name__ == '__main__':
main()
Writing an SVG file
I thought I'd see how writing out an SVG file from Cairo works, this is a short example.
import cairo
import rsvg
import math
fo = file('test.svg', 'w')
WIDTH, HEIGHT = 256, 256
## Prepare a destination surface -> out to an SVG file!
surface = cairo.SVGSurface (fo, WIDTH, HEIGHT)
## draw something - this taken from the web.
ctx = cairo.Context (surface)
ctx.scale (WIDTH/1.0, HEIGHT/1.0) # Normalizing the canvas
pat = cairo.LinearGradient (0.0, 0.0, 0.0, 1.0)
pat.add_color_stop_rgba (1, 0.7, 0, 0, 0.5) # First stop, 50% opacity
pat.add_color_stop_rgba (0, 0.9, 0.7, 0.2, 1) # Last stop, 100% opacity
ctx.rectangle (0, 0, 1, 1) # Rectangle(x0, y0, x1, y1)
ctx.set_source (pat)
ctx.fill ()
ctx.translate (0.1, 0.1) # Changing the current transformation matrix
ctx.move_to (0, 0)
ctx.arc (0.2, 0.1, 0.1, -math.pi/2, 0) # Arc(cx, cy, radius, start_angle, stop_angle)
ctx.line_to (0.5, 0.1) # Line to (x,y)
ctx.curve_to (0.5, 0.2, 0.5, 0.4, 0.2, 0.

ctx.close_path ()
ctx.set_source_rgb (0.3, 0.2, 0.5) # Solid color
ctx.set_line_width (0.02)
ctx.stroke ()
## Do the deed.
surface.finish()
You can use 'svgdisplay' (if you have the binary), Inkscape, Firefox or the first demo app
to view test.svg
A feedback loop
What happens when you feed the snake it's tail?
import cairo
import rsvg
fo = file('test.svg', 'w')
WIDTH, HEIGHT = 256, 256
surface = cairo.SVGSurface (fo, WIDTH, HEIGHT)
ctx = cairo.Context (surface)
svg = rsvg.Handle(file="test2.svg")
svg.render_cairo(ctx)
surface.finish()
Well, it works! test.svg comes in and comes out as test2.svg. I don't think it proves
anything, but it's fun.
Python
The following is a minimal windows application that uses cairo. It has double buffering and
uses the PyWin32 library.
from win32api import *
try:
from winxpgui import *
except ImportError:
from win32gui import *
import win32con
import sys, os
import cairo
def WebColour(sCol):
#print sCol
ic = [int(sCol[i:i+2], 16)/255.0 for i in range(1, 7, 2)]
#print ic
return ic
def roundedRectangle(cr,x,y,w,h,radius_x=5,radius_y=5):
#from mono moonlight aka mono silverlight
#test limits (without using multiplications)
# http://graphics.stanford.edu/courses/cs ... al/q1.html
ARC_TO_BEZIER = 0.55228475
if radius_x > w - radius_x:
radius_x = w / 2
if radius_y > h - radius_y:
radius_y = h / 2
#approximate (quite close) the arc using a bezier curve
c1 = ARC_TO_BEZIER * radius_x
c2 = ARC_TO_BEZIER * radius_y
cr.new_path();
cr.move_to ( x + radius_x, y)
cr.rel_line_to ( w - 2 * radius_x, 0.0)
cr.rel_curve_to ( c1, 0.0, radius_x, c2, radius_x, radius_y)
cr.rel_line_to ( 0, h - 2 * radius_y)
cr.rel_curve_to ( 0.0, c2, c1 - radius_x, radius_y, -radius_x, radius_y)
cr.rel_line_to ( -w + 2 * radius_x, 0)
cr.rel_curve_to ( -c1, 0, -radius_x, -c2, -radius_x, -radius_y)
cr.rel_line_to (0, -h + 2 * radius_y)
cr.rel_curve_to (0.0, -c2, radius_x - c1, -radius_y, radius_x, -radius_y)
cr.close_path ()
class MainWindow(object):
def __init__(self):
message_map = {
win32con.WM_DESTROY: self.OnDestroy,
win32con.WM_PAINT: self.OnPaint,
win32con.WM_SETCURSOR: self.OnCursor,
win32con.WM_ERASEBKGND: self.OnBackgroundErase,
win32con.WM_LBUTTONDOWN: self.OnClick,
win32con.WM_KEYUP: self.OnKey,
#win32con.WM_COMMAND: self.OnCommand,
#win32con.WM_USER+20 : self.OnTaskbarNotify,
# owner-draw related handlers.
#win32con.WM_MEASUREITEM: self.OnMeasureItem,
#win32con.WM_DRAWITEM: self.OnDrawItem,
}
# Register the Window class.
wc = WNDCLASS()
hinst = wc.hInstance = GetModuleHandle(None)
self.hinst = hinst
wc.lpszClassName = "CairoWindow"
wc.lpfnWndProc = message_map # could also specify a wndproc.
classAtom = RegisterClass(wc)
# Create the Window.
style = win32con.WS_THICKFRAME | win32con.WS_MAXIMIZEBOX | win32con.WS_MINIMIZEBOX |
win32con.WS_SYSMENU | win32con.WS_VISIBLE
self.hwnd = CreateWindow( classAtom, "Cairo is the greatest thing!", style, \
0, 0, 550, 350, \
0, 0, hinst, None)
UpdateWindow(self.hwnd)
def OnKey(self, hWnd, msg, wParam, lparam):
if wParam == 38: #up
print "up"
elif wParam == 40: #down
print "down"
def OnClick(self, hWnd, msg, wparam, lparam):
x = LOWORD(lparam)
y = HIWORD(lparam)
def Render(self):
try:
InvalidateRect(self.hwnd, None, True)
return True
except:
print "That didn't work"
return False
def OnPaint(self, hWnd, msg, wparam, lparam):
hdc, ps = BeginPaint(hWnd)
rc = GetClientRect(hWnd)
left, top, right, bottom = rc
width = right - left
height = bottom - top
x = left
y = top
_buffer = CreateCompatibleDC(hdc)
#Double Buffer Stage 1
hBitmap = CreateCompatibleBitmap(hdc,width,height)
hOldBitmap = SelectObject(_buffer, hBitmap )
surf = cairo.Win32Surface(_buffer)
ctx = cairo.Context(surf)
ctx.set_source_rgb(1,1,1)
ctx.paint()
roundedRectangle(ctx, 50, 50, 250, 250, 10, 10)
clr = WebColour("#F0FEE9")
ctx.set_source_rgb(clr[0],clr[1],clr[2])
ctx.fill_preserve()
clr = WebColour("#B3FF8D")
ctx.set_source_rgb(clr[0],clr[1],clr[2])
ctx.stroke()
ctx.set_source_rgb(0,0,0)
ctx.select_font_face("Arial",cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(10)
txt = "Cairo is the greatest thing!"
ctx.move_to(5,10)
ctx.show_text(txt)
surf.finish()
BitBlt(hdc,0, 0, width, height,
_buffer, x, y, win32con.SRCCOPY)
SelectObject( _buffer, hOldBitmap )
DeleteObject( hBitmap )
DeleteDC( _buffer )
EndPaint(hWnd,ps)
def OnCursor(self, hwnd, msg, wparam, lparam):
#IDC_HAND = 32649
#IDC_ARROW
cur_normal = LoadCursor(0, win32con.IDC_ARROW)
SetCursor(cur_normal)
def OnBackgroundErase(self, hwnd, msg, wparam, lparam):
return False
def OnDestroy(self, hwnd, msg, wparam, lparam):
#nid = (self.hwnd, 0)
#Shell_NotifyIcon(NIM_DELETE, nid)
PostQuitMessage(0) # Terminate the app.
print "Exited."
w=MainWindow()
PumpMessages()
This is some basic information about matrix transformation for people who forgot their math
course or didn't have one.
PIL and Cairographics / PyCairo
Please note that the code below was to only blur an cairo image surface and thus ignores
conversion from BGRA to RGBA to BGRA that is needed in other situations. Theoretically just
replace RGBA with BGRA in frombuffer and tostring.
im = cairo.ImageSurface.create_from_png("img11.png")
im1 = Image.frombuffer("RGBA",( im.get_width(),im.get_height()
),im.get_data(),"raw","RGBA",0,1)
im1 = im1.filter(ImageFilter.BLUR)
im1 = im1.filter(ImageFilter.BLUR)
im1 = im1.filter(ImageFilter.SMOOTH_MORE)
#imgd = im1.tostring("raw","RGBA",0,1)
imgd = im1.tostring()
a = array('B',imgd)
stride = self.width * 4
surface = cairo.ImageSurface.create_for_data (a, cairo.FORMAT_ARGB32,
self.width, self.height, stride)
roundedrectangles
Method A
From the cairo samples, modified. Gives a very nice shape but takes width and height only as
guides.
def roundedrecA(self,cr,x,y,width,height,radius=5):
#/* a custom shape, that could be wrapped in a function */
#radius = 5 #/*< and an approximate curvature radius */
x0 = x+radius/2.0 #/*< parameters like cairo_rectangle */
y0 = y+radius/2.0
rect_width = width - radius
rect_height = height - radius
cr.save()
#cr.set_line_width (0.04)
#self.snippet_normalize (cr, width, height)
x1=x0+rect_width
y1=y0+rect_height
#if (!rect_width || !rect_height)
# return
if rect_width/2<radius:
if rect_height/2<radius:
cr.move_to (x0, (y0 + y1)/2)
cr.curve_to (x0 ,y0, x0, y0, (x0 + x1)/2, y0)
cr.curve_to (x1, y0, x1, y0, x1, (y0 + y1)/2)
cr.curve_to (x1, y1, x1, y1, (x1 + x0)/2, y1)
cr.curve_to (x0, y1, x0, y1, x0, (y0 + y1)/2)
else:
cr.move_to (x0, y0 + radius)
cr.curve_to (x0 ,y0, x0, y0, (x0 + x1)/2, y0)
cr.curve_to (x1, y0, x1, y0, x1, y0 + radius)
cr.line_to (x1 , y1 - radius)
cr.curve_to (x1, y1, x1, y1, (x1 + x0)/2, y1)
cr.curve_to (x0, y1, x0, y1, x0, y1- radius)
else:
if rect_height/2<radius:
cr.move_to (x0, (y0 + y1)/2)
cr.curve_to (x0 , y0, x0 , y0, x0 + radius, y0)
cr.line_to (x1 - radius, y0)
cr.curve_to (x1, y0, x1, y0, x1, (y0 + y1)/2)
cr.curve_to (x1, y1, x1, y1, x1 - radius, y1)
cr.line_to (x0 + radius, y1)
cr.curve_to (x0, y1, x0, y1, x0, (y0 + y1)/2)
else:
cr.move_to (x0, y0 + radius)
cr.curve_to (x0 , y0, x0 , y0, x0 + radius, y0)
cr.line_to (x1 - radius, y0)
cr.curve_to (x1, y0, x1, y0, x1, y0 + radius)
cr.line_to (x1 , y1 - radius)
cr.curve_to (x1, y1, x1, y1, x1 - radius, y1)
cr.line_to (x0 + radius, y1)
cr.curve_to (x0, y1, x0, y1, x0, y1- radius)
cr.close_path ()
cr.restore()
Method B
From mono moonlight aka mono silverlight. Works very well on larger shapes.
def roundedrecMoonlight(self,cr,x,y,w,h,radius_x=5,radius_y=5):
#from mono moonlight aka mono silverlight
#test limits (without using multiplications)
# http://graphics.stanford.edu/courses/cs ... al/q1.html
ARC_TO_BEZIER = 0.55228475
if radius_x > w - radius_x:
radius_x = w / 2
if radius_y > h - radius_y:
radius_y = h / 2
#approximate (quite close) the arc using a bezier curve
c1 = ARC_TO_BEZIER * radius_x
c2 = ARC_TO_BEZIER * radius_y
cr.new_path();
cr.move_to ( x + radius_x, y)
cr.rel_line_to ( w - 2 * radius_x, 0.0)
cr.rel_curve_to ( c1, 0.0, radius_x, c2, radius_x, radius_y)
cr.rel_line_to ( 0, h - 2 * radius_y)
cr.rel_curve_to ( 0.0, c2, c1 - radius_x, radius_y, -radius_x, radius_y)
cr.rel_line_to ( -w + 2 * radius_x, 0)
cr.rel_curve_to ( -c1, 0, -radius_x, -c2, -radius_x, -radius_y)
cr.rel_line_to (0, -h + 2 * radius_y)
cr.rel_curve_to (0.0, -c2, radius_x - c1, -radius_y, radius_x, -radius_y)
cr.close_path ()
Method C
I can't remember where I got this. If you are the author please add your name. Works well on
smaller shapes.
def roundedrec(self,context,x,y,w,h,r = 10):
"Draw a rounded rectangle"
# A****BQ
# H C
# * *
# G D
# F****E
context.move_to(x+r,y) # Move to A
context.line_to(x+w-r,y) # Straight line to B
context.curve_to(x+w,y,x+w,y,x+w,y+r) # Curve to C, Control points are both at Q
context.line_to(x+w,y+h-r) # Move to D
context.curve_to(x+w,y+h,x+w,y+h,x+w-r,y+h) # Curve to E
context.line_to(x+r,y+h) # Line to F
context.curve_to(x,y+h,x,y+h,x,y+h-r) # Curve to G
context.line_to(x,y+r) # Line to H
context.curve_to(x,y,x,y,x+r,y) # Curve to A
return
Loading images
Based on the cworth recipe. Loads file in any gdk-pixbuf supported format (look into your
gdk-pixbuf.loaders or run 'gdk-pixbuf-query-loaders | grep gtk20'). Can be used as a very
primitive image viewer.
#!/usr/bin/env python
import sys
import gtk
import cairo
def expose (da, event, pixbuf):
ctx = da.window.cairo_create()
# You can put ctx.scale(..) or ctx.rotate(..) here, if you need some
ctx.set_source_pixbuf(pixbuf,0,0)
ctx.paint()
ctx.stroke()
def main():
filename = sys.argv[1]
pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
imgw=pixbuf.get_width()
imgh=pixbuf.get_height()
win = gtk.Window()
win.connect('destroy', gtk.main_quit)
win.set_default_size(imgw, imgh)
da = gtk.DrawingArea()
win.add(da)
da.connect('expose_event', expose, pixbuf)
win.show_all()
gtk.main()
if __name__ == '__main__':
if len(sys.argv) != 2:
program = sys.argv[0]
print program +':', 'usage:', program, '<filename>'
sys.exit(0)
else:
main()
matrix transform
--------------------------------------------------------------------------------
Lets take that C = math.cos(A), S = math.sin(A), T = math.tan(A)
mtrx have to be used in the command ctx.transform(mtrx)
Action Command Matrix for transform
Shift by dx,dy ctx.translate(dx,dy) mtrx = cairo.Matrix(1,0,0,1,dx,dy)
Scale by fx,fy ctx.scale(fx,fy) mtrx = cairo.Matrix(fx,0,0,fy,0,0)
Rotation to A radians ctx.rotate(A) mtrx = cairo.Matrix(C,S,-S,C,0,0)
Rotation to A radians with center in x,y ctx.translate(x,y); ctx.rotate(A);
ctx.translate(-x,-y) mtrx = cairo.Matrix(C,S,-S,C,x-C*x+S*y,y-S*x-C*y)
X-skew by A -- mtrx = cairo.Matrix(1,0,T,1,0,0)
Y-skew by A -- mtrx = cairo.Matrix(1,T,0,1,0,0)
Flip H/V with center in cx:cy -- mtrx = cairo.Matrix(fx,0,0,fy,cx*(1-fx),cy*(fy-1))
Flip H/V and rotation with center in cx:cy -- mtrx =
cairo.Matrix(fx*C,fx*S,-S*fy,C*fy,C*cx*(1-fx)-S*cy*(fy-1)+cx-C*cx+S*cy,S*cx*(1-fx)+C*cy*(fy-
1)+cy-S*cx-C*cy)
(For flips fx/fy = 1 means 'no flip', fx/fy = -1 are used for horizontal/vertical flip).
--------------------------------------------------------------------------------
To apply more than one transformation you can multiply matrix. 'Unfortunately' matrix
multiplication is slightly different than regular multiplication of numbers. For square
matrix with N columns and N rows the rule is 'Rij == sum of Aix to Bxj products, where x =
[1,N]'. It's easy to figure out that for matrix multiplication A*B is not always the same as
B*A. The rule of matrix multiplication is illustrated with a picture here:
quickframework
A Quick GTK+ GNU/Linux Framework
I got this from http://www.tortall.net/mu/wiki/PyGTKCairoTutorial : it's handy for running
snippets of cairo code to see what's going on.
This sample shows how to use a mask (a second source to filter the first source.)
#! /usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):
# Draw in response to an expose-event
__gsignals__ = { "expose-event": "override" }
# Handle the expose-event by drawing
def do_expose_event(self, event):
# Create the cairo context
cr = self.window.cairo_create()
# Restrict Cairo to the exposed area; avoid extra work
cr.rectangle(event.area.x, event.area.y,
event.area.width, event.area.height)
cr.clip()
self.draw(cr, *self.window.get_size())
def draw(self, cr, width, height):
# Fill the background with gray
cr.set_source_rgb(0.5, 0.5, 0.5)
cr.rectangle(0, 0, width, height)
cr.fill()
# GTK mumbo-jumbo to show the widget in a window and quit when it's closed
def run(Widget):
window = gtk.Window()
window.connect("delete-event", gtk.main_quit)
widget = Widget()
widget.show()
window.add(widget)
window.present()
gtk.main()
## Do all your testing in Shapes ##
class Shapes(Screen):
def draw(self, cr, width, height):
## This will draw using a mask.
cr.scale(width,height) #Without this line the mask does not seem to work!
self.linear = cairo.LinearGradient(0, 0, 1, 1)
self.linear.add_color_stop_rgb(0, 0, 0.3, 0.

self.linear.add_color_stop_rgb(1, 0, 0.8, 0.3)
self.radial = cairo.RadialGradient(0.5, 0.5, 0.25, 0.5, 0.5, 0.5)
self.radial.add_color_stop_rgba(0, 0, 0, 0, 1)
self.radial.add_color_stop_rgba(0.5, 0, 0, 0, 0)
cr.set_source(self.linear)
cr.mask(self.radial)
run(Shapes)
In a cairo.matrix(1,2,3,4,5,6), 1 is a11, 2 is a21, 3 is a12, 4 is a22, 5 is a13 and 6 is
a23. a31 and a32 are 0, a33 is 1.
animationrotation
A pyCairo/PyGTK animation framework
There are two demos now, the first shows (gasp) a square rotating around an arbitrary point.
Scroll down for the second one which is longer and a bit more interesting.
This demo is two things: First it's a nice little framework that gives "life" to a function
so that animation becomes possible. If you want stuff to be drawn again and again with
little changes, this is a good start. (I'd love to see other solutions). Secondly it shows
how to rotate a "shape" (set of cairo commands) around any given ( x, y ) point.
I tried to comment it thoroughly, so it will 'splain itself.
## ani Copyright (C) 2008 anirudh pandey
##
## Contact: anicoolrocks@hotmail.com - I hope this email lasts
import pygtk
import gtk, gobject, cairo
from gtk import gdk
class Screen( gtk.DrawingArea ):
""" This class is a Drawing Area"""
def __init__( self, w, h, speed ):
super( Screen, self ).__init__( )
## Old fashioned way to connect expose. I don't savvy the gobject stuff.
self.connect ( "expose_event", self.do_expose_event )
## We want to know where the mouse is:
self.connect ( "motion_notify_event", self._mouseMoved )
## More GTK voodoo : unmask events
self.add_events ( gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK |
gdk.POINTER_MOTION_MASK )
## This is what gives the animation life!
gobject.timeout_add( speed, self.tick ) # Go call tick every 'speed' whatsits.
self.width, self.height = w, h
self.set_size_request ( w, h )
self.x, self.y = 11110,11111110 #unlikely first coord to prevent false hits.
def tick ( self ):
"""This invalidates the screen, causing the expose event to fire."""
self.alloc = self.get_allocation ( )
rect = gtk.gdk.Rectangle ( self.alloc.x, self.alloc.y, self.alloc.width,
self.alloc.height )
self.window.invalidate_rect ( rect, True )
return True # Causes timeout to tick again.
## When expose event fires, this is run
def do_expose_event( self, widget, event ):
self.cr = self.window.cairo_create( )
## Call our draw function to do stuff.
self.draw( )
def _mouseMoved ( self, widget, event ):
self.x = event.x
self.y = event.y
class BunchOfStuff ( object ):
"""Stores a bunch of data"""
def __init__ ( self, x=0, y=0, rx=0, ry=0, rot=0, sx=1, sy=1 ):
self.x = x
self.y = y
self.rx = rx
self.ry = ry
self.rot = rot
self.sx = sx
self.sy = sy
class MyStuff ( Screen ):
"""This class is also a Drawing Area, coming from Screen."""
def __init__ ( self, w, h, speed):
Screen.__init__( self, w, h, speed )
## Setup three sets of data for the three objects to be drawn
self.red = BunchOfStuff ( x=50, y=-10, rx=50, ry=25 )
self.green = BunchOfStuff ( x=-10, y=10 )
self.blue = BunchOfStuff ( x=-70,y=30, sx=1, sy=1 )
self.sign = +1 # to flip the blue animation's sign
def setToCenter ( self ):
"""Shift 0,0 to be in the center of page."""
matrix = cairo.Matrix ( 1, 0, 0, 1, self.width/2, self.height/2 )
self.cr.transform ( matrix ) # Make it so...
def doMatrixVoodoo ( self, bos ):
"""Do all the matrix mumbo to get stuff to the right place on the screen."""
ThingMatrix =cairo.Matrix ( 1, 0, 0, 1, 0, 0 )
## Next, move the drawing to it's x,y
cairo.Matrix.translate ( ThingMatrix, bos.x, bos.y )
self.cr.transform ( ThingMatrix ) # Changes the context to reflect that
## Now, change the matrix again to:
if bos.rx != 0 and bos.ry != 0 : # Only do this if there's a special reason
cairo.Matrix.translate( ThingMatrix, bos.rx, bos.ry ) # move it all to point of
rotation
cairo.Matrix.rotate( ThingMatrix, bos.rot ) # Do the rotation
if bos.rx != 0 and bos.ry != 0 :
cairo.Matrix.translate( ThingMatrix, -bos.rx, -bos.ry ) # move it back again
cairo.Matrix.scale( ThingMatrix, bos.sx, bos.sy ) # Now scale it all
self.cr.transform ( ThingMatrix ) # and commit it to the context
def draw( self ):
cr = self.cr # Shabby shortcut.
#---------TOP LEVEL - THE "PAGE"
self.cr.identity_matrix ( ) # VITAL LINE :: I'm not sure what it's doing.
self.setToCenter ( )
#----------FIRST LEVEL
cr.save ( ) # Creates a 'bubble' of private coordinates. Save # 1
## RED - draw the red object
self.doMatrixVoodoo ( self.red )
self.drawCairoStuff ( self.red )
#---------- SECOND LEVEL - RELATIVE TO FIRST
cr.save ( ) #save 2
## GREEN - draw the green one
self.doMatrixVoodoo ( self.green )
self.drawCairoStuff ( self.green, col= ( 0,1,0 ) )
## Demonstrate how to detect a mouse hit on this shape:
## Draw the hit shape :: It *should* be drawn exactly over the green rectangle.
self.drawHitShape ( )
cr.save ( ) # Start a bubble
cr.identity_matrix ( ) # Reset the matrix within it.
hit = cr.in_fill ( self.x, self.y ) # Use Cairo's built-in hit test
cr.new_path ( ) # stops the hit shape from being drawn
cr.restore ( ) # Close the bubble like this never happened.
cr.restore ( ) #restore 2 :: "pop" the bubble.
## We are in level one's influence now
cr.restore ( ) #restore 1
## Back on PAGE's influence now
#-------- THIRD LEVEL -- RELATIVE TO PAGE
cr.save ( ) # Creates a 'bubble' of private coordinates.
## Draw the blue object
self.doMatrixVoodoo ( self.blue ) # within the bubble, this will not effect the PAGE
self.drawCairoStuff ( self.blue, col= ( 0,0,1 ) )
cr.restore ( )
## Back on the PAGE level again.
#indicate center
self.drawCrosshair ( )
self.guageScale ( )
## Let's animate the red object
## ( which *also* moves the green because it's a 'child'
## of the red by way of being in the same "bubble" )
self.red.rot += 0.01
## Now animate the blue
self.blue.sx += self.sign * 0.1
if self.blue.sx < 0 or self.blue.sx > 4:
self.sign *= -1
self.blue.sy = self.blue.sx
## Print to the console -- low-tech special effects

if hit: print "HIT!", self.x, self.y
def guageScale ( self ):
"""Draw some axis so we can see where stuff is."""
c = self.cr
m = 0
for x in range ( 10,210,10 ):
m += 1
w = 10 + ( m % 2 * 10 )
if x == 100: w = 50
c.rectangle ( x,-w/2,1,w )
c.rectangle ( -x, -w/2, 1, w )
c.rectangle ( -w/2, x, w, 1 )
c.rectangle ( -w/2, -x , w, 1 )
c.set_source_rgb ( 0,0,0 )
c.fill ( )
def drawCairoStuff ( self, bos, col= ( 1,0,0 ) ):
"""This draws the squares we see. Pass it a BagOfStuff (bos) and a colour."""
cr = self.cr
## Thrillingly, we draw a rectangle.
## It's drawn such that 0,0 is in it's center.
cr.rectangle( -25, -25, 50, 50 )
cr.set_source_rgb( col[0],col[1],col[2] )
cr.fill( )
## Now draw an axis
self.guageScale ( )
## Now a visual indicator of the point of rotation
cr.set_source_rgb( 1,1,1 )
cr.rectangle ( bos.rx - 2, bos.ry - 2, 4, 4 )
cr.fill ( )
## Same as the rectangle we see. No fill.
def drawHitShape ( self ):
"""Draws a shape that we'll use to test hits."""
self.cr.rectangle( -25, -25, 50, 50 ) # Same as the shape of the squares
def drawCrosshair ( self ):
"""Another visual aid."""
ctx = self.cr
ctx.set_source_rgb ( 0, 0, 0 )
ctx.move_to ( 0,10 )
ctx.line_to ( 0, -10 )
ctx.move_to ( -10, 0 )
ctx.line_to ( 10, 0 )
ctx.stroke ( )
def run( Widget, w, h, speed ):
window = gtk.Window( )
window.connect( "delete-event", gtk.main_quit )
widget = Widget( w, h, speed )
widget.show( )
window.add( widget )
window.present( )
gtk.main( )
run( MyStuff, 400, 400, speed = 20 )
renderpdf
Rendering a PDF or PS file with cairo
PDF files can be rendered to a cairo context using poppler. PS or EPS files can also be
rendered to a cairo context by first converting to PDF using Ghostscript.
When using a vector backend, the vectors and text in the PDF file are preserved in the
output as vectors. There is no unnecessary rasterization.
Compile the example with:
gcc -o pdf2cairo pdf2cairo.c `pkg-config --cflags --libs cairo poppler-glib`
pdf2cairo.c:
#include <poppler.h>
#include <cairo.h>
#include <cairo-ps.h>
int main(int argc, char *argv[])
{
PopplerDocument *document;
PopplerPage *page;
double width, height;
GError *error;
const char *filename;
gchar *absolute, *uri;
int num_pages, i;
cairo_surface_t *surface;
cairo_t *cr;
cairo_status_t status;
if (argc != 2) {
printf ("Usage: pdf2cairo input_file.pdf\n");
return 0;
}
filename = argv[1];
g_type_init ();
error = NULL;
if (g_path_is_absolute(filename)) {
absolute = g_strdup (filename);
} else {
gchar *dir = g_get_current_dir ();
absolute = g_build_filename (dir, filename, (gchar *) 0);
free (dir);
}
uri = g_filename_to_uri (absolute, NULL, &error);
free (absolute);
if (uri == NULL) {
printf("poppler fail: %s\n", error->message);
return 1;
}
document = poppler_document_new_from_file (uri, NULL, &error);
if (document == NULL) {
printf("poppler fail: %s\n", error->message);
return 1;
}
num_pages = poppler_document_get_n_pages (document);
/* Page size does not matter here as the size is changed before
* each page */
surface = cairo_ps_surface_create ("output.ps", 595, 842);
cr = cairo_create (surface);
for (i = 0; i < num_pages; i++) {
page = poppler_document_get_page (document, i);
if (page == NULL) {
printf("poppler fail: page not found\n");
return 1;
}
poppler_page_get_size (page, &width, &height);
cairo_ps_surface_set_size (surface, width, height);
cairo_save (cr);
/* Use poppler_page_render () for image backends and
* poppler_page_render_for_printing() for vector backends. */
poppler_page_render_for_printing (page, cr);
cairo_restore (cr);
cairo_surface_show_page (surface);
g_object_unref (page);
}
status = cairo_status(cr);
if (status)
printf("%s\n", cairo_status_to_string (status));
cairo_destroy (cr);
cairo_surface_finish (surface);
status = cairo_surface_status(surface);
if (status)
printf("%s\n", cairo_status_to_string (status));
cairo_surface_destroy (surface);
g_object_unref (document);
return 0;
}
svgtopycairo
SVG paths can be parsed and turned into a seqence of cairo commands that re-draw them.
This took a while, the pyparsing had me in knots, but now it's short and sweet. A fuller
implementation of what can be done in SVG would be really nice. (Hint...)
Make sure you pass it a very simple SVG file (from Inkscape is best) -- one that has had all
the shapes reduced to paths. Oh, and keep your canvas 400 by 400 or it may draw clear off
the screen.
Depends on
elementree: import elementree as myDearWatson

XML.
pyparsing: This module is deeply wonderful. I won't pretend to savvy even 1% of it, but it
really does the job. They have a great mailing list where I got a lot of help. It let's you
parse strings into lists and that is no small feat.
SVG Path element
To briefly explain, inside an svg file (which is just xml) you'll find a tag named 'g' and
under that one or more tags named 'path'. Inside path there is an element called 'd'; that's
the actual path. It's formed like this: "COMMAND NUMBER COMMA NUMBER Optionally[NUMBER COMMA
NUMBER a few more times]", where COMMAND is M for move, L for line, C for curve and Z for
close path. There may be others, but that's what I tackled. Have a look at the pyparsing
grammar which makes it fairly clear how different commands have different numbers behind
them.
Please forgive any bugs.
#!/usr/bin/env python
"""\
Usage: drawsvg.py file
file - one SVG file (from Inkscape!) that is all simple paths
"""
## ani Copyright (C) 2008 anirudh pandey
##
## Contact: anicoolrocks@hotmail.com - I hope this email lasts.
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
from pyparsing import *
import os, sys
from elementtree import ElementTree as et
# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):
# Draw in response to an expose-event
__gsignals__ = { "expose-event": "override" }
# Handle the expose-event by drawing
def do_expose_event(self, event):
# Create the cairo context
cr = self.window.cairo_create()
# Restrict Cairo to the exposed area; avoid extra work
cr.rectangle(event.area.x, event.area.y,
event.area.width, event.area.height)
cr.clip()
self.draw(cr, *self.window.get_size())
def draw(self, cr, width, height):
# Fill the background with gray
cr.set_source_rgb(0.5, 0.5, 0.5)
cr.rectangle(0, 0, width, height)
cr.fill()
# GTK mumbo-jumbo to show the widget in a window and quit when it's closed
def run(Widget):
window = gtk.Window()
window.set_size_request(400, 400)
window.connect("delete-event", gtk.main_quit)
widget = Widget()
widget.show()
window.add(widget)
window.present()
gtk.main()
## Do the drawing ##
class Shapes(Screen):
def draw(self, ctx, width, height):
#Build a string of cairo commands
cairo_commands = ""
command_list = []
for tokens in paths:
for command,couples in tokens[:-1]: #looks weird, but it works

c = couples.asList()
if command == "M":
cairo_commands += "ctx.move_to(%s,%s);" % (c[0],c[1])
if command == "C":
cairo_commands += "ctx.curve_to(%s,%s,%s,%s,%s,%s);" %
(c[0],c[1],c[2],c[3],c[4],c[5])
if command == "L":
cairo_commands += "ctx.line_to(%s,%s);" % (c[0],c[1])
if command == "Z":
cairo_commands += "ctx.close_path();"
command_list.append(cairo_commands) #Add them to the list
cairo_commands = ""
#Draw it. Only stroked, to fill as per the SVG drawing is another whole story.
ctx.set_source_rgb(1,0,0)
for c in command_list:
exec(c)
ctx.stroke()
#Check args:
if len(sys.argv) < 2:
raise SystemExit(__doc__)
file = sys.argv[1]
## Pyparsing grammar:
## With HUGE help from Paul McGuire <paul@alanweberassociates.com>
## Thanks!
dot = Literal(".")
comma = Literal(",").suppress()
floater = Combine(Optional("-") + Word(nums) + dot + Word(nums))
## Unremark to have numbers be floats rather than strings.
#floater.setParseAction(lambda toks:float(toks[0]))
couple = floater + comma + floater
M_command = "M" + Group(couple)
C_command = "C" + Group(couple + couple + couple)
L_command = "L" + Group(couple)
Z_command = "Z"
svgcommand = M_command | C_command | L_command | Z_command
phrase = OneOrMore(Group(svgcommand))
## Find and open the svg file
xml_file = os.path.abspath(__file__)
xml_file = os.path.dirname(xml_file)
xml_file = os.path.join(xml_file, file)
tree = et.parse(xml_file)
ns = "http://www.w3.org/2000/svg" #The XML namespace.
paths = []
for group in tree.getiterator('{%s}g' % ns):
for e in group.getiterator('{%s}path' % ns):
p = e.get("d")
tokens = phrase.parseString(p.upper())
paths.append(tokens) # paths is a global var.
run(Shapes)
Loading fonts using FreeType for cairo use in Python
Back to cookbook
The following snippet uses Python's ctypes module to load a font file using FreeType and
create a cairo font face from it, using the cairo-ft API that is not part of pycairo yet.
The resulting cairo font face however can be used normally with pycairo.
import ctypes
import cairo
_initialized = False
def create_cairo_font_face_for_file (filename, faceindex=0, loadoptions=0):
global _initialized
global _freetype_so
global _cairo_so
global _ft_lib
global _surface
CAIRO_STATUS_SUCCESS = 0
FT_Err_Ok = 0
if not _initialized:
# find shared objects
_freetype_so = ctypes.CDLL ("libfreetype.so.6")
_cairo_so = ctypes.CDLL ("libcairo.so.2")
# initialize freetype
_ft_lib = ctypes.c_void_p ()
if FT_Err_Ok != _freetype_so.FT_Init_FreeType (ctypes.byref (_ft_lib)):
raise "Error initialising FreeType library."
class PycairoContext(ctypes.Structure):
_fields_ = [("PyObject_HEAD", ctypes.c_byte * object.__basicsize__),
("ctx", ctypes.c_void_p),
("base", ctypes.c_void_p)]
_surface = cairo.ImageSurface (cairo.FORMAT_A8, 0, 0)
_initialized = True
# create freetype face
ft_face = ctypes.c_void_p()
cairo_ctx = cairo.Context (_surface)
cairo_t = PycairoContext.from_address(id(cairo_ctx)).ctx
_cairo_so.cairo_ft_font_face_create_for_ft_face.restype = ctypes.c_void_p
if FT_Err_Ok != _freetype_so.FT_New_Face (_ft_lib, filename, faceindex,
ctypes.byref(ft_face)):
raise "Error creating FreeType font face for " + filename
# create cairo font face for freetype face
cr_face = _cairo_so.cairo_ft_font_face_create_for_ft_face (ft_face, loadoptions)
if CAIRO_STATUS_SUCCESS != _cairo_so.cairo_font_face_status (cr_face):
raise "Error creating cairo font face for " + filename
_cairo_so.cairo_set_font_face (cairo_t, cr_face)
if CAIRO_STATUS_SUCCESS != _cairo_so.cairo_status (cairo_t):
raise "Error creating cairo font face for " + filename
face = cairo_ctx.get_font_face ()
return face
if __name__ == '__main__':
face = create_cairo_font_face_for_file
("/usr/share/fonts/dejavu-lgc/DejaVuLGCSerif.ttf", 0)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 128)
ctx = cairo.Context(surface)
ctx.set_font_face(face)
ctx.set_font_size(30)
ctx.move_to(0, 44)
ctx.show_text("Hello,")
ctx.move_to(30, 74)
ctx.show_text("world!")
del ctx
surface.write_to_png("hello.png")
Cairo's compositing operators
Normally, you will be using cairo to draw objects on top of each other. But cairo can do
differently, if you need it! In fact, you can use all the Porter/Duff compositing operators.
Fourteen different operators are currently available in cairo. This page is a try to
describe them. It may contain errors and is possibly incomplete. Please help to improve it!
Example images
It is probably best to describe the effect of compositing operators by showing example
images, so that's what is done below. The images show the result of drawing an object (a
translucent blue rectangle) onto a surface which already contains another image (a
translucent red rectangle).
The operator names try to describe what the operators mean. Two words need to be mentioned:
the source and the destination (or dest). In this context, these provide the two objects
which contribute to the graphical output of the drawing operation. The destination is the
name of the surface before the drawing operation, the source is the name of what is added
while drawing.
The Destination
To create the example images, the red rectangle is first drawn to the (empty) destination
surface with the default operator:
cairo_rectangle (cr, 0, 0, 120, 90);
cairo_set_source_rgba (cr, 0.7, 0, 0, 0.

cairo_fill (cr);
The surface is now a transparent plane with a partially transparent rectangle in it. This
represents the destination to which the next drawing operation will be performed. Both the
red and the fully transparent part of the surface are now natural parts of the destination.
Setting the compositing operator
The current operator is now set using cairo_set_operator().
The Source
The tutorial describes how drawing works in cairo:
"Cairo internally draws with one fundamental drawing operation: the source and mask are
freely placed somewhere over the destination. Then the layers are all pressed together and
the paint from the source is transferred to the destination wherever the mask allows it."
However, there is no mention of a mask in the names of the compositing operators. They refer
to a source and a destination only. This allows for two different interpretations with
regard to what should be regarded as the source for the compositing operation:
Since the mask determines where the paint from the source is transferred, it also determines
the area in which the compositing operation is performed. This interpretation can be called
bounded.
The area where the compositing operation is performed is not bounded by the mask. Instead,
any parts where the source layer is not transferred are considered to be fully transparent.
We call this interpretation of the source unbounded.
For some of the operators, both interpretations lead to the same result, for others they do
not. In cairo, the actual interpretation depends on the operator. The unbounded operators
modify destination outside of the bounds of the mask. However, their effect can still be
limited by way of clipping.
The blue rectangle is drawn with the new operator in effect:
cairo_rectangle (cr, 40, 30, 120, 90);
cairo_set_source_rgba (cr, 0, 0, 0.9, 0.4);
cairo_fill (cr);
The results can be seen in the images below. Note that both rectangles are drawn with
transparency. When studying the images, you may be able to guess how they would look for
opaque objects, too. For reference, the code to create the images is also available.
Operators
For the mathematical description, it is convenient to use the following conventions:
The index A (assigned to color components and transparency factor) refers to the source,
while the index B refers to the destination. The index R refers to the result of the
compositing operation.
Each pixel in both the source and the destination is fully described by a tuple (r, g, b,
a). We will use "x" as a shorthand for any color component, since they are always treated
the same. "a" describes the alpha value (opacity) and is treated differently.
The product of xA and aA is described by xaA. xaB and xaR have analogous meaning.
The color computations equations contain a division by aR, which cannot be done where aR is
zero. But in this case the color doesn't matter anyway (full transparency) and can also be
set to zero. (Internally, the colors may be handled differently, so this might or might not
be necessary.)
CAIRO_OPERATOR_CLEAR
Where the second object is drawn, the first is completely removed. Anywhere else it is left
intact. The second object itself is not drawn.
The effect of the CLEAR operator depends on the interpretation of the source. In cairo, this
operator is bounded.
Resulting alpha (aR) Resulting color (xR)
0 0
CAIRO_OPERATOR_SOURCE
The second object is drawn as if nothing else were below. Only outside of the blue rectangle
the red one is left intact.
The effect of the SOURCE operator depends on the interpretation of the source. In cairo,
this operator is bounded.
Resulting alpha (aR) Resulting color (xR)
aA xA
CAIRO_OPERATOR_OVER
The image shows what you would expect if you held two semi-transparent slides on top of each
other. This operator is cairo's default operator.
The output of the OVER operator is the same for both bounded and unbounded source.
Resulting alpha (aR) Resulting color (xR)
aA + aB·(1-aA) (xaA + xaB·(1-aA))/aR
CAIRO_OPERATOR_IN
The first object is removed completely, the second is only drawn where the first was.
Note that the transparency of the first object is taken into account. That is, the small
blue rectangle is slightly lighter than it would have been, had the first object been
opaque.
The effect of the IN operator depends on the interpretation of the source. In cairo, this
operator is unbounded.
Resulting alpha (aR) Resulting color (xR)
aA·aB xA
CAIRO_OPERATOR_OUT
The blue rectangle is drawn only where the red one wasn't. Since the red one was partially
transparent, you can see a blue shadow in the overlapping area. Otherwise, the red object is
completely removed.
The effect of the OUT operator depends on the interpretation of the source. In cairo, this
operator is unbounded.
Resulting alpha (aR) Resulting color (xR)
aA·(1-aB) xA
CAIRO_OPERATOR_ATOP
This leaves the first object mostly intact, but mixes both objects in the overlapping area.
The second object object is not drawn except there.
If you look closely, you will notice that the resulting color in the overlapping area is
different from what the OVER operator produces. Any two operators produce different output
in the overlapping area!
The output of the ATOP operator is the same for both bounded and unbounded source.
Resulting alpha (aR) Resulting color (xR)
aB xaA + xB·(1-aA)
CAIRO_OPERATOR_DEST
Leaves the first object untouched, the second is discarded completely.
The output of the DEST operator is the same for both bounded and unbounded source.
Resulting alpha (aR) Resulting color (xR)
aB xB
CAIRO_OPERATOR_DEST_OVER
The result is similar to the OVER operator. Except that the "order" of the objects is
reversed, so the second is drawn below the first.
The output of the DEST_OVER operator is the same for both bounded and unbounded source.
Resulting alpha (aR) Resulting color (xR)
(1-aB)·aA + aB (xaA·(1-aB) + xaB)/aR
CAIRO_OPERATOR_DEST_IN
The blue rectangle is used to determine which part of the red one is left intact. Anything
outside the overlapping area is removed.
This works like the IN operator, but again with the second object "below" the first.
Similar to the two interpretations of the source, it is possible to imagine the same
distinction with regard to the destination. In cairo, the DEST_IN operator is unbounded.
Resulting alpha (aR) Resulting color (xR)
aA·aB xB
CAIRO_OPERATOR_DEST_OUT
The second object is used to reduce the visibility of the first in the overlapping area. Its
transparency/opacity is taken into account. The second object is not drawn itself.
The output of the DEST_OUT operator is the same for both bounded and unbounded
interpretations.
Resulting alpha (aR) Resulting color (xR)
(1-aA)·aB xB
CAIRO_OPERATOR_DEST_ATOP
Same as the ATOP operator, but again as if the order of the drawing operations had been
reversed.
In cairo, the DEST_ATOP operator is unbounded.
Resulting alpha (aR) Resulting color (xR)
aA xA·(1-aB) + xaB
CAIRO_OPERATOR_XOR
The output of the XOR operator is the same for both bounded and unbounded source
interpretations.
Resulting alpha (aR) Resulting color (xR)
aA + aB - 2·aA·aB (xaA·(1-aB) + xaB·(1-aA))/aR
CAIRO_OPERATOR_ADD
The output of the ADD operator is the same for both bounded and unbounded source
interpretations.
Resulting alpha (aR) Resulting color (xR)
min(1, aA+aB) (xaA + xaB)/aR
CAIRO_OPERATOR_SATURATE
The output of the SATURATE operator is the same for both bounded and unbounded source
interpretations.
Resulting alpha (aR) Resulting color (xR)
min(1, aA+aB) (min(aA, 1-aB)·xA + xaB)/aR
Links
The original paper by Thomas Porter and Tom Duff:
http://keithp.com/~keithp/porterduff/p253-porter.pdf,
The description of the X Rendering Extension by Keith Packard is where the information about
the ADD and SATURATE operators comes from: http://keithp.com/~keithp/render/protocol.html
A description of compositing in SVG 1.2 by Craig Northway:
http://www.svgopen.org/2005/papers/abstractsvgopen/











