diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 88b1e8e7c17f..a6d851b3d138 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -119,6 +119,12 @@ original location: * Removed the class `FigureManagerQTAgg` and deprecated `NavigationToolbar2QTAgg` which will be removed in 1.5. +* The function signatures of `tight_bbox.adjust_bbox` and + `tight_bbox.process_figure_for_rasterizing` have been changed. A new + `fixed_dpi` parameter allows for overriding the `figure.dpi` setting + instead of trying to deduce the intended behaviour from the file format. + + .. _changes_in_1_3: diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index c9212939b1a7..c7a10904f37e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1625,6 +1625,7 @@ class FigureCanvasBase(object): ] supports_blit = True + fixed_dpi = None filetypes = _default_filetypes if _has_pil: @@ -2171,8 +2172,8 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', bbox_inches = bbox_inches.padded(pad) - restore_bbox = tight_bbox.adjust_bbox(self.figure, format, - bbox_inches) + restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, + canvas.fixed_dpi) _bbox_inches_restore = (bbox_inches, restore_bbox) else: diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 3a439c330f66..85b3c42c5368 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -93,12 +93,9 @@ def start_rasterizing(self): if self._bbox_inches_restore: # when tight bbox is used r = process_figure_for_rasterizing(self.figure, - self._bbox_inches_restore, - mode="png") - + self._bbox_inches_restore) self._bbox_inches_restore = r - if self._rasterizing == 0: self._raster_renderer = self._raster_renderer_class( self._width*self.dpi, self._height*self.dpi, self.dpi) @@ -143,5 +140,5 @@ def stop_rasterizing(self): if self._bbox_inches_restore: # when tight bbox is used r = process_figure_for_rasterizing(self.figure, self._bbox_inches_restore, - mode="pdf") + self._figdpi) self._bbox_inches_restore = r diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 6d76cd58fd24..2e0e6086836c 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2434,6 +2434,8 @@ class FigureCanvasPdf(FigureCanvasBase): figure - A Figure instance """ + fixed_dpi = 72 + def draw(self): pass diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index b7b5a6a8852d..598ba8a0382d 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -799,7 +799,7 @@ def _print_pgf_to_fh(self, fh, *args, **kwargs): writeln(fh, r"\makeatletter") writeln(fh, r"\begin{pgfpicture}") writeln(fh, r"\pgfpathrectangle{\pgfpointorigin}{\pgfqpoint{%fin}{%fin}}" % (w, h)) - writeln(fh, r"\pgfusepath{use as bounding box}") + writeln(fh, r"\pgfusepath{use as bounding box, clip}") _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) renderer = MixedModeRenderer(self.figure, w, h, dpi, RendererPgf(self.figure, fh), diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index a4f4ff6df0fb..0dffb4a921fc 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -966,6 +966,8 @@ def new_figure_manager_given_figure(num, figure): class FigureCanvasPS(FigureCanvasBase): _renderer_class = RendererPS + fixed_dpi = 72 + def draw(self): pass diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 585f51420e8b..de05ed0232f0 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -1150,6 +1150,8 @@ class FigureCanvasSVG(FigureCanvasBase): filetypes = {'svg': 'Scalable Vector Graphics', 'svgz': 'Scalable Vector Graphics'} + fixed_dpi = 72 + def print_svg(self, filename, *args, **kwargs): if is_string_like(filename): fh_to_close = svgwriter = io.open(filename, 'w', encoding='utf-8') diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_bbox_inches.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_bbox_inches.pdf new file mode 100644 index 000000000000..dcc0f56c998b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_backend_pgf/pgf_bbox_inches.pdf differ diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py index 76e3979dc12e..2703c4ca5809 100644 --- a/lib/matplotlib/tests/test_backend_pgf.py +++ b/lib/matplotlib/tests/test_backend_pgf.py @@ -58,9 +58,9 @@ def backend_switcher(*args, **kwargs): return switch_backend_decorator -def compare_figure(fname): +def compare_figure(fname, savefig_kwargs={}): actual = os.path.join(result_dir, fname) - plt.savefig(actual) + plt.savefig(actual, **savefig_kwargs) expected = os.path.join(result_dir, "expected_%s" % fname) shutil.copyfile(os.path.join(baseline_dir, fname), expected) @@ -168,6 +168,24 @@ def test_mixedmode(): compare_figure('pgf_mixedmode.pdf') +# test bbox_inches clipping +@switch_backend('pgf') +def test_bbox_inches(): + if not check_for('xelatex'): + raise SkipTest('xelatex + pgf is required') + + Y, X = np.ogrid[-1:1:40j, -1:1:40j] + fig = plt.figure() + ax1 = fig.add_subplot(121) + ax1.plot(range(5)) + ax2 = fig.add_subplot(122) + ax2.plot(range(5)) + plt.tight_layout() + + bbox = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) + compare_figure('pgf_bbox_inches.pdf', savefig_kwargs={'bbox_inches': bbox}) + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tight_bbox.py b/lib/matplotlib/tight_bbox.py index e89b5f8de9c9..f98a6bc10369 100644 --- a/lib/matplotlib/tight_bbox.py +++ b/lib/matplotlib/tight_bbox.py @@ -11,7 +11,7 @@ from matplotlib.transforms import Bbox, TransformedBbox, Affine2D -def adjust_bbox(fig, format, bbox_inches): +def adjust_bbox(fig, bbox_inches, fixed_dpi=None): """ Temporarily adjust the figure so that only the specified area (bbox_inches) is saved. @@ -50,63 +50,20 @@ def restore_bbox(): fig.transFigure.invalidate() fig.patch.set_bounds(0, 0, 1, 1) - adjust_bbox_handler = _adjust_bbox_handler_d.get(format) - if adjust_bbox_handler is not None: - adjust_bbox_handler(fig, bbox_inches) - return restore_bbox + if fixed_dpi is not None: + tr = Affine2D().scale(fixed_dpi) + dpi_scale = fixed_dpi / fig.dpi else: - warnings.warn("bbox_inches option for %s backend is not " - "implemented yet." % (format)) - return None - - -def adjust_bbox_png(fig, bbox_inches): - """ - adjust_bbox for png (Agg) format - """ - - tr = fig.dpi_scale_trans - - _bbox = TransformedBbox(bbox_inches, - tr) - x0, y0 = _bbox.x0, _bbox.y0 - fig.bbox_inches = Bbox.from_bounds(0, 0, - bbox_inches.width, - bbox_inches.height) - - x0, y0 = _bbox.x0, _bbox.y0 - w1, h1 = fig.bbox.width, fig.bbox.height - fig.transFigure._boxout = Bbox.from_bounds(-x0, -y0, - w1, h1) - fig.transFigure.invalidate() - - fig.bbox = TransformedBbox(fig.bbox_inches, tr) - - fig.patch.set_bounds(x0 / w1, y0 / h1, - fig.bbox.width / w1, fig.bbox.height / h1) - - -def adjust_bbox_pdf(fig, bbox_inches): - """ - adjust_bbox for pdf & eps format - """ - - if fig._cachedRenderer.__class__.__name__ == "RendererPgf": tr = Affine2D().scale(fig.dpi) - f = 1. - else: - tr = Affine2D().scale(72) - f = 72. / fig.dpi + dpi_scale = 1. _bbox = TransformedBbox(bbox_inches, tr) fig.bbox_inches = Bbox.from_bounds(0, 0, - bbox_inches.width, - bbox_inches.height) + bbox_inches.width, bbox_inches.height) x0, y0 = _bbox.x0, _bbox.y0 - w1, h1 = fig.bbox.width * f, fig.bbox.height * f - fig.transFigure._boxout = Bbox.from_bounds(-x0, -y0, - w1, h1) + w1, h1 = fig.bbox.width * dpi_scale, fig.bbox.height * dpi_scale + fig.transFigure._boxout = Bbox.from_bounds(-x0, -y0, w1, h1) fig.transFigure.invalidate() fig.bbox = TransformedBbox(fig.bbox_inches, tr) @@ -114,10 +71,10 @@ def adjust_bbox_pdf(fig, bbox_inches): fig.patch.set_bounds(x0 / w1, y0 / h1, fig.bbox.width / w1, fig.bbox.height / h1) + return restore_bbox -def process_figure_for_rasterizing(figure, - bbox_inches_restore, mode): +def process_figure_for_rasterizing(fig, bbox_inches_restore, fixed_dpi=None): """ This need to be called when figure dpi changes during the drawing (e.g., rasterizing). It recovers the bbox and re-adjust it with @@ -126,14 +83,6 @@ def process_figure_for_rasterizing(figure, bbox_inches, restore_bbox = bbox_inches_restore restore_bbox() - r = adjust_bbox(figure, mode, - bbox_inches) + r = adjust_bbox(figure, bbox_inches, fixed_dpi) return bbox_inches, r - - -_adjust_bbox_handler_d = {} -for format in ["png", "raw", "rgba", "jpg", "jpeg", "tiff"]: - _adjust_bbox_handler_d[format] = adjust_bbox_png -for format in ["pgf", "pdf", "eps", "svg", "svgz"]: - _adjust_bbox_handler_d[format] = adjust_bbox_pdf