Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b7f834e

Browse files
committed
Implement draw_path_collection for cairo.
Further increase the performance of mplot3d/wire3d_animation on the gtk3cairo backend from ~10.5fps to ~11.6fps (as a comparison, gtk3agg is at ~16.2fps).
1 parent c78badd commit b7f834e

File tree

1 file changed

+81
-28
lines changed

1 file changed

+81
-28
lines changed

lib/matplotlib/backends/backend_cairo.py

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import six
1414

15+
import copy
1516
import gzip
1617
import sys
1718
import warnings
@@ -77,35 +78,41 @@ def buffer_info(self):
7778

7879

7980
def _convert_path(ctx, path, transform, clip=None):
81+
return _convert_paths(ctx, [path], [transform], clip)
82+
83+
84+
def _convert_paths(ctx, paths, transforms, clip=None):
8085
if HAS_CAIRO_CFFI:
8186
try:
82-
return _convert_path_fast(ctx, path, transform, clip)
87+
return _convert_paths_fast(ctx, paths, transforms, clip)
8388
except NotImplementedError:
8489
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):
90+
return _convert_paths_slow(ctx, paths, transforms, clip)
91+
92+
93+
def _convert_paths_slow(ctx, paths, transforms, clip=None):
94+
for path, transform in zip(paths, transforms):
95+
for points, code in path.iter_segments(transform, clip=clip):
96+
if code == Path.MOVETO:
97+
ctx.move_to(*points)
98+
elif code == Path.CLOSEPOLY:
99+
ctx.close_path()
100+
elif code == Path.LINETO:
101+
ctx.line_to(*points)
102+
elif code == Path.CURVE3:
103+
ctx.curve_to(points[0], points[1],
104+
points[0], points[1],
105+
points[2], points[3])
106+
elif code == Path.CURVE4:
107+
ctx.curve_to(*points)
108+
109+
110+
def _convert_paths_fast(ctx, paths, transforms, clip=None):
105111
ffi = cairo.ffi
106-
cleaned = path.cleaned(transform=transform, clip=clip)
107-
vertices = cleaned.vertices
108-
codes = cleaned.codes
112+
cleaneds = [path.cleaned(transform=transform, clip=clip)
113+
for path, transform in zip(paths, transforms)]
114+
vertices = np.concatenate([cleaned.vertices for cleaned in cleaneds])
115+
codes = np.concatenate([cleaned.codes for cleaned in cleaneds])
109116

110117
# TODO: Implement Bezier degree elevation formula. Note that the "slow"
111118
# implementation is, in fact, also incorrect...
@@ -114,10 +121,8 @@ def _convert_path_fast(ctx, path, transform, clip=None):
114121
# Remove unused vertices and convert to cairo codes. Note that unlike
115122
# cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after
116123
# 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]
124+
vertices = vertices[(codes != Path.STOP) & (codes != Path.CLOSEPOLY)]
125+
codes = codes[codes != Path.STOP]
121126
codes = _MPL_TO_CAIRO_PATH_TYPE[codes]
122127
# Where are the headers of each cairo portions?
123128
cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES[codes]
@@ -263,6 +268,54 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform,
263268
self._fill_and_stroke(
264269
ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
265270

271+
def draw_path_collection(
272+
self, gc, master_transform, paths, all_transforms, offsets,
273+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
274+
antialiaseds, urls, offset_position):
275+
276+
path_ids = []
277+
for path, transform in self._iter_collection_raw_paths(
278+
master_transform, paths, all_transforms):
279+
path_ids.append((path, Affine2D(transform)))
280+
281+
reuse_key = None
282+
grouped_draw = []
283+
284+
def _draw_paths():
285+
if not grouped_draw:
286+
return
287+
gc_vars, rgb_fc = reuse_key
288+
gc = copy.copy(gc0)
289+
vars(gc).update(gc_vars)
290+
for k, v in gc_vars.items():
291+
try:
292+
getattr(gc, "set" + k)(v)
293+
except (AttributeError, TypeError):
294+
pass
295+
gc.ctx.new_path()
296+
paths, transforms = zip(*grouped_draw)
297+
grouped_draw.clear()
298+
_convert_paths(gc.ctx, paths, transforms)
299+
self._fill_and_stroke(
300+
gc.ctx, rgb_fc, gc.get_alpha(), gc.get_forced_alpha())
301+
302+
for xo, yo, path_id, gc0, rgb_fc in self._iter_collection(
303+
gc, master_transform, all_transforms, path_ids, offsets,
304+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
305+
antialiaseds, urls, offset_position):
306+
path, transform = path_id
307+
transform = (Affine2D(transform.get_matrix()).translate(xo, yo)
308+
+ Affine2D().scale(1, -1).translate(0, self.height))
309+
# rgb_fc could be a ndarray, for which equality is elementwise.
310+
new_key = vars(gc0), tuple(rgb_fc) if rgb_fc is not None else None
311+
if new_key == reuse_key:
312+
grouped_draw.append((path, transform))
313+
else:
314+
_draw_paths()
315+
grouped_draw.append((path, transform))
316+
reuse_key = new_key
317+
_draw_paths()
318+
266319
def draw_image(self, gc, x, y, im):
267320
# bbox - not currently used
268321
if sys.byteorder == 'little':

0 commit comments

Comments
 (0)