How to use the Cairo 2D graphics library (Phoenix)

Keywords : Drawing, Cairo, BufferedPaintDC, Bitmap, Gradient.


Introduction :

Shows how to draw on a DC using the cairo 2D graphics library and either the PyCairo or cairocffi package which wrap the cairo API.


Demonstrating :

Tested py3.x, wx4.x and Win10.

Are you ready to use some samples ? ;)

Test, modify, correct, complete, improve and share your discoveries ! (!)


Sample one

You must install this package for use it :

pip install cairocffi

or

pip install pycairo

img_sample_one.png

Available with wxPython demo.

   1 # sample_one.py
   2 
   3 import os
   4 import wx
   5 import math
   6 
   7 try:
   8     import wx.lib.wxcairo as wxcairo
   9     import cairo
  10     haveCairo = True
  11 except ImportError:
  12     haveCairo = False
  13 
  14 # def opj
  15 # class MyPanel
  16 # class MyFrame
  17 # class MyApp
  18 
  19 #----------------------------------------------------------------------
  20 
  21 def opj(path):
  22     """
  23     Convert paths to the platform-specific separator.
  24     """
  25 
  26     st = os.path.join(*tuple(path.split('/')))
  27     # HACK: on Linux, a leading / gets lost...
  28     if path.startswith('/'):
  29         st = '/' + st
  30     return st
  31 
  32 #----------------------------------------------------------------------
  33 
  34 class MyPanel(wx.Panel):
  35     def __init__(self, parent):
  36         wx.Panel.__init__(self, parent, -1)
  37 
  38         self.Bind(wx.EVT_PAINT, self.OnPaint)
  39 
  40     #-----------------------------------------------------------------------
  41 
  42     def OnPaint(self, evt):
  43         """
  44         ...
  45         """
  46 
  47         if self.IsDoubleBuffered():
  48             dc = wx.PaintDC(self)
  49         else:
  50             dc = wx.BufferedPaintDC(self)
  51         dc.SetBackground(wx.WHITE_BRUSH)
  52         dc.Clear()
  53 
  54         self.Render(dc)
  55 
  56 
  57     def Render(self, dc):
  58         """
  59         ...
  60         """
  61 
  62         # Draw some stuff on the plain dc.
  63         sz = self.GetSize()
  64         dc.SetPen(wx.Pen("navy", 1))
  65 
  66         x = y = 0
  67         while x < sz.width * 2 or y < sz.height * 2:
  68             x += 20
  69             y += 20
  70             dc.DrawLine(x, 0, 0, y)
  71 
  72         # Now draw something with cairo.
  73         ctx = wxcairo.ContextFromDC(dc)
  74         ctx.set_line_width(15)
  75         ctx.move_to(125, 25)
  76         ctx.line_to(225, 225)
  77         ctx.rel_line_to(-200, 0)
  78         ctx.close_path()
  79         ctx.set_source_rgba(0, 0, 0.5, 1)
  80         ctx.stroke()
  81 
  82         # And something else...
  83         ctx.arc(200, 200, 80, 0, math.pi*2)
  84         ctx.set_source_rgba(0, 1, 1, 0.5)
  85         ctx.fill_preserve()
  86         ctx.set_source_rgb(1, 0.5, 0)
  87         ctx.stroke()
  88 
  89         # Here's a gradient pattern.
  90         ptn = cairo.RadialGradient(315, 70, 25,
  91                                    302, 70, 128)
  92         ptn.add_color_stop_rgba(0, 1,1,1,1)
  93         ptn.add_color_stop_rgba(1, 0,0,0,1)
  94         ctx.set_source(ptn)
  95         ctx.arc(328, 96, 75, 0, math.pi*2)
  96         ctx.fill()
  97 
  98         # Draw some text.
  99         face = wxcairo.FontFaceFromFont(
 100             wx.FFont(10, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD))
 101         ctx.set_font_face(face)
 102         ctx.set_font_size(60)
 103         ctx.move_to(360, 180)
 104         ctx.set_source_rgb(0, 0, 0)
 105         ctx.show_text("Hello")
 106 
 107         # Text as a path, with fill and stroke.
 108         ctx.move_to(400, 220)
 109         ctx.text_path("World")
 110         ctx.set_source_rgb(0.39, 0.07, 0.78)
 111         ctx.fill_preserve()
 112         ctx.set_source_rgb(0,0,0)
 113         ctx.set_line_width(2)
 114         ctx.stroke()
 115 
 116         # Show iterating and modifying a (text) path.
 117         ctx.new_path()
 118         ctx.move_to(0, 0)
 119         ctx.set_source_rgb(0.3, 0.3, 0.3)
 120         ctx.set_font_size(30)
 121         text = "This path was warped..."
 122         ctx.text_path(text)
 123         tw, th = ctx.text_extents(text)[2:4]
 124         self.warpPath(ctx, tw, th, 360,300)
 125         ctx.fill()
 126 
 127         # Drawing a bitmap.  Note that we can easily load a PNG file
 128         # into a surface, but I wanted to show how to convert from a
 129         # wx.Bitmap here instead.  This is how to do it using just cairo :
 130         #img = cairo.ImageSurface.create_from_png(opj('bitmaps/toucan.png'))
 131 
 132         # And this is how to convert a wx.Btmap to a cairo image
 133         # surface.  NOTE: currently on Mac there appears to be a
 134         # problem using conversions of some types of images.  They
 135         # show up totally transparent when used. The conversion itself
 136         # appears to be working okay, because converting back to
 137         # wx.Bitmap or writing the image surface to a file produces
 138         # the expected result.  The other platforms are okay.
 139         bmp = wx.Bitmap(opj('bitmaps/toucan.png'))
 140         img = wxcairo.ImageSurfaceFromBitmap(bmp)
 141 
 142         ctx.set_source_surface(img, 70, 230)
 143         ctx.paint()
 144 
 145         # This is how to convert an image surface to a wx.Bitmap.
 146         bmp2 = wxcairo.BitmapFromImageSurface(img)
 147         dc.DrawBitmap(bmp2, 280, 300)
 148 
 149 
 150     def warpPath(self, ctx, tw, th, dx, dy):
 151         """
 152         ...
 153         """
 154 
 155         def f(x, y):
 156             xn = x - tw/2
 157             yn = y+ xn ** 3 / ((tw/2)**3) * 70
 158             return xn+dx, yn+dy
 159 
 160         path = ctx.copy_path()
 161 
 162         ctx.new_path()
 163         for type, points in path:
 164             if type == cairo.PATH_MOVE_TO:
 165                 x, y = f(*points)
 166                 ctx.move_to(x, y)
 167 
 168             elif type == cairo.PATH_LINE_TO:
 169                 x, y = f(*points)
 170                 ctx.line_to(x, y)
 171 
 172             elif type == cairo.PATH_CURVE_TO:
 173                 x1, y1, x2, y2, x3, y3 = points
 174                 x1, y1 = f(x1, y1)
 175                 x2, y2 = f(x2, y2)
 176                 x3, y3 = f(x3, y3)
 177                 ctx.curve_to(x1, y1, x2, y2, x3, y3)
 178 
 179             elif type == cairo.PATH_CLOSE_PATH:
 180                 ctx.close_path()
 181 
 182 #---------------------------------------------------------------------------
 183 
 184 class MyFrame(wx.Frame):
 185     """
 186     ...
 187     """
 188     def __init__(self):
 189         super(MyFrame, self).__init__(None,
 190                                       -1,
 191                                       title="Sample_one")
 192 
 193         #------------
 194 
 195         # Simplified init method.
 196         self.SetProperties()
 197         self.CreateCtrls()
 198         self.BindEvents()
 199         self.DoLayout()
 200 
 201         #------------
 202 
 203         self.CenterOnScreen()
 204 
 205     #-----------------------------------------------------------------------
 206 
 207     def SetProperties(self):
 208         """
 209         ...
 210         """
 211 
 212         self.SetMinSize((600, 420))
 213 
 214         #------------
 215 
 216         frameicon = wx.Icon("wxwin.ico")
 217         self.SetIcon(frameicon)
 218 
 219 
 220     def CreateCtrls(self):
 221         """
 222         ...
 223         """
 224 
 225         # Create a panel.
 226         self.panel = MyPanel(self)
 227 
 228 
 229     def BindEvents(self):
 230         """
 231         Bind some events to an events handler.
 232         """
 233 
 234         # Bind the close event to an event handler.
 235         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 236 
 237 
 238     def DoLayout(self):
 239         """
 240         ...
 241         """
 242 
 243         # MainSizer is the top-level one that manages everything.
 244         mainSizer = wx.BoxSizer(wx.VERTICAL)
 245 
 246         # Finally, tell the panel to use the sizer for layout.
 247         self.panel.SetAutoLayout(True)
 248         self.panel.SetSizer(mainSizer)
 249 
 250         mainSizer.Fit(self.panel)
 251 
 252     #-----------------------------------------------------------------------
 253 
 254     def OnCloseMe(self, event):
 255         """
 256         ...
 257         """
 258 
 259         self.Close(True)
 260 
 261 
 262     def OnCloseWindow(self, event):
 263         """
 264         ...
 265         """
 266 
 267         self.Destroy()
 268 
 269 #---------------------------------------------------------------------------
 270 
 271 class MyApp(wx.App):
 272     """
 273     ...
 274     """
 275     def OnInit(self):
 276 
 277         #------------
 278 
 279         frame = MyFrame()
 280         self.SetTopWindow(frame)
 281         frame.CenterOnScreen(wx.BOTH)
 282         frame.Show(True)
 283 
 284         return True
 285 
 286 #---------------------------------------------------------------------------
 287 
 288 def main():
 289     app = MyApp(redirect=False)
 290     app.MainLoop()
 291 
 292 #---------------------------------------------------------------------------
 293 
 294 if __name__ == "__main__" :
 295     main()


Sample two

img_sample_two.png

   1 # sample_two.py
   2 
   3 import wx
   4 
   5 try:
   6     import wx.lib.wxcairo as wxcairo
   7     import cairo
   8     haveCairo = True
   9 except ImportError:
  10     haveCairo = False
  11 
  12 # class MyPanel
  13 # class MyFrame
  14 # class MyApp
  15 
  16 
  17 """
  18 We want the font to be dynamically sized so the text fits.
  19 Resizing the window should dynamically resize the text, but
  20 this should only happen if the text is not already at a
  21 width or height constraint.
  22 """
  23 
  24 #-------------------------------------------------------------------------------
  25 
  26 class MyPanel(wx.Panel):
  27     def __init__(self, parent):
  28         wx.Panel.__init__(self, parent, style=wx.BORDER_SIMPLE)
  29 
  30         #------------
  31 
  32         self.text = "Hello World !"
  33 
  34         #------------
  35 
  36         self.Bind(wx.EVT_SIZE, self.OnResize)
  37         self.Bind(wx.EVT_PAINT, self.OnPaint)
  38 
  39     #---------------------------------------------------------------------------
  40 
  41     def OnPaint(self, evt):
  42         """
  43         ...
  44         """
  45 
  46         # Here we do some magic WX stuff.
  47         dc = wx.BufferedPaintDC(self)
  48         width, height = self.GetClientSize()
  49         cr = wx.lib.wxcairo.ContextFromDC(dc)
  50 
  51         # Here's actual Cairo drawing.
  52         size = min(width, height)
  53         cr.scale(size, size)
  54         cr.set_source_rgb(0, 0, 0) # black
  55         cr.rectangle(0, 0, width, height)
  56         cr.fill()
  57 
  58         cr.set_source_rgb(1, 1, 1) # white
  59         cr.set_line_width (0.04)
  60         cr.select_font_face ("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
  61         cr.set_font_size (0.07)
  62         cr.move_to (0.5, 0.5)
  63         cr.show_text (self.text)
  64         cr.stroke ()
  65 
  66 
  67     def SetText(self, text):
  68         """
  69         ...
  70         """
  71 
  72         # Change what text is shown.
  73         self.text = text
  74         self.Refresh()
  75 
  76 
  77     def OnResize(self, event):
  78         """
  79         ...
  80         """
  81 
  82         self.Refresh()
  83         self.Layout()
  84 
  85 #-------------------------------------------------------------------------------
  86 
  87 class MyFrame(wx.Frame):
  88     def __init__(self, parent, title):
  89         wx.Frame.__init__(self, parent, title=title, size=(320, 250))
  90 
  91         self.SetMinSize((320, 250))
  92 
  93         frameicon = wx.Icon("wxwin.ico")
  94         self.SetIcon(frameicon)
  95 
  96         #------------
  97 
  98         self.canvas = MyPanel(self)
  99 
 100         self.Show()
 101 
 102 #-------------------------------------------------------------------------------
 103 
 104 class MyApp(wx.App):
 105     def OnInit(self):
 106 
 107         #------------
 108 
 109         frame = MyFrame(None, "Sample_two")
 110         self.SetTopWindow(frame)
 111         frame.Show(True)
 112 
 113         return True
 114 
 115 #-------------------------------------------------------------------------------
 116 
 117 def main():
 118     app = MyApp(False)
 119     app.MainLoop()
 120 
 121 #-------------------------------------------------------------------------------
 122 
 123 if __name__ == "__main__" :
 124     main()


Sample three

img_sample_three.png

   1 # sample_three.py
   2 
   3 import wx
   4 import math
   5 
   6 try:
   7     import wx.lib.wxcairo as wxcairo
   8     import cairo
   9     haveCairo = True
  10 except ImportError:
  11     haveCairo = False
  12 
  13 # class MyPanel
  14 # class MyFrame
  15 # class MyApp
  16 
  17 #---------------------------------------------------------------------------
  18 
  19 class MyPanel(wx.Panel):
  20     def __init__(self, parent):
  21         wx.Panel.__init__(self, parent, -1)
  22 
  23         self.Bind(wx.EVT_PAINT, self.OnPaint)
  24 
  25     #-----------------------------------------------------------------------
  26 
  27     def OnPaint(self, event):
  28         """
  29         ...
  30         """
  31 
  32         if self.IsDoubleBuffered():
  33             dc = wx.PaintDC(self)
  34         else:
  35             dc = wx.BufferedPaintDC(self)
  36         dc.SetBackground(wx.WHITE_BRUSH)
  37         dc.Clear()
  38 
  39         self.Render(dc)
  40 
  41 
  42     def Render(self, dc):
  43         """
  44         ...
  45         """
  46 
  47         # Now draw something with cairo.
  48         ctx = wxcairo.ContextFromDC(dc)
  49 
  50         # Drawing a bitmap.  Note that we can easily load a PNG file
  51         # into a surface, but I wanted to show how to convert from a
  52         # wx.Bitmap here instead.  This is how to do it using just cairo :
  53         # img = cairo.ImageSurface.create_from_png(opj('bitmaps/toucan.png'))
  54 
  55         # And this is how to convert a wx.Bitmap to a cairo image
  56         # surface.  NOTE : currently on Mac there appears to be a
  57         # problem using conversions of some types of images.  They
  58         # show up totally transparent when used. The conversion itself
  59         # appears to be working okay, because converting back to
  60         # wx.Bitmap or writing the image surface to a file produces
  61         # the expected result.  The other platforms are okay.
  62         bmp = wx.Bitmap('fruit.jpg')
  63         img = wxcairo.ImageSurfaceFromBitmap(bmp)
  64 
  65         ctx.set_source_surface(img, 50, 50)
  66         ctx.paint()
  67 
  68         # This is how to convert an image surface to a wx.Bitmap.
  69         bmp2 = wxcairo.BitmapFromImageSurface(img)
  70         dc.DrawBitmap(bmp2, 100, 100)
  71 
  72         img.write_to_png('img_sample_three.png')
  73 
  74 #---------------------------------------------------------------------------
  75 
  76 class MyFrame(wx.Frame):
  77     def __init__(self, parent, id, title):
  78         wx.Frame.__init__(self, parent, -1, title,
  79                           style=wx.DEFAULT_FRAME_STYLE)
  80 
  81         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  82 
  83         #------------
  84 
  85         self.SetIcon(wx.Icon('wxwin.ico'))
  86         self.SetInitialSize((330, 330))
  87 
  88         #------------
  89 
  90         # Attributes
  91         self.panel= MyPanel(self)
  92 
  93         # Layout
  94         self.DoLayout()
  95 
  96     #-----------------------------------------------------------------------
  97 
  98     def DoLayout(self):
  99         """
 100         ...
 101         """
 102 
 103         sizer = wx.BoxSizer(wx.VERTICAL)
 104         sizer.Add(self.panel, 1, wx.EXPAND)
 105         self.SetSizer(sizer)
 106 
 107 
 108     def OnCloseWindow(self, event):
 109         """
 110         ...
 111         """
 112 
 113         self.Destroy()
 114 
 115 #---------------------------------------------------------------------------
 116 
 117 class MyApp(wx.App):
 118     def OnInit(self):
 119 
 120         #------------
 121 
 122         frame = MyFrame(None, -1, "Sample three (image)")
 123         self.SetTopWindow(frame)
 124         frame.Show(True)
 125 
 126         return True
 127 
 128 #---------------------------------------------------------------------------
 129 
 130 def main():
 131     app = MyApp(False)
 132     app.MainLoop()
 133 
 134 #---------------------------------------------------------------------------
 135 
 136 
 137 if __name__ == "__main__" :
 138     main()


Sample four

img_sample_four.png

   1 # sample_four.py
   2 
   3 import wx
   4 import math
   5 
   6 try:
   7     import wx.lib.wxcairo as wxcairo
   8     import cairo
   9     haveCairo = True
  10 except ImportError:
  11     haveCairo = False
  12 
  13 # class MyPanel
  14 # class MyFrame
  15 # class MyApp
  16 
  17 #---------------------------------------------------------------------------
  18 
  19 class MyPanel(wx.Panel):
  20     def __init__(self, parent):
  21         wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
  22 
  23         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  24         self.Bind(wx.EVT_PAINT, self.OnPaint)
  25 
  26     #-----------------------------------------------------------------------
  27 
  28     def OnKeyDown(self, event):
  29         """
  30         ...
  31         """
  32 
  33         keycode = event.GetKeyCode()
  34 
  35         if keycode == wx.WXK_F12:
  36             self.OnSave()
  37         else:
  38             event.Skip()
  39 
  40 
  41     def OnPaint(self, event):
  42         """
  43         ...
  44         """
  45 
  46         if self.IsDoubleBuffered():
  47             dc = wx.PaintDC(self)
  48         else:
  49             dc = wx.BufferedPaintDC(self)
  50         dc.SetBackground(wx.YELLOW_BRUSH)
  51         dc.Clear()
  52 
  53         self.Render(dc)
  54 
  55 
  56     def Render(self, dc):
  57         """
  58         ...
  59         """
  60 
  61         # Now draw something with cairo.
  62         ctx = wxcairo.ContextFromDC(dc)
  63 
  64         # Drawing a bitmap.  Note that we can easily load a PNG file
  65         # into a surface, but I wanted to show how to convert from a
  66         # wx.Bitmap here instead.  This is how to do it using just cairo :
  67         # img = cairo.ImageSurface.create_from_png(opj('bitmaps/toucan.png'))
  68 
  69         # And this is how to convert a wx.Bitmap to a cairo image
  70         # surface.  NOTE : currently on Mac there appears to be a
  71         # problem using conversions of some types of images.  They
  72         # show up totally transparent when used. The conversion itself
  73         # appears to be working okay, because converting back to
  74         # wx.Bitmap or writing the image surface to a file produces
  75         # the expected result.  The other platforms are okay.
  76         bmp = wx.Bitmap("guidoy.png")
  77         self.img = wxcairo.ImageSurfaceFromBitmap(bmp)
  78 
  79         x = 20
  80         y = 20
  81         w = 275
  82         h = 250
  83         r = 20
  84 
  85         ctx.move_to(x+r, y)
  86         ctx.line_to(x+w-r-1, y)
  87         ctx.arc(x+w-r-1, y+r, r, -0.5*math.pi, 0)
  88         ctx.line_to(x+w-1, y+h-r-1)
  89         ctx.arc(x+w-r-1, y+h-r-1, r, 0, 0.5*math.pi)
  90         ctx.line_to(x+r, y+h-1)
  91         ctx.arc(x+r, y+h-r-1, r, 0.5*math.pi, math.pi)
  92         ctx.line_to(x, y+r)
  93         ctx.arc(x+r, y+r, r, math.pi, 1.5*math.pi)
  94         ctx.close_path()
  95         ctx.clip()
  96 
  97         ctx.set_source_surface(self.img, 0, 0)
  98         ctx.paint()
  99 
 100 
 101     def OnSave(self):
 102         """
 103         ...
 104         """
 105 
 106         self.img.write_to_png('img_sample_four.png')
 107 
 108 #---------------------------------------------------------------------------
 109 
 110 class MyFrame(wx.Frame):
 111     def __init__(self, parent, id, title):
 112         wx.Frame.__init__(self, parent, -1, title,
 113                           style=wx.DEFAULT_FRAME_STYLE)
 114 
 115         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
 116 
 117         #------------
 118 
 119         self.SetIcon(wx.Icon('wxwin.ico'))
 120         self.SetInitialSize((330, 330))
 121 
 122         #------------
 123 
 124         # Attributes
 125         self.panel= MyPanel(self)
 126 
 127         # Layout
 128         self.DoLayout()
 129 
 130     #-----------------------------------------------------------------------
 131 
 132     def DoLayout(self):
 133         """
 134         ...
 135         """
 136 
 137         sizer = wx.BoxSizer(wx.VERTICAL)
 138         sizer.Add(self.panel, 1, wx.EXPAND)
 139         self.SetSizer(sizer)
 140 
 141 
 142     def OnCloseWindow(self, event):
 143         """
 144         ...
 145         """
 146 
 147         self.Destroy()
 148 
 149 #---------------------------------------------------------------------------
 150 
 151 class MyApp(wx.App):
 152     def OnInit(self):
 153 
 154         #------------
 155 
 156         frame = MyFrame(None, -1, "Sample four (clip image)")
 157         self.SetTopWindow(frame)
 158         frame.Show(True)
 159         return True
 160 
 161 #---------------------------------------------------------------------------
 162 
 163 def main():
 164     app = MyApp(False)
 165     app.MainLoop()
 166 
 167 #---------------------------------------------------------------------------
 168 
 169 if __name__ == "__main__" :
 170     main()


Download source

source.zip


Additional Information

Link :

https://www.cairographics.org/documentation/

https://www.cairographics.org/samples/

https://www.cairographics.org/cookbook/

https://www.cairographics.org/tutorial/

https://www.cairographics.org/manual/

http://www.tortall.net/mu/wiki/CairoTutorial

https://stackoverflow.com/questions/23661347/drawing-with-cairo-in-wxpython

- - - - -

https://wiki.wxpython.org/TitleIndex

https://docs.wxpython.org/


Thanks to

Robin Dunn (sample_one.py coding), ??? (sample_two.py coding), the wxPython community...


About this page

Date(d/m/y) Person (bot) Comments :

12/01/20 - Ecco (Updated page for wxPython Phoenix).


Comments

- blah, blah, blah....

How to use the Cairo 2D graphics library (Phoenix) (last edited 2020-12-13 13:35:20 by Ecco)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.