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

Skip to content

Commit 52ed746

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 8a07eef commit 52ed746

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
@@ -9,6 +9,7 @@
99

1010
import six
1111

12+
import copy
1213
import gzip
1314
import sys
1415
import warnings
@@ -94,35 +95,41 @@ def buffer_info(self):
9495

9596

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

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

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

0 commit comments

Comments
 (0)