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