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

Skip to content

Commit 001d435

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 422a15c commit 001d435

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
@@ -23,6 +23,7 @@
2323

2424
import six
2525

26+
import copy
2627
import gzip
2728
import os
2829
import sys
@@ -91,35 +92,41 @@ def buffer_info(self):
9192

9293

9394
def _convert_path(ctx, path, transform, clip=None):
95+
return _convert_paths(ctx, [path], [transform], clip)
96+
97+
98+
def _convert_paths(ctx, paths, transforms, clip=None):
9499
if HAS_CAIRO_CFFI:
95100
try:
96-
return _convert_path_fast(ctx, path, transform, clip)
101+
return _convert_paths_fast(ctx, paths, transforms, clip)
97102
except NotImplementedError:
98103
pass
99-
return _convert_path_slow(ctx, path, transform, clip)
100-
101-
102-
def _convert_path_slow(ctx, path, transform, clip=None):
103-
for points, code in path.iter_segments(transform, clip=clip):
104-
if code == Path.MOVETO:
105-
ctx.move_to(*points)
106-
elif code == Path.CLOSEPOLY:
107-
ctx.close_path()
108-
elif code == Path.LINETO:
109-
ctx.line_to(*points)
110-
elif code == Path.CURVE3:
111-
ctx.curve_to(points[0], points[1],
112-
points[0], points[1],
113-
points[2], points[3])
114-
elif code == Path.CURVE4:
115-
ctx.curve_to(*points)
116-
117-
118-
def _convert_path_fast(ctx, path, transform, clip=None):
104+
return _convert_paths_slow(ctx, paths, transforms, clip)
105+
106+
107+
def _convert_paths_slow(ctx, paths, transforms, clip=None):
108+
for path, transform in zip(paths, transforms):
109+
for points, code in path.iter_segments(transform, clip=clip):
110+
if code == Path.MOVETO:
111+
ctx.move_to(*points)
112+
elif code == Path.CLOSEPOLY:
113+
ctx.close_path()
114+
elif code == Path.LINETO:
115+
ctx.line_to(*points)
116+
elif code == Path.CURVE3:
117+
ctx.curve_to(points[0], points[1],
118+
points[0], points[1],
119+
points[2], points[3])
120+
elif code == Path.CURVE4:
121+
ctx.curve_to(*points)
122+
123+
124+
def _convert_paths_fast(ctx, paths, transforms, clip=None):
119125
ffi = cairo.ffi
120-
cleaned = path.cleaned(transform=transform, clip=clip)
121-
vertices = cleaned.vertices
122-
codes = cleaned.codes
126+
cleaneds = [path.cleaned(transform=transform, clip=clip)
127+
for path, transform in zip(paths, transforms)]
128+
vertices = np.concatenate([cleaned.vertices for cleaned in cleaneds])
129+
codes = np.concatenate([cleaned.codes for cleaned in cleaneds])
123130

124131
# TODO: Implement Bezier degree elevation formula. Note that the "slow"
125132
# implementation is, in fact, also incorrect...
@@ -128,10 +135,8 @@ def _convert_path_fast(ctx, path, transform, clip=None):
128135
# Remove unused vertices and convert to cairo codes. Note that unlike
129136
# cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after
130137
# CLOSE_PATH, so our resulting buffer may be smaller.
131-
if codes[-1] == Path.STOP:
132-
codes = codes[:-1]
133-
vertices = vertices[:-1]
134-
vertices = vertices[codes != Path.CLOSEPOLY]
138+
vertices = vertices[(codes != Path.STOP) & (codes != Path.CLOSEPOLY)]
139+
codes = codes[codes != Path.STOP]
135140
codes = _MPL_TO_CAIRO_PATH_TYPE[codes]
136141
# Where are the headers of each cairo portions?
137142
cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES[codes]
@@ -277,6 +282,54 @@ def draw_markers(
277282
self._fill_and_stroke(
278283
ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
279284

285+
def draw_path_collection(
286+
self, gc, master_transform, paths, all_transforms, offsets,
287+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
288+
antialiaseds, urls, offset_position):
289+
290+
path_ids = []
291+
for path, transform in self._iter_collection_raw_paths(
292+
master_transform, paths, all_transforms):
293+
path_ids.append((path, Affine2D(transform)))
294+
295+
reuse_key = None
296+
grouped_draw = []
297+
298+
def _draw_paths():
299+
if not grouped_draw:
300+
return
301+
gc_vars, rgb_fc = reuse_key
302+
gc = copy.copy(gc0)
303+
vars(gc).update(gc_vars)
304+
for k, v in gc_vars.items():
305+
try:
306+
getattr(gc, "set" + k)(v)
307+
except (AttributeError, TypeError):
308+
pass
309+
gc.ctx.new_path()
310+
paths, transforms = zip(*grouped_draw)
311+
grouped_draw.clear()
312+
_convert_paths(gc.ctx, paths, transforms)
313+
self._fill_and_stroke(
314+
gc.ctx, rgb_fc, gc.get_alpha(), gc.get_forced_alpha())
315+
316+
for xo, yo, path_id, gc0, rgb_fc in self._iter_collection(
317+
gc, master_transform, all_transforms, path_ids, offsets,
318+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
319+
antialiaseds, urls, offset_position):
320+
path, transform = path_id
321+
transform = (Affine2D(transform.get_matrix()).translate(xo, yo)
322+
+ Affine2D().scale(1, -1).translate(0, self.height))
323+
# rgb_fc could be a ndarray, for which equality is elementwise.
324+
new_key = vars(gc0), tuple(rgb_fc) if rgb_fc is not None else None
325+
if new_key == reuse_key:
326+
grouped_draw.append((path, transform))
327+
else:
328+
_draw_paths()
329+
grouped_draw.append((path, transform))
330+
reuse_key = new_key
331+
_draw_paths()
332+
280333
def draw_image(self, gc, x, y, im):
281334
# bbox - not currently used
282335
if sys.byteorder == 'little':

0 commit comments

Comments
 (0)