2D-ENGINE(CARIO)--------REAL STUFF FOR REAL HACKERS

Discussion about hacker.org's server
Post Reply
User avatar
anicoolrocks
Posts: 29
Joined: Tue Jul 01, 2008 5:03 am

2D-ENGINE(CARIO)--------REAL STUFF FOR REAL HACKERS

Post by anicoolrocks »

:evil: :evil: :evil: :evil: :evil: :evil: :twisted: :roll: :twisted: :twisted: :twisted:

2D ENGINE(CAIRO)

:shock: :shock: :shock: :shock: :shock: 8) 8) 8) 8) :arrow: :arrow: :?: :idea:

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.8) # Curve(x1, y1, x2, y2, x3, y3)
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.8)
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 :) It's a great module for slicing through

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.8);
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/



:P :P :P :P :P :twisted: :twisted: :twisted: :evil: :evil: :evil: :evil:
The_Dark_Avenger
Posts: 115
Joined: Wed Jun 11, 2008 9:47 pm

Post by The_Dark_Avenger »

attemp of humor??
Post Reply