From: <lee...@us...> - 2009-04-16 17:28:53
|
Revision: 7044 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7044&view=rev Author: leejjoon Date: 2009-04-16 17:28:41 +0000 (Thu, 16 Apr 2009) Log Message: ----------- Fixed a bug in mixed mode renderer that images produced by an rasterizing backend are placed with incorrect size. Modified Paths: -------------- trunk/matplotlib/lib/matplotlib/backend_bases.py trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py trunk/matplotlib/lib/matplotlib/backends/backend_svg.py Added Paths: ----------- trunk/matplotlib/examples/misc/tight_bbox_test.py trunk/matplotlib/lib/matplotlib/tight_bbox.py Added: trunk/matplotlib/examples/misc/tight_bbox_test.py =================================================================== --- trunk/matplotlib/examples/misc/tight_bbox_test.py (rev 0) +++ trunk/matplotlib/examples/misc/tight_bbox_test.py 2009-04-16 17:28:41 UTC (rev 7044) @@ -0,0 +1,14 @@ +import matplotlib.pyplot as plt +import numpy as np + +ax = plt.axes([0.1, 0.3, 0.5, 0.5]) + +ax.pcolormesh(np.array([[1,2],[3,4]])) +plt.yticks([0.5, 1.5], ["long long tick label", + "tick label"]) +plt.ylabel("My y-label") +plt.title("Check saved figures for their bboxes") +for ext in ["png", "pdf", "svg", "svgz", "eps"]: + print "saving tight_bbox_test.%s" % (ext,) + plt.savefig("tight_bbox_test.%s" % (ext,), bbox_inches="tight") +plt.show() Modified: trunk/matplotlib/lib/matplotlib/backend_bases.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-04-15 17:52:23 UTC (rev 7043) +++ trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-04-16 17:28:41 UTC (rev 7044) @@ -36,6 +36,8 @@ from matplotlib.transforms import Bbox, TransformedBbox, Affine2D import cStringIO +import matplotlib.tight_bbox as tight_bbox + class RendererBase: """An abstract base class to handle drawing/rendering operations. @@ -271,7 +273,6 @@ gc.set_alpha(rgbFace[-1]) rgbFace = rgbFace[:3] gc.set_antialiased(antialiaseds[i % Naa]) - if Nurls: gc.set_url(urls[i % Nurls]) @@ -1426,7 +1427,16 @@ if bbox_inches: # call adjust_bbox to save only the given area if bbox_inches == "tight": - # save the figure to estimate the bounding box + # when bbox_inches == "tight", it saves the figure + # twice. The first save command is just to estimate + # the bounding box of the figure. A stringIO object is + # used as a temporary file object, but it causes a + # problem for some backends (ps backend with + # usetex=True) if they expect a filename, not a + # file-like object. As I think it is best to change + # the backend to support file-like object, i'm going + # to leave it as it is. However, a better solution + # than stringIO seems to be needed. -JJL result = getattr(self, method_name)( cStringIO.StringIO(), dpi=dpi, @@ -1439,10 +1449,13 @@ pad = kwargs.pop("pad_inches", 0.1) bbox_inches = bbox_inches.padded(pad) - restore_bbox = self._adjust_bbox(self.figure, format, - bbox_inches) + restore_bbox = tight_bbox.adjust_bbox(self.figure, format, + bbox_inches) + + _bbox_inches_restore = (bbox_inches, restore_bbox) + else: + _bbox_inches_restore = None - try: result = getattr(self, method_name)( filename, @@ -1450,6 +1463,7 @@ facecolor=facecolor, edgecolor=edgecolor, orientation=orientation, + bbox_inches_restore=_bbox_inches_restore, **kwargs) finally: if bbox_inches and restore_bbox: @@ -1463,108 +1477,8 @@ return result - def _adjust_bbox(self, fig, format, bbox_inches): - """ - Temporarily adjust the figure so that only the specified area - (bbox_inches) is saved. - It modifies fig.bbox, fig.bbox_inches, - fig.transFigure._boxout, and fig.patch. While the figure size - changes, the scale of the original figure is conserved. A - function whitch restores the original values are returned. - """ - origBbox = fig.bbox - origBboxInches = fig.bbox_inches - _boxout = fig.transFigure._boxout - - asp_list = [] - locator_list = [] - for ax in fig.axes: - pos = ax.get_position(original=False).frozen() - locator_list.append(ax.get_axes_locator()) - asp_list.append(ax.get_aspect()) - - def _l(a, r, pos=pos): return pos - ax.set_axes_locator(_l) - ax.set_aspect("auto") - - - - def restore_bbox(): - - for ax, asp, loc in zip(fig.axes, asp_list, locator_list): - ax.set_aspect(asp) - ax.set_axes_locator(loc) - - fig.bbox = origBbox - fig.bbox_inches = origBboxInches - fig.transFigure._boxout = _boxout - fig.transFigure.invalidate() - fig.patch.set_bounds(0, 0, 1, 1) - - if format in ["png", "raw", "rgba"]: - self._adjust_bbox_png(fig, bbox_inches) - return restore_bbox - elif format in ["pdf", "eps"]: - self._adjust_bbox_pdf(fig, bbox_inches) - return restore_bbox - else: - warnings.warn("bbox_inches option for %s backend is not implemented yet." % (format)) - return None - - - def _adjust_bbox_png(self, 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 - self.figure.transFigure._boxout = Bbox.from_bounds(-x0, -y0, - w1, h1) - self.figure.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(self, fig, bbox_inches): - """ - _adjust_bbox for pdf & eps format - """ - - tr = Affine2D().scale(72) - - _bbox = TransformedBbox(bbox_inches, tr) - - fig.bbox_inches = Bbox.from_bounds(0, 0, - bbox_inches.width, - bbox_inches.height) - x0, y0 = _bbox.x0, _bbox.y0 - f = 72. / fig.dpi - w1, h1 = fig.bbox.width*f, fig.bbox.height*f - self.figure.transFigure._boxout = Bbox.from_bounds(-x0, -y0, - w1, h1) - self.figure.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 get_default_filetype(self): raise NotImplementedError Modified: trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py 2009-04-15 17:52:23 UTC (rev 7043) +++ trunk/matplotlib/lib/matplotlib/backends/backend_mixed.py 2009-04-16 17:28:41 UTC (rev 7044) @@ -1,5 +1,6 @@ from matplotlib._image import frombuffer from matplotlib.backends.backend_agg import RendererAgg +from matplotlib.tight_bbox import process_figure_for_rasterizing class MixedModeRenderer(object): """ @@ -9,8 +10,12 @@ complex objects, such as quad meshes, are rasterised and then output as images. """ - def __init__(self, width, height, dpi, vector_renderer, raster_renderer_class=None): + def __init__(self, figure, width, height, dpi, vector_renderer, + raster_renderer_class=None, + bbox_inches_restore=None): """ + figure: The figure instance. + width: The width of the canvas in logical units height: The height of the canvas in logical units @@ -38,6 +43,13 @@ self._raster_renderer = None self._rasterizing = 0 + # A renference to the figure is needed as we need to change + # the figure dpi before and after the rasterization. Although + # this looks ugly, I couldn't find a better solution. -JJL + self.figure=figure + + self._bbox_inches_restore = bbox_inches_restore + self._set_current_renderer(vector_renderer) _methods = """ @@ -56,6 +68,7 @@ renderer.start_rasterizing = self.start_rasterizing renderer.stop_rasterizing = self.stop_rasterizing + def start_rasterizing(self): """ Enter "raster" mode. All subsequent drawing commands (until @@ -65,12 +78,25 @@ If start_rasterizing is called multiple times before stop_rasterizing is called, this method has no effect. """ + + # change the dpi of the figure temporarily. + self.figure.set_dpi(self.dpi) + + 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 = r + + if self._rasterizing == 0: self._raster_renderer = self._raster_renderer_class( self._width*self.dpi, self._height*self.dpi, self.dpi) self._set_current_renderer(self._raster_renderer) self._rasterizing += 1 + def stop_rasterizing(self): """ Exit "raster" mode. All of the drawing that was done since @@ -91,6 +117,17 @@ image = frombuffer(buffer, w, h, True) image.is_grayscale = False image.flipud_out() - self._renderer.draw_image(l, height - b - h, image, None) + self._renderer.draw_image(int(float(l)/self.dpi*72.), + int((float(height) - b - h)/self.dpi*72.), + image, None) self._raster_renderer = None self._rasterizing = False + + # restore the figure dpi. + self.figure.set_dpi(72) + + if self._bbox_inches_restore: # when tight bbox is used + r = process_figure_for_rasterizing(self.figure, + self._bbox_inches_restore, + mode="pdf") + self._bbox_inches_restore = r Modified: trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2009-04-15 17:52:23 UTC (rev 7043) +++ trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2009-04-16 17:28:41 UTC (rev 7044) @@ -1990,8 +1990,10 @@ else: file = PdfFile(filename) file.newPage(width, height) - renderer = MixedModeRenderer( - width, height, 72, RendererPdf(file, image_dpi)) + _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) + renderer = MixedModeRenderer(self.figure, + width, height, image_dpi, RendererPdf(file, image_dpi), + bbox_inches_restore=_bbox_inches_restore) self.figure.draw(renderer) renderer.finalize() if isinstance(filename, PdfPages): # finish off this page Modified: trunk/matplotlib/lib/matplotlib/backends/backend_svg.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_svg.py 2009-04-15 17:52:23 UTC (rev 7043) +++ trunk/matplotlib/lib/matplotlib/backends/backend_svg.py 2009-04-16 17:28:41 UTC (rev 7044) @@ -612,7 +612,7 @@ fh_to_close = None else: raise ValueError("filename must be a path or a file-like object") - return self._print_svg(filename, svgwriter, fh_to_close) + return self._print_svg(filename, svgwriter, fh_to_close, **kwargs) def print_svgz(self, filename, *args, **kwargs): if is_string_like(filename): @@ -625,7 +625,7 @@ raise ValueError("filename must be a path or a file-like object") return self._print_svg(filename, svgwriter, fh_to_close) - def _print_svg(self, filename, svgwriter, fh_to_close=None): + def _print_svg(self, filename, svgwriter, fh_to_close=None, **kwargs): self.figure.set_dpi(72.0) width, height = self.figure.get_size_inches() w, h = width*72, height*72 @@ -633,8 +633,20 @@ if rcParams['svg.image_noscale']: renderer = RendererSVG(w, h, svgwriter, filename) else: - renderer = MixedModeRenderer( - width, height, 72.0, RendererSVG(w, h, svgwriter, filename)) + # setting mixed renderer dpi other than 72 results in + # incorrect size of the rasterized image. It seems that the + # svg internally uses fixed dpi of 72 and seems to cause + # the problem. I hope someone who knows the svg backends + # take a look at this problem. Meanwhile, the dpi + # parameter is ignored and image_dpi is fixed at 72. - JJL + + #image_dpi = kwargs.pop("dpi", 72) + image_dpi = 72 + _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None) + renderer = MixedModeRenderer(self.figure, + width, height, image_dpi, RendererSVG(w, h, svgwriter, filename), + bbox_inches_restore=_bbox_inches_restore) + self.figure.draw(renderer) renderer.finalize() if fh_to_close is not None: Added: trunk/matplotlib/lib/matplotlib/tight_bbox.py =================================================================== --- trunk/matplotlib/lib/matplotlib/tight_bbox.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/tight_bbox.py 2009-04-16 17:28:41 UTC (rev 7044) @@ -0,0 +1,127 @@ +""" +This module is to support *bbox_inches* option in savefig command. +""" + +import warnings +from matplotlib.transforms import Bbox, TransformedBbox, Affine2D + + +def adjust_bbox(fig, format, bbox_inches): + """ + Temporarily adjust the figure so that only the specified area + (bbox_inches) is saved. + + It modifies fig.bbox, fig.bbox_inches, + fig.transFigure._boxout, and fig.patch. While the figure size + changes, the scale of the original figure is conserved. A + function whitch restores the original values are returned. + """ + + origBbox = fig.bbox + origBboxInches = fig.bbox_inches + _boxout = fig.transFigure._boxout + + asp_list = [] + locator_list = [] + for ax in fig.axes: + pos = ax.get_position(original=False).frozen() + locator_list.append(ax.get_axes_locator()) + asp_list.append(ax.get_aspect()) + + def _l(a, r, pos=pos): return pos + ax.set_axes_locator(_l) + ax.set_aspect("auto") + + + + def restore_bbox(): + + for ax, asp, loc in zip(fig.axes, asp_list, locator_list): + ax.set_aspect(asp) + ax.set_axes_locator(loc) + + fig.bbox = origBbox + fig.bbox_inches = origBboxInches + fig.transFigure._boxout = _boxout + fig.transFigure.invalidate() + fig.patch.set_bounds(0, 0, 1, 1) + + if format in ["png", "raw", "rgba"]: + adjust_bbox_png(fig, bbox_inches) + return restore_bbox + elif format in ["pdf", "eps", "svg", "svgz"]: + adjust_bbox_pdf(fig, bbox_inches) + return restore_bbox + 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 + """ + + tr = Affine2D().scale(72) + + _bbox = TransformedBbox(bbox_inches, tr) + + fig.bbox_inches = Bbox.from_bounds(0, 0, + bbox_inches.width, + bbox_inches.height) + x0, y0 = _bbox.x0, _bbox.y0 + f = 72. / fig.dpi + w1, h1 = fig.bbox.width*f, fig.bbox.height*f + 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 process_figure_for_rasterizing(figure, + bbox_inches_restore, mode): + + """ + This need to be called when figure dpi changes during the drawing + (e.g., rasterizing). It recovers the bbox and re-adjust it with + the new dpi. + """ + + bbox_inches, restore_bbox = bbox_inches_restore + restore_bbox() + r = adjust_bbox(figure, mode, + bbox_inches) + + return bbox_inches, r + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |