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