@@ -79,6 +79,87 @@ def buffer_info(self):
79
79
return (self .__data , self .__size )
80
80
81
81
82
+ # Mapping from Matplotlib Path codes to cairo path codes.
83
+ _MPL_TO_CAIRO_PATH_TYPE = np .zeros (80 , dtype = int ) # CLOSEPOLY = 79.
84
+ _MPL_TO_CAIRO_PATH_TYPE [Path .MOVETO ] = cairo .PATH_MOVE_TO
85
+ _MPL_TO_CAIRO_PATH_TYPE [Path .LINETO ] = cairo .PATH_LINE_TO
86
+ _MPL_TO_CAIRO_PATH_TYPE [Path .CURVE4 ] = cairo .PATH_CURVE_TO
87
+ _MPL_TO_CAIRO_PATH_TYPE [Path .CLOSEPOLY ] = cairo .PATH_CLOSE_PATH
88
+ # Sizes in cairo_path_data_t of each cairo path element.
89
+ _CAIRO_PATH_TYPE_SIZES = np .zeros (4 , dtype = int )
90
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_MOVE_TO ] = 2
91
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_LINE_TO ] = 2
92
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_CURVE_TO ] = 4
93
+ _CAIRO_PATH_TYPE_SIZES [cairo .PATH_CLOSE_PATH ] = 1
94
+
95
+
96
+ def _convert_path (ctx , path , transform , clip = None ):
97
+ if HAS_CAIRO_CFFI :
98
+ try :
99
+ return _convert_path_fast (ctx , path , transform , clip )
100
+ except NotImplementedError :
101
+ pass
102
+ return _convert_path_slow (ctx , path , transform , clip )
103
+
104
+
105
+ def _convert_path_slow (ctx , path , transform , clip = None ):
106
+ for points , code in path .iter_segments (transform , clip = clip ):
107
+ if code == Path .MOVETO :
108
+ ctx .move_to (* points )
109
+ elif code == Path .CLOSEPOLY :
110
+ ctx .close_path ()
111
+ elif code == Path .LINETO :
112
+ ctx .line_to (* points )
113
+ elif code == Path .CURVE3 :
114
+ ctx .curve_to (points [0 ], points [1 ],
115
+ points [0 ], points [1 ],
116
+ points [2 ], points [3 ])
117
+ elif code == Path .CURVE4 :
118
+ ctx .curve_to (* points )
119
+
120
+
121
+ def _convert_path_fast (ctx , path , transform , clip = None ):
122
+ ffi = cairo .ffi
123
+ cleaned = path .cleaned (transform = transform , clip = clip )
124
+ vertices = cleaned .vertices
125
+ codes = cleaned .codes
126
+
127
+ # TODO: Implement Bezier degree elevation formula. Note that the "slow"
128
+ # implementation is, in fact, also incorrect...
129
+ if np .any (codes == Path .CURVE3 ):
130
+ raise NotImplementedError ("Quadratic Bezier curves are not supported" )
131
+ # Remove unused vertices and convert to cairo codes. Note that unlike
132
+ # cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after
133
+ # CLOSE_PATH, so our resulting buffer may be smaller.
134
+ if codes [- 1 ] == Path .STOP :
135
+ codes = codes [:- 1 ]
136
+ vertices = vertices [:- 1 ]
137
+ vertices = vertices [codes != Path .CLOSEPOLY ]
138
+ codes = _MPL_TO_CAIRO_PATH_TYPE [codes ]
139
+ # Where are the headers of each cairo portions?
140
+ cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES [codes ]
141
+ cairo_type_positions = np .insert (np .cumsum (cairo_type_sizes ), 0 , 0 )
142
+ cairo_num_data = cairo_type_positions [- 1 ]
143
+ cairo_type_positions = cairo_type_positions [:- 1 ]
144
+
145
+ # Fill the buffer.
146
+ buf = np .empty (cairo_num_data * 16 , np .uint8 )
147
+ as_int = np .frombuffer (buf .data , np .int32 )
148
+ as_float = np .frombuffer (buf .data , np .float64 )
149
+ mask = np .ones_like (as_float , bool )
150
+ as_int [::4 ][cairo_type_positions ] = codes
151
+ as_int [1 ::4 ][cairo_type_positions ] = cairo_type_sizes
152
+ mask [::2 ][cairo_type_positions ] = mask [1 ::2 ][cairo_type_positions ] = False
153
+ as_float [mask ] = vertices .ravel ()
154
+
155
+ # Construct the cairo_path_t, and pass it to the context.
156
+ ptr = ffi .new ("cairo_path_t *" )
157
+ ptr .status = cairo .STATUS_SUCCESS
158
+ ptr .data = ffi .cast ("cairo_path_data_t *" , ffi .from_buffer (buf ))
159
+ ptr .num_data = cairo_num_data
160
+ cairo .cairo .cairo_append_path (ctx ._pointer , ptr )
161
+
162
+
82
163
class RendererCairo (RendererBase ):
83
164
fontweights = {
84
165
100 : cairo .FONT_WEIGHT_NORMAL ,
@@ -138,38 +219,16 @@ def _fill_and_stroke(self, ctx, fill_c, alpha, alpha_overrides):
138
219
ctx .restore ()
139
220
ctx .stroke ()
140
221
141
- @staticmethod
142
- def convert_path (ctx , path , transform , clip = None ):
143
- for points , code in path .iter_segments (transform , clip = clip ):
144
- if code == Path .MOVETO :
145
- ctx .move_to (* points )
146
- elif code == Path .CLOSEPOLY :
147
- ctx .close_path ()
148
- elif code == Path .LINETO :
149
- ctx .line_to (* points )
150
- elif code == Path .CURVE3 :
151
- ctx .curve_to (points [0 ], points [1 ],
152
- points [0 ], points [1 ],
153
- points [2 ], points [3 ])
154
- elif code == Path .CURVE4 :
155
- ctx .curve_to (* points )
156
-
157
222
def draw_path (self , gc , path , transform , rgbFace = None ):
158
223
ctx = gc .ctx
159
-
160
- # We'll clip the path to the actual rendering extents
161
- # if the path isn't filled.
162
- if rgbFace is None and gc .get_hatch () is None :
163
- clip = ctx .clip_extents ()
164
- else :
165
- clip = None
166
-
224
+ # Clip the path to the actual rendering extents if it isn't filled.
225
+ clip = (ctx .clip_extents ()
226
+ if rgbFace is None and gc .get_hatch () is None
227
+ else None )
167
228
transform = (transform
168
- + Affine2D ().scale (1.0 , - 1.0 ).translate (0 , self .height ))
169
-
229
+ + Affine2D ().scale (1 , - 1 ).translate (0 , self .height ))
170
230
ctx .new_path ()
171
- self .convert_path (ctx , path , transform , clip )
172
-
231
+ _convert_path (ctx , path , transform , clip )
173
232
self ._fill_and_stroke (
174
233
ctx , rgbFace , gc .get_alpha (), gc .get_forced_alpha ())
175
234
@@ -179,8 +238,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform,
179
238
180
239
ctx .new_path ()
181
240
# Create the path for the marker; it needs to be flipped here already!
182
- self . convert_path (
183
- ctx , marker_path , marker_trans + Affine2D ().scale (1.0 , - 1.0 ))
241
+ _convert_path (
242
+ ctx , marker_path , marker_trans + Affine2D ().scale (1 , - 1 ))
184
243
marker_path = ctx .copy_path_flat ()
185
244
186
245
# Figure out whether the path has a fill
@@ -193,7 +252,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform,
193
252
filled = True
194
253
195
254
transform = (transform
196
- + Affine2D ().scale (1.0 , - 1.0 ).translate (0 , self .height ))
255
+ + Affine2D ().scale (1 , - 1 ).translate (0 , self .height ))
197
256
198
257
ctx .new_path ()
199
258
for i , (vertices , codes ) in enumerate (
@@ -247,7 +306,7 @@ def draw_image(self, gc, x, y, im):
247
306
248
307
ctx .save ()
249
308
ctx .set_source_surface (surface , float (x ), float (y ))
250
- if gc .get_alpha () != 1.0 :
309
+ if gc .get_alpha () != 1 :
251
310
ctx .paint_with_alpha (gc .get_alpha ())
252
311
else :
253
312
ctx .paint ()
@@ -413,7 +472,7 @@ def set_clip_path(self, path):
413
472
ctx .new_path ()
414
473
affine = (affine
415
474
+ Affine2D ().scale (1 , - 1 ).translate (0 , self .renderer .height ))
416
- RendererCairo . convert_path (ctx , tpath , affine )
475
+ _convert_path (ctx , tpath , affine )
417
476
ctx .clip ()
418
477
419
478
def set_dashes (self , offset , dashes ):
0 commit comments