Skip to content

Commit 297cf79

Browse files
committed
FIX: do not let no-op monkey patches to renderer leak out
closes #17542
1 parent 4356b67 commit 297cf79

File tree

4 files changed

+54
-18
lines changed

4 files changed

+54
-18
lines changed

lib/matplotlib/backend_bases.py

+17-14
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from matplotlib.backend_managers import ToolManager
4747
from matplotlib.transforms import Affine2D
4848
from matplotlib.path import Path
49+
from matplotlib.cbook import _setattr_cm
4950

5051

5152
_log = logging.getLogger(__name__)
@@ -1502,15 +1503,14 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
15021503
self.key = key
15031504

15041505

1505-
def _get_renderer(figure, print_method=None, *, draw_disabled=False):
1506+
def _get_renderer(figure, print_method=None):
15061507
"""
15071508
Get the renderer that would be used to save a `~.Figure`, and cache it on
15081509
the figure.
15091510
1510-
If *draw_disabled* is True, additionally replace drawing methods on
1511-
*renderer* by no-ops. This is used by the tight-bbox-saving renderer,
1512-
which needs to walk through the artist tree to compute the tight-bbox, but
1513-
for which the output file may be closed early.
1511+
If you need a renderer without any active draw methods use
1512+
cbook._setattr_cm to temporary patch them out at your call site.
1513+
15141514
"""
15151515
# This is implemented by triggering a draw, then immediately jumping out of
15161516
# Figure.draw() by raising an exception.
@@ -1529,12 +1529,6 @@ def _draw(renderer): raise Done(renderer)
15291529
except Done as exc:
15301530
renderer, = figure._cachedRenderer, = exc.args
15311531

1532-
if draw_disabled:
1533-
for meth_name in dir(RendererBase):
1534-
if (meth_name.startswith("draw_")
1535-
or meth_name in ["open_group", "close_group"]):
1536-
setattr(renderer, meth_name, lambda *args, **kwargs: None)
1537-
15381532
return renderer
15391533

15401534

@@ -2093,9 +2087,18 @@ def print_figure(
20932087
renderer = _get_renderer(
20942088
self.figure,
20952089
functools.partial(
2096-
print_method, orientation=orientation),
2097-
draw_disabled=True)
2098-
self.figure.draw(renderer)
2090+
print_method, orientation=orientation)
2091+
)
2092+
no_ops = {
2093+
meth_name: lambda *args, **kwargs: None
2094+
for meth_name in dir(RendererBase)
2095+
if (meth_name.startswith("draw_")
2096+
or meth_name in ["open_group", "close_group"])
2097+
}
2098+
2099+
with _setattr_cm(renderer, **no_ops):
2100+
self.figure.draw(renderer)
2101+
20992102
bbox_inches = self.figure.get_tightbbox(
21002103
renderer, bbox_extra_artists=bbox_extra_artists)
21012104
if pad_inches is None:

lib/matplotlib/figure.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -2392,6 +2392,8 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
23922392

23932393
from .tight_layout import (
23942394
get_renderer, get_subplotspec_list, get_tight_layout_figure)
2395+
from .cbook import _setattr_cm
2396+
from .backend_bases import RendererBase
23952397

23962398
subplotspec_list = get_subplotspec_list(self.axes)
23972399
if None in subplotspec_list:
@@ -2402,9 +2404,17 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
24022404
if renderer is None:
24032405
renderer = get_renderer(self)
24042406

2405-
kwargs = get_tight_layout_figure(
2406-
self, self.axes, subplotspec_list, renderer,
2407-
pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
2407+
no_ops = {
2408+
meth_name: lambda *args, **kwargs: None
2409+
for meth_name in dir(RendererBase)
2410+
if (meth_name.startswith("draw_")
2411+
or meth_name in ["open_group", "close_group"])
2412+
}
2413+
2414+
with _setattr_cm(renderer, **no_ops):
2415+
kwargs = get_tight_layout_figure(
2416+
self, self.axes, subplotspec_list, renderer,
2417+
pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
24082418
if kwargs:
24092419
self.subplots_adjust(**kwargs)
24102420

lib/matplotlib/tests/test_bbox_tight.py

+23
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,26 @@ def test_tight_pcolorfast():
110110
# Previously, the bbox would include the area of the image clipped out by
111111
# the axes, resulting in a very tall image given the y limits of (0, 0.1).
112112
assert width > height
113+
114+
115+
def test_noop_tight_bbox():
116+
from PIL import Image
117+
x_size, y_size = (10, 7)
118+
dpi = 100
119+
# make the figure just the right size up front
120+
fig = plt.figure(frameon=False, dpi=dpi, figsize=(x_size/dpi, y_size/dpi))
121+
ax = plt.Axes(fig, [0., 0., 1., 1.])
122+
fig.add_axes(ax)
123+
ax.set_axis_off()
124+
ax.get_xaxis().set_visible(False)
125+
ax.get_yaxis().set_visible(False)
126+
127+
data = np.arange(x_size * y_size).reshape(y_size, x_size)
128+
ax.imshow(data)
129+
out = BytesIO()
130+
fig.savefig(out, bbox_inches='tight', pad_inches=0)
131+
out.seek(0)
132+
im = np.asarray(Image.open(out))
133+
assert (im[:, :, 3] == 255).all()
134+
assert not (im[:, :, :3] == 255).all()
135+
assert im.shape == (7, 10, 4)

lib/matplotlib/tight_layout.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def get_renderer(fig):
173173
return canvas.get_renderer()
174174
else:
175175
from . import backend_bases
176-
return backend_bases._get_renderer(fig, draw_disabled=True)
176+
return backend_bases._get_renderer(fig)
177177

178178

179179
def get_subplotspec_list(axes_list, grid_spec=None):

0 commit comments

Comments
 (0)