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

Skip to content

Commit 9fe4a0d

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 a8d1dcb commit 9fe4a0d

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 os, sys, warnings, gzip
2728

2829
import numpy as np
@@ -93,35 +94,41 @@ def buffer_info(self):
9394

9495

9596
def _convert_path(ctx, path, transform, clip=None):
97+
return _convert_paths(ctx, [path], [transform], clip)
98+
99+
100+
def _convert_paths(ctx, paths, transforms, clip=None):
96101
if HAS_CAIRO_CFFI:
97102
try:
98-
return _convert_path_fast(ctx, path, transform, clip)
103+
return _convert_paths_fast(ctx, paths, transforms, clip)
99104
except NotImplementedError:
100105
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):
106+
return _convert_paths_slow(ctx, paths, transforms, clip)
107+
108+
109+
def _convert_paths_slow(ctx, paths, transforms, clip=None):
110+
for path, transform in zip(paths, transforms):
111+
for points, code in path.iter_segments(transform, clip=clip):
112+
if code == Path.MOVETO:
113+
ctx.move_to(*points)
114+
elif code == Path.CLOSEPOLY:
115+
ctx.close_path()
116+
elif code == Path.LINETO:
117+
ctx.line_to(*points)
118+
elif code == Path.CURVE3:
119+
ctx.curve_to(points[0], points[1],
120+
points[0], points[1],
121+
points[2], points[3])
122+
elif code == Path.CURVE4:
123+
ctx.curve_to(*points)
124+
125+
126+
def _convert_paths_fast(ctx, paths, transforms, clip=None):
121127
ffi = cairo.ffi
122-
cleaned = path.cleaned(transform=transform, clip=clip)
123-
vertices = cleaned.vertices
124-
codes = cleaned.codes
128+
cleaneds = [path.cleaned(transform=transform, clip=clip)
129+
for path, transform in zip(paths, transforms)]
130+
vertices = np.concatenate([cleaned.vertices for cleaned in cleaneds])
131+
codes = np.concatenate([cleaned.codes for cleaned in cleaneds])
125132

126133
# TODO: Implement Bezier degree elevation formula. Note that the "slow"
127134
# implementation is, in fact, also incorrect...
@@ -130,10 +137,8 @@ def _convert_path_fast(ctx, path, transform, clip=None):
130137
# Remove unused vertices and convert to cairo codes. Note that unlike
131138
# cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after
132139
# 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]
140+
vertices = vertices[(codes != Path.STOP) & (codes != Path.CLOSEPOLY)]
141+
codes = codes[codes != Path.STOP]
137142
codes = _MPL_TO_CAIRO_PATH_TYPE[codes]
138143
# Where are the headers of each cairo portions?
139144
cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES[codes]
@@ -278,6 +283,54 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, rgbFace=N
278283
if not filled:
279284
self._fill_and_stroke(ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
280285

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

0 commit comments

Comments
 (0)