@@ -62,6 +62,87 @@ def buffer_info(self):
62
62
return (self .__data , self .__size )
63
63
64
64
65
+ # Mapping from Matplotlib Path codes to cairo path codes.
66
+ _MPL_TO_CAIRO_PATH_TYPE = np .zeros (80 , dtype = int ) # CLOSEPOLY = 79.
67
+ _MPL_TO_CAIRO_PATH_TYPE [Path .MOVETO ] = cairo .PATH_MOVE_TO
68
+ _MPL_TO_CAIRO_PATH_TYPE [Path .LINETO ] = cairo .PATH_LINE_TO
69
+ _MPL_TO_CAIRO_PATH_TYPE [Path .CURVE4 ] = cairo .PATH_CURVE_TO
70
+ _MPL_TO_CAIRO_PATH_TYPE [Path .CLOSEPOLY ] = cairo .PATH_CLOSE_PATH
71
+ # Sizes in cairo_path_data_t of each cairo path element.
72
+ _CAIRO_PATH_TYPE_SIZES = np .zeros (4 , dtype = int )
73
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_MOVE_TO ] = 2
74
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_LINE_TO ] = 2
75
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_CURVE_TO ] = 4
76
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_CLOSE_PATH ] = 1
77
+
78
+
79
+ def _convert_path (ctx , path , transform , clip = None ):
80
+ if HAS_CAIRO_CFFI :
81
+ try :
82
+ return _convert_path_fast (ctx , path , transform , clip )
83
+ except NotImplementedError :
84
+ pass
85
+ return _convert_path_slow (ctx , path , transform , clip )
86
+
87
+
88
+ def _convert_path_slow (ctx , path , transform , clip = None ):
89
+ for points , code in path .iter_segments (transform , clip = clip ):
90
+ if code == Path .MOVETO :
91
+ ctx .move_to (* points )
92
+ elif code == Path .CLOSEPOLY :
93
+ ctx .close_path ()
94
+ elif code == Path .LINETO :
95
+ ctx .line_to (* points )
96
+ elif code == Path .CURVE3 :
97
+ ctx .curve_to (points [0 ], points [1 ],
98
+ points [0 ], points [1 ],
99
+ points [2 ], points [3 ])
100
+ elif code == Path .CURVE4 :
101
+ ctx .curve_to (* points )
102
+
103
+
104
+ def _convert_path_fast (ctx , path , transform , clip = None ):
105
+ ffi = cairo .ffi
106
+ cleaned = path .cleaned (transform = transform , clip = clip )
107
+ vertices = cleaned .vertices
108
+ codes = cleaned .codes
109
+
110
+ # TODO: Implement Bezier degree elevation formula. Note that the "slow"
111
+ # implementation is, in fact, also incorrect...
112
+ if np .any (codes == Path .CURVE3 ):
113
+ raise NotImplementedError ("Quadratic Bezier curves are not supported" )
114
+ # Remove unused vertices and convert to cairo codes. Note that unlike
115
+ # cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after
116
+ # CLOSE_PATH, so our resulting buffer may be smaller.
117
+ if codes [- 1 ] == Path .STOP :
118
+ codes = codes [:- 1 ]
119
+ vertices = vertices [:- 1 ]
120
+ vertices = vertices [codes != Path .CLOSEPOLY ]
121
+ codes = _MPL_TO_CAIRO_PATH_TYPE [codes ]
122
+ # Where are the headers of each cairo portions?
123
+ cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES [codes ]
124
+ cairo_type_positions = np .insert (np .cumsum (cairo_type_sizes ), 0 , 0 )
125
+ cairo_num_data = cairo_type_positions [- 1 ]
126
+ cairo_type_positions = cairo_type_positions [:- 1 ]
127
+
128
+ # Fill the buffer.
129
+ buf = np .empty (cairo_num_data * 16 , np .uint8 )
130
+ as_int = np .frombuffer (buf .data , np .int32 )
131
+ as_float = np .frombuffer (buf .data , np .float64 )
132
+ mask = np .ones_like (as_float , bool )
133
+ as_int [::4 ][cairo_type_positions ] = codes
134
+ as_int [1 ::4 ][cairo_type_positions ] = cairo_type_sizes
135
+ mask [::2 ][cairo_type_positions ] = mask [1 ::2 ][cairo_type_positions ] = False
136
+ as_float [mask ] = vertices .ravel ()
137
+
138
+ # Construct the cairo_path_t, and pass it to the context.
139
+ ptr = ffi .new ("cairo_path_t *" )
140
+ ptr .status = cairo .STATUS_SUCCESS
141
+ ptr .data = ffi .cast ("cairo_path_data_t *" , ffi .from_buffer (buf ))
142
+ ptr .num_data = cairo_num_data
143
+ cairo .cairo .cairo_append_path (ctx ._pointer , ptr )
144
+
145
+
65
146
class RendererCairo (RendererBase ):
66
147
fontweights = {
67
148
100 : cairo .FONT_WEIGHT_NORMAL ,
@@ -121,38 +202,16 @@ def _fill_and_stroke(self, ctx, fill_c, alpha, alpha_overrides):
121
202
ctx .restore ()
122
203
ctx .stroke ()
123
204
124
- @staticmethod
125
- def convert_path (ctx , path , transform , clip = None ):
126
- for points , code in path .iter_segments (transform , clip = clip ):
127
- if code == Path .MOVETO :
128
- ctx .move_to (* points )
129
- elif code == Path .CLOSEPOLY :
130
- ctx .close_path ()
131
- elif code == Path .LINETO :
132
- ctx .line_to (* points )
133
- elif code == Path .CURVE3 :
134
- ctx .curve_to (points [0 ], points [1 ],
135
- points [0 ], points [1 ],
136
- points [2 ], points [3 ])
137
- elif code == Path .CURVE4 :
138
- ctx .curve_to (* points )
139
-
140
205
def draw_path (self , gc , path , transform , rgbFace = None ):
141
206
ctx = gc .ctx
142
-
143
- # We'll clip the path to the actual rendering extents
144
- # if the path isn't filled.
145
- if rgbFace is None and gc .get_hatch () is None :
146
- clip = ctx .clip_extents ()
147
- else :
148
- clip = None
149
-
207
+ # Clip the path to the actual rendering extents if it isn't filled.
208
+ clip = (ctx .clip_extents ()
209
+ if rgbFace is None and gc .get_hatch () is None
210
+ else None )
150
211
transform = (transform
151
- + Affine2D ().scale (1.0 , - 1.0 ).translate (0 , self .height ))
152
-
212
+ + Affine2D ().scale (1 , - 1 ).translate (0 , self .height ))
153
213
ctx .new_path ()
154
- self .convert_path (ctx , path , transform , clip )
155
-
214
+ _convert_path (ctx , path , transform , clip )
156
215
self ._fill_and_stroke (
157
216
ctx , rgbFace , gc .get_alpha (), gc .get_forced_alpha ())
158
217
@@ -162,8 +221,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform,
162
221
163
222
ctx .new_path ()
164
223
# Create the path for the marker; it needs to be flipped here already!
165
- self . convert_path (
166
- ctx , marker_path , marker_trans + Affine2D ().scale (1.0 , - 1.0 ))
224
+ _convert_path (
225
+ ctx , marker_path , marker_trans + Affine2D ().scale (1 , - 1 ))
167
226
marker_path = ctx .copy_path_flat ()
168
227
169
228
# Figure out whether the path has a fill
@@ -176,7 +235,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform,
176
235
filled = True
177
236
178
237
transform = (transform
179
- + Affine2D ().scale (1.0 , - 1.0 ).translate (0 , self .height ))
238
+ + Affine2D ().scale (1 , - 1 ).translate (0 , self .height ))
180
239
181
240
ctx .new_path ()
182
241
for i , (vertices , codes ) in enumerate (
@@ -230,7 +289,7 @@ def draw_image(self, gc, x, y, im):
230
289
231
290
ctx .save ()
232
291
ctx .set_source_surface (surface , float (x ), float (y ))
233
- if gc .get_alpha () != 1.0 :
292
+ if gc .get_alpha () != 1 :
234
293
ctx .paint_with_alpha (gc .get_alpha ())
235
294
else :
236
295
ctx .paint ()
@@ -396,7 +455,7 @@ def set_clip_path(self, path):
396
455
ctx .new_path ()
397
456
affine = (affine
398
457
+ Affine2D ().scale (1 , - 1 ).translate (0 , self .renderer .height ))
399
- RendererCairo . convert_path (ctx , tpath , affine )
458
+ _convert_path (ctx , tpath , affine )
400
459
ctx .clip ()
401
460
402
461
def set_dashes (self , offset , dashes ):
0 commit comments