How to use the Cairo 2D graphics library (Phoenix)
Keywords : Drawing, Cairo, BufferedPaintDC, Bitmap, Gradient.
Contents
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
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
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
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
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
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
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....