diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index c55864243a75..6d862f4d3504 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3026,6 +3026,15 @@ def __getstate__(self): # add version information to the state state['__mpl_version__'] = mpl.__version__ + # Ensure DPI stored in pickle is the unscaled, original DPI. + # When a HiDPI backend (e.g., macOSX) sets a device pixel ratio, the + # runtime Figure.dpi is scaled from the original value. If we persisted + # the scaled dpi here, unpickling and recreating the canvas would set + # _original_dpi from an already-scaled value and the backend would + # scale it again, effectively doubling the dpi on each cycle. + # Persisting the original dpi avoids cumulative scaling. + state["_dpi"] = getattr(self, "_original_dpi", self._dpi) + # check whether the figure manager (if any) is registered with pyplot from matplotlib import _pylab_helpers if self.canvas.manager in _pylab_helpers.Gcf.figs.values(): diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 0d0a5e597767..b7c44c32936c 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -237,3 +237,27 @@ def test_dynamic_norm(): def test_vertexselector(): line, = plt.plot([0, 1], picker=True) pickle.loads(pickle.dumps(VertexSelector(line))) + + +def test_hidpi_pickle_unpickle_does_not_double_dpi(): + # Emulate a HiDPI backend by setting device pixel ratio to 2. Verify that + # pickling and unpickling the figure does not cause the dpi to double on + # subsequent device pixel ratio application. + fig = plt.figure(dpi=100) + # Emulate HiDPI scaling applied by backend. + fig.canvas._set_device_pixel_ratio(2) + assert fig.dpi == 200 + + # First unpickle cycle should not change effective dpi when DPR=2 is + # applied again by the backend. + pf = pickle.dumps(fig, pickle.HIGHEST_PROTOCOL) + fig2 = pickle.loads(pf) + # Emulate backend reapplying DPR on new canvas. + fig2.canvas._set_device_pixel_ratio(2) + assert fig2.dpi == 200 + + # Repeated cycles should remain stable. + pf2 = pickle.dumps(fig2, pickle.HIGHEST_PROTOCOL) + fig3 = pickle.loads(pf2) + fig3.canvas._set_device_pixel_ratio(2) + assert fig3.dpi == 200