Skip to content

Commit f686001

Browse files
committed
Merge pull request #3826 from mdboom/path_collection_optimization_crash
Don't do draw_marker optimization for large paths
2 parents fed39c8 + 4e42686 commit f686001

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

lib/matplotlib/collections.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,25 +271,41 @@ def draw(self, renderer):
271271
from matplotlib.patheffects import PathEffectRenderer
272272
renderer = PathEffectRenderer(self.get_path_effects(), renderer)
273273

274+
# If the collection is made up of a single shape/color/stroke,
275+
# it can be rendered once and blitted multiple times, using
276+
# `draw_markers` rather than `draw_path_collection`. This is
277+
# *much* faster for Agg, and results in smaller file sizes in
278+
# PDF/SVG/PS.
279+
274280
trans = self.get_transforms()
275281
facecolors = self.get_facecolor()
276282
edgecolors = self.get_edgecolor()
283+
do_single_path_optimization = False
277284
if (len(paths) == 1 and len(trans) <= 1 and
278285
len(facecolors) == 1 and len(edgecolors) == 1 and
279286
len(self._linewidths) == 1 and
280287
self._linestyles == [(None, None)] and
281288
len(self._antialiaseds) == 1 and len(self._urls) == 1 and
282289
self.get_hatch() is None):
290+
if len(trans):
291+
combined_transform = (transforms.Affine2D(trans[0]) +
292+
transform)
293+
else:
294+
combined_transform = transform
295+
extents = paths[0].get_extents(combined_transform)
296+
width, height = renderer.get_canvas_width_height()
297+
if (extents.width < width and
298+
extents.height < height):
299+
do_single_path_optimization = True
300+
301+
if do_single_path_optimization:
283302
gc.set_foreground(tuple(edgecolors[0]))
284303
gc.set_linewidth(self._linewidths[0])
285304
gc.set_linestyle(self._linestyles[0])
286305
gc.set_antialiased(self._antialiaseds[0])
287306
gc.set_url(self._urls[0])
288-
if len(trans):
289-
transform = (transforms.Affine2D(trans[0]) +
290-
transform)
291307
renderer.draw_markers(
292-
gc, paths[0], transform.frozen(),
308+
gc, paths[0], combined_transform.frozen(),
293309
mpath.Path(offsets), transOffset, tuple(facecolors[0]))
294310
else:
295311
renderer.draw_path_collection(

lib/matplotlib/tests/test_agg.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
import io
77
import os
88

9+
import numpy as np
910
from numpy.testing import assert_array_almost_equal
1011

1112
from matplotlib.image import imread
1213
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
1314
from matplotlib.figure import Figure
1415
from matplotlib.testing.decorators import cleanup
16+
from matplotlib import pyplot as plt
17+
from matplotlib import collections
18+
from matplotlib import path
1519

1620

1721
@cleanup
@@ -47,6 +51,21 @@ def test_repeated_save_with_alpha():
4751
decimal=3)
4852

4953

54+
@cleanup
55+
def test_large_single_path_collection():
56+
buff = io.BytesIO()
57+
58+
# Generates a too-large single path in a path collection that
59+
# would cause a segfault if the draw_markers optimization is
60+
# applied.
61+
f, ax = plt.subplots()
62+
collection = collections.PathCollection(
63+
[path.Path([[-10, 5], [10, 5], [10, -5], [-10, -5], [-10, 5]])])
64+
ax.add_artist(collection)
65+
ax.set_xlim(10**-3, 1)
66+
plt.savefig(buff)
67+
68+
5069
def report_memory(i):
5170
pid = os.getpid()
5271
a2 = os.popen('ps -p %d -o rss,sz' % pid).readlines()

0 commit comments

Comments
 (0)