Skip to content

Commit 889c73f

Browse files
authored
Merge pull request #17605 from tacaswell/auto-backport-of-pr-17560-on-v3.2.x
Backport PR #17560: FIX: do not let no-op monkey patches to renderer …
2 parents 29145cf + 5a91618 commit 889c73f

File tree

3 files changed

+61
-20
lines changed

3 files changed

+61
-20
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
The base class for the messaging area.
3333
"""
3434

35-
from contextlib import contextmanager
35+
from contextlib import contextmanager, suppress
3636
from enum import IntEnum
3737
import functools
3838
import importlib
@@ -52,6 +52,7 @@
5252
from matplotlib._pylab_helpers import Gcf
5353
from matplotlib.transforms import Affine2D
5454
from matplotlib.path import Path
55+
from matplotlib.cbook import _setattr_cm
5556

5657
try:
5758
from PIL import __version__ as PILLOW_VERSION
@@ -712,6 +713,23 @@ def stop_filter(self, filter_func):
712713
Currently only supported by the agg renderer.
713714
"""
714715

716+
def _draw_disabled(self):
717+
"""
718+
Context manager to temporary disable drawing.
719+
720+
This is used for getting the drawn size of Artists. This lets us
721+
run the draw process to update any Python state but does not pay the
722+
cost of the draw_XYZ calls on the canvas.
723+
"""
724+
no_ops = {
725+
meth_name: lambda *args, **kwargs: None
726+
for meth_name in dir(RendererBase)
727+
if (meth_name.startswith("draw_")
728+
or meth_name in ["open_group", "close_group"])
729+
}
730+
731+
return _setattr_cm(self, **no_ops)
732+
715733

716734
class GraphicsContextBase:
717735
"""An abstract base class that provides color, line styles, etc."""
@@ -1520,15 +1538,14 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
15201538
LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
15211539

15221540

1523-
def _get_renderer(figure, print_method, *, draw_disabled=False):
1541+
def _get_renderer(figure, print_method):
15241542
"""
15251543
Get the renderer that would be used to save a `~.Figure`, and cache it on
15261544
the figure.
15271545
1528-
If *draw_disabled* is True, additionally replace drawing methods on
1529-
*renderer* by no-ops. This is used by the tight-bbox-saving renderer,
1530-
which needs to walk through the artist tree to compute the tight-bbox, but
1531-
for which the output file may be closed early.
1546+
If you need a renderer without any active draw methods use
1547+
renderer._draw_disabled to temporary patch them out at your call site.
1548+
15321549
"""
15331550
# This is implemented by triggering a draw, then immediately jumping out of
15341551
# Figure.draw() by raising an exception.
@@ -1544,12 +1561,6 @@ def _draw(renderer): raise Done(renderer)
15441561
except Done as exc:
15451562
renderer, = figure._cachedRenderer, = exc.args
15461563

1547-
if draw_disabled:
1548-
for meth_name in dir(RendererBase):
1549-
if (meth_name.startswith("draw_")
1550-
or meth_name in ["open_group", "close_group"]):
1551-
setattr(renderer, meth_name, lambda *args, **kwargs: None)
1552-
15531564
return renderer
15541565

15551566

@@ -2079,9 +2090,13 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
20792090
renderer = _get_renderer(
20802091
self.figure,
20812092
functools.partial(
2082-
print_method, dpi=dpi, orientation=orientation),
2083-
draw_disabled=True)
2084-
self.figure.draw(renderer)
2093+
print_method, dpi=dpi, orientation=orientation)
2094+
)
2095+
ctx = (renderer._draw_disabled()
2096+
if hasattr(renderer, '_draw_disabled')
2097+
else suppress())
2098+
with ctx:
2099+
self.figure.draw(renderer)
20852100
bbox_artists = kwargs.pop("bbox_extra_artists", None)
20862101
bbox_inches = self.figure.get_tightbbox(renderer,
20872102
bbox_extra_artists=bbox_artists)

lib/matplotlib/figure.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2478,7 +2478,7 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
24782478

24792479
from .tight_layout import (
24802480
get_renderer, get_subplotspec_list, get_tight_layout_figure)
2481-
2481+
from contextlib import suppress
24822482
subplotspec_list = get_subplotspec_list(self.axes)
24832483
if None in subplotspec_list:
24842484
cbook._warn_external("This figure includes Axes that are not "
@@ -2487,10 +2487,13 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
24872487

24882488
if renderer is None:
24892489
renderer = get_renderer(self)
2490-
2491-
kwargs = get_tight_layout_figure(
2492-
self, self.axes, subplotspec_list, renderer,
2493-
pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
2490+
ctx = (renderer._draw_disabled()
2491+
if hasattr(renderer, '_draw_disabled')
2492+
else suppress())
2493+
with ctx:
2494+
kwargs = get_tight_layout_figure(
2495+
self, self.axes, subplotspec_list, renderer,
2496+
pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
24942497
if kwargs:
24952498
self.subplots_adjust(**kwargs)
24962499

lib/matplotlib/tests/test_bbox_tight.py

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

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