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.

------------------------------------------------------------------------------
Stay on top of everything new and different, both inside and 
around Java (TM) technology - register by April 22, and save
$200 on the JavaOne (SM) conference, June 2-5, 2009, San Francisco.
300 plus technical and hands-on sessions. Register today. 
Use priority code J9JMT32. http://p.sf.net/sfu/p
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to