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

Lines changed: 17 additions & 14 deletions
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

Lines changed: 13 additions & 3 deletions
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

Lines changed: 23 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy