Posted by Colonel Patch on May 25, 2011
Here is a little demo I did to help get my head around using cairo and pango as I wanted to have a base for a flexible widget. It uses scaling ,translation and animation to bounce an image around the screen. There is also a routine for detecting mouse clicks so you can "tickle" the head!
## Program to show the use of cairo graphics
## and matrix operations for transforms
## author: Paul Heaton
## email: p.m.heaton@reading.ac.uk
import pygtk
import gtk, gobject, cairo, pango
from gtk import gdk
class CairoScreen( gtk.DrawingArea ):
def __init__(self):
#superclass this Drawing Area as a custom widget
super(CairoScreen,self).__init__()
## Connect expose event.
self.connect ( "expose_event", self.do_expose_event )
## connect mouse callbacks:
#self.connect ( "motion_notify_event", self._mouseMoved )
self.connect ( "button_press_event", self._mouseClicked )
## unmask events
self.add_events ( gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK )
##Initiate timer at 25 fps.
gobject.timeout_add( 40, self.tick )
def tick ( self ):
## Invalidates the screen so the expose event fires.
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 # Cause timeout to tick again.
def _mouseMoved ( self, widget, event ):
self.mx = event.x
self.my = event.y
self.draw(self.width,self.height)
#print "mx="+str(event.x)+" my="+str(event.y)
def _mouseClicked ( self, widget, event ):
self.mx = event.x
self.my = event.y
#print "mx="+str(event.x)+" my="+str(event.y)
def do_expose_event( self, widget, event ):
## create a cairo resource
self.cr = self.window.cairo_create( )
##Redraw screen at current screensize.
self.draw( *self.window.get_size( ) )
## Derive a new cairo screen from CairoScreen class above
class AnimScreen ( CairoScreen ):
"""Add extravariables to hold object (imagesurface) and
object variables to CairoScreen."""
def __init__ ( self ):
CairoScreen.__init__( self )
# add your own image here!
self.imagesurface = cairo.ImageSurface.create_from_png('baby_head_small.png')
# add pango layout for on screen text
self.pangolayout = self.create_pango_layout("text")
self.pangolayout.set_text ("ooooh.\n That tickles!")
# [FAMILY-LIST] [STYLE-OPTIONS] [SIZE] eg serif,monospace bold italic condensed 16
fd=pango.FontDescription("sans serif bold italic 28")
self.pangolayout.set_font_description(fd)
alloc=self.pangolayout.get_pixel_size()
self.fontx=alloc[0]
self.fonty=alloc[1]
print "fontxy=",self.fontx,", ",self.fonty
self.fontw, self.fonth = self.pangolayout.get_pixel_size()
#mouse pos passed from m events
self.mx = -999999
self.my = -999999
## x,y of object surface (left side of screen to start)
self.x, self.y = -200, 0
#save origin width and height
self.iw=self.imagesurface.get_width()
self.ih=self.imagesurface.get_height()
## move origin (mag dxdy=speed)
self.dx,self.dy=2,1
## rx,ry is radius of thetaation from surface orgin
self.rx, self.ry = 0, 0
## theta is angle counter
self.theta = 0.0
## sx,sy is to scale surface
self.sx, self.sy =1.0, 1.0
## sign variable for scaling and movement calcs
self.sign=+1
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 draw( self, width, height ):
## width and height of shape or image passed
## shortcut
cr = self.cr
cr.identity_matrix ( ) # reset context
#stuff drawn here will be drawn over by next layer
#so appearing behind
self.drawbox ( cr )
## Set 0,0 to be in the center of page
## ie...
## -y | -y
## -x | +x
## ----0------
## -x | +x
## +y | +y
#create a matrix path step to do transform
matrix = cairo.Matrix ( 1, 0, 0, 1, width/2, height/2 )
cr.transform ( matrix ) # and transform to image surface...
cr.save ( ) # save toplevel ie the above mask
## Create a matrix to change the imagesurface's position and theta rotation.
transform_matrix = cairo.Matrix ( 1, 0, 0, 1, 0, 0 )
#get current allocation of CairoScreen
x,y,w,h=self.get_allocation()
## with origin in center wall bounce is at +or- width/4 or height/4
## not sure why /4 !!
## remember matrix is in center of image
if self.x < -w/4 or self.x+self.imagesurface.get_width()/2 > w/4:
self.dx *= -1
if self.y < -h/4 or self.y + self.imagesurface.get_height()/2> h/4:
self.dy *=-1
## move transform_matrix to x,y of imagesurface
cairo.Matrix.translate ( transform_matrix, self.x, self.y )
## rotate a little
self.theta += 0.05
# move it all to point of rotation
## in this case the center of the image/shape
#and apply transform
cairo.Matrix.translate( transform_matrix , self.x, self.y )
## Now, rotate the matrix
cairo.Matrix.rotate( transform_matrix, self.theta ) # Do the rotation
# move it back again
cairo.Matrix.translate( transform_matrix, -self.x, -self.y)
## change size a bit
self.sx += self.sign * 0.005
if self.sx < 0.5 or self.sx > 1.5:
self.sign *= -1
self.sy = self.sx
# and Scale the matrix
cairo.Matrix.scale( transform_matrix, self.sx, self.sy ) # Now scale it all
## commit all matrix ops to context
## ie use the mask path above in cr when doing drawing ops
# ie Draw it
cr.transform ( transform_matrix )
## move the image surface x and y a bit
self.x += self.dx
self.y += self.dy
## anything drawn now is under influence of transform_matrix.
## until a restore is called
self.drawhead ( cr ,self.x,self.y)
x1=self.x
y1=self.y
x2=self.iw
y2=self.ih
self.cr.rectangle( x1, y1, x2, y2 ) # Same as the shape of the imagemap
cr.save() # save level 1 (bouncing head)
# Detect mouse hit
cr.identity_matrix ( ) # Reset the matrix to 0,0 in TLH Corner.
hit = cr.in_fill ( self.mx, self.my ) # Use Cairo's built-in hit test
cr.new_path ( ) # stops the hit shape from being drawn
#self.drawcross ( cr , self.mx, self.my)
cr.restore ( ) # restore level 1.
cr.restore ( ) # restore to toplevel
## Draw a crosshair to identify 0,0
self.drawcross ( cr )
if hit:
#print "HIT!", self.x, self.y
playout = self.pangolayout
cr.set_source_rgb ( 0, 0, 1 )
#could have offset here
cr.move_to ( self.mx-width/2,self.my-height/2 )
#cr.move_to((0 - fontw - 4), (0- fonth ))
cr.update_layout(playout)
cr.show_layout(playout)
def drawhead(self,cr, x=0, y=0):
cimg = self.imagesurface
cr.set_source_surface(cimg, x, y)
cr.paint() # draw image surface
def drawcross ( self, cr ,x=0, y=0):
## Default 0,0 in the center
cr.set_source_rgb ( 0, 0, 0 )
cr.move_to ( x,y+10 )
cr.line_to ( x, y-10 )
cr.move_to ( x-10, y )
cr.line_to ( x+10, y )
cr.stroke ( )
def drawbox ( self, cr):
## 0,0 TLHC
x,y,w,h=self.get_allocation()
cr.set_source_rgb ( 1, 0, 0 )
cr.move_to ( x,y )
cr.line_to ( w/2-w/4, h/2-h/4 )
cr.line_to ( w/2+w/4, h/2-h/4 )
cr.line_to ( w, y )
cr.move_to ( x,h )
cr.line_to ( w/2-w/4, h/2+h/4 )
cr.line_to ( w/2+w/4, h/2+h/4 )
cr.line_to ( w, h )
cr.move_to ( w/2-w/4, h/2-h/4 )
cr.line_to ( w/2-w/4, h/2+h/4 )
cr.move_to ( w/2+w/4, h/2-h/4 )
cr.line_to ( w/2+w/4, h/2+h/4 )
cr.stroke ( )
def run( Widget ):
window = gtk.Window( )
window.set_size_request(800,600)
window.set_position(gtk.WIN_POS_CENTER)
window.connect( "delete-event", gtk.main_quit )
widget = Widget( )
widget.show( )
window.add( widget )
window.present( )
gtk.main( )
if __name__=="__main__":
run( AnimScreen )
| © 2012 Man-Machine. | Powered by concrete5 | Sign In to Edit this Site |