I'm attaching the revised patch (I may split the patch for commit).
Regards,
-JJ
On Tue, May 5, 2009 at 3:22 PM, Eric Bruning <eric.brun...@gmail.com> wrote:
> To avoid confusion, how about renaming draw_wrapper._rasterized to
> draw_wrapper._supports_rasterization ?
>
> This helps to distinguish from artist._rasterized, which has a
> different purpose.
>
> The lack of consistency in decoration language for different artists
> is my fault. It reflects the different ways the base artist module is
> imported for the subclasses of artist.
>
> Thanks,
> Eric
>
Index: lib/matplotlib/legend.py
===================================================================
--- lib/matplotlib/legend.py (revision 7085)
+++ lib/matplotlib/legend.py (working copy)
@@ -26,7 +26,7 @@
import numpy as np
from matplotlib import rcParams
-from matplotlib.artist import Artist
+from matplotlib.artist import Artist, allow_rasterization
from matplotlib.cbook import is_string_like, iterable, silent_list, safezip
from matplotlib.font_manager import FontProperties
from matplotlib.lines import Line2D
@@ -323,6 +323,7 @@
return x+xdescent, y+ydescent
+ @allow_rasterization
def draw(self, renderer):
"Draw everything that belongs to the legend"
if not self.get_visible(): return
Index: lib/matplotlib/backend_bases.py
===================================================================
--- lib/matplotlib/backend_bases.py (revision 7085)
+++ lib/matplotlib/backend_bases.py (working copy)
@@ -1443,6 +1443,7 @@
facecolor=facecolor,
edgecolor=edgecolor,
orientation=orientation,
+ dryrun=True,
**kwargs)
renderer = self.figure._cachedRenderer
bbox_inches = self.figure.get_tightbbox(renderer)
Index: lib/matplotlib/artist.py
===================================================================
--- lib/matplotlib/artist.py (revision 7085)
+++ lib/matplotlib/artist.py (working copy)
@@ -22,6 +22,38 @@
# http://groups.google.com/groups?hl=en&lr=&threadm=mailman.5090.1098044946.5135.python-list%40python.org&rnum=1&prev=/groups%3Fq%3D__doc__%2Bauthor%253Ajdhunter%2540ace.bsd.uchicago.edu%26hl%3Den%26btnG%3DGoogle%2BSearch
+
+
+def allow_rasterization(draw):
+ """
+ Decorator for Artist.draw method. Provides routines
+ that run before and after the draw call. The before and after functions
+ are useful for changing artist-dependant renderer attributes or making
+ other setup function calls, such as starting and flushing a mixed-mode
+ renderer.
+ """
+ def before(artist, renderer):
+ if artist.get_rasterized():
+ renderer.start_rasterizing()
+
+ def after(artist, renderer):
+ if artist.get_rasterized():
+ renderer.stop_rasterizing()
+
+ # the axes class has a second argument inframe for its draw method.
+ def draw_wrapper(artist, renderer, *kl):
+ before(artist, renderer)
+ draw(artist, renderer, *kl)
+ after(artist, renderer)
+
+ # "safe wrapping" to exactly replicate anything we haven't overridden above
+ draw_wrapper.__name__ = draw.__name__
+ draw_wrapper.__dict__ = draw.__dict__
+ draw_wrapper.__doc__ = draw.__doc__
+ draw_wrapper._supports_rasterization = True
+ return draw_wrapper
+
+
class Artist(object):
"""
Abstract base class for someone who renders into a
@@ -45,6 +77,7 @@
self._label = ''
self._picker = None
self._contains = None
+ self._rasterized = None
self.eventson = False # fire events only if eventson
self._oid = 0 # an observer id
@@ -510,7 +543,23 @@
else:
gc.set_clip_rectangle(None)
gc.set_clip_path(None)
+
+ def get_rasterized(self):
+ return self._rasterized
+
+ def set_rasterized(self, rasterized):
+ """
+ Force rasterized (bitmap) drawing in vector backend output.
+
+ Defaults to None, which implies the backend's default behavior
+
+ ACCEPTS: [True | False | None]
+ """
+ if rasterized and not hasattr(self.draw, "_supports_rasterization"):
+ warnings.warn("Rasterization of '%s' will be ignored" % self)
+ self._rasterized = rasterized
+
def draw(self, renderer, *args, **kwargs):
'Derived classes drawing method'
if not self.get_visible(): return
Index: lib/matplotlib/lines.py
===================================================================
--- lib/matplotlib/lines.py (revision 7085)
+++ lib/matplotlib/lines.py (working copy)
@@ -18,6 +18,8 @@
from transforms import Affine2D, Bbox, TransformedPath, IdentityTransform
from matplotlib import rcParams
+from artist import allow_rasterization
+
# special-purpose marker identifiers:
(TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN,
CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN) = range(8)
@@ -459,6 +461,7 @@
if len(x)<2: return 1
return np.alltrue(x[1:]-x[0:-1]>=0)
+ @allow_rasterization
def draw(self, renderer):
if self._invalid:
self.recache()
Index: lib/matplotlib/quiver.py
===================================================================
--- lib/matplotlib/quiver.py (revision 7085)
+++ lib/matplotlib/quiver.py (working copy)
@@ -21,6 +21,7 @@
import matplotlib.transforms as transforms
import matplotlib.text as mtext
import matplotlib.artist as martist
+from matplotlib.artist import allow_rasterization
import matplotlib.font_manager as font_manager
from matplotlib.cbook import delete_masked_points
from matplotlib.patches import CirclePolygon
@@ -282,6 +283,7 @@
else:
return y
+ @allow_rasterization
def draw(self, renderer):
self._init()
self.vector.draw(renderer)
@@ -418,6 +420,7 @@
if self.width is None:
self.width = 0.06 * self.span / sn
+ @allow_rasterization
def draw(self, renderer):
self._init()
if self._new_UV or self.angles == 'xy':
Index: lib/matplotlib/patches.py
===================================================================
--- lib/matplotlib/patches.py (revision 7085)
+++ lib/matplotlib/patches.py (working copy)
@@ -7,6 +7,7 @@
import numpy as np
import matplotlib.cbook as cbook
import matplotlib.artist as artist
+from matplotlib.artist import allow_rasterization
import matplotlib.colors as colors
import matplotlib.transforms as transforms
from matplotlib.path import Path
@@ -260,7 +261,7 @@
'Return the current hatching pattern'
return self._hatch
-
+ @allow_rasterization
def draw(self, renderer):
'Draw the :class:`Patch` to the given *renderer*.'
if not self.get_visible(): return
@@ -1176,6 +1177,7 @@
self.theta2 = theta2
__init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
+ @allow_rasterization
def draw(self, renderer):
"""
Ellipses are normally drawn using an approximation that uses
Index: lib/matplotlib/axes.py
===================================================================
--- lib/matplotlib/axes.py (revision 7085)
+++ lib/matplotlib/axes.py (working copy)
@@ -8,6 +8,7 @@
rcParams = matplotlib.rcParams
import matplotlib.artist as martist
+from matplotlib.artist import allow_rasterization
import matplotlib.axis as maxis
import matplotlib.cbook as cbook
import matplotlib.collections as mcoll
@@ -531,6 +532,8 @@
self._frameon = frameon
self._axisbelow = rcParams['axes.axisbelow']
+ self._rasterization_zorder = -30000
+
self._hold = rcParams['axes.hold']
self._connected = {} # a dict from events to (id, func)
self.cla()
@@ -1566,6 +1569,19 @@
"""
self._autoscaleYon = b
+ def set_rasterization_zorder(self, z):
+ """
+ Set zorder value below which artists will be rasterized
+ """
+ self._rasterization_zorder = z
+
+ def get_rasterization_zorder(self):
+ """
+ Get zorder value below which artists will be rasterized
+ """
+ return self._rasterization_zorder
+
+
def autoscale_view(self, tight=False, scalex=True, scaley=True):
"""
autoscale the view limits using the data limits. You can
@@ -1602,6 +1618,7 @@
#### Drawing
+ @allow_rasterization
def draw(self, renderer=None, inframe=False):
"Draw everything (plot lines, axes, labels)"
if renderer is None:
@@ -1619,15 +1636,55 @@
else:
self.apply_aspect()
+
+ artists = []
+
+ artists.extend(self.collections)
+ artists.extend(self.patches)
+ artists.extend(self.lines)
+ artists.extend(self.texts)
+ artists.extend(self.artists)
+ if self.axison and not inframe:
+ if self._axisbelow:
+ self.xaxis.set_zorder(0.5)
+ self.yaxis.set_zorder(0.5)
+ else:
+ self.xaxis.set_zorder(2.5)
+ self.yaxis.set_zorder(2.5)
+ artists.extend([self.xaxis, self.yaxis])
+ if not inframe: artists.append(self.title)
+ artists.extend(self.tables)
+ if self.legend_ is not None:
+ artists.append(self.legend_)
+
+ # the frame draws the edges around the axes patch -- we
+ # decouple these so the patch can be in the background and the
+ # frame in the foreground.
+ if self.axison and self._frameon:
+ artists.append(self.frame)
+
+
+ dsu = [ (a.zorder, i, a) for i, a in enumerate(artists)
+ if not a.get_animated() ]
+ dsu.sort()
+
+
+ # rasterze artists with negative zorder
+ # if the minimum zorder is negative, start rasterization
+ rasterization_zorder = self._rasterization_zorder
+ if len(dsu) > 0 and dsu[0][0] < rasterization_zorder:
+ renderer.start_rasterizing()
+ dsu_rasterized = [l for l in dsu if l[0] < rasterization_zorder]
+ dsu = [l for l in dsu if l[0] >= rasterization_zorder]
+ else:
+ dsu_rasterized = []
+
+
# the patch draws the background rectangle -- the frame below
# will draw the edges
if self.axison and self._frameon:
self.patch.draw(renderer)
- artists = []
-
-
-
if len(self.images)<=1 or renderer.option_image_nocomposite():
for im in self.images:
im.draw(renderer)
@@ -1656,35 +1713,13 @@
self.patch.get_path(),
self.patch.get_transform())
- artists.extend(self.collections)
- artists.extend(self.patches)
- artists.extend(self.lines)
- artists.extend(self.texts)
- artists.extend(self.artists)
- if self.axison and not inframe:
- if self._axisbelow:
- self.xaxis.set_zorder(0.5)
- self.yaxis.set_zorder(0.5)
- else:
- self.xaxis.set_zorder(2.5)
- self.yaxis.set_zorder(2.5)
- artists.extend([self.xaxis, self.yaxis])
- if not inframe: artists.append(self.title)
- artists.extend(self.tables)
- if self.legend_ is not None:
- artists.append(self.legend_)
- # the frame draws the edges around the axes patch -- we
- # decouple these so the patch can be in the background and the
- # frame in the foreground.
- if self.axison and self._frameon:
- artists.append(self.frame)
+ if dsu_rasterized:
+ for zorder, i, a in dsu_rasterized:
+ a.draw(renderer)
+ renderer.stop_rasterizing()
- dsu = [ (a.zorder, i, a) for i, a in enumerate(artists)
- if not a.get_animated() ]
- dsu.sort()
-
for zorder, i, a in dsu:
a.draw(renderer)
Index: lib/matplotlib/axis.py
===================================================================
--- lib/matplotlib/axis.py (revision 7085)
+++ lib/matplotlib/axis.py (working copy)
@@ -5,6 +5,7 @@
from matplotlib import rcParams
import matplotlib.artist as artist
+from matplotlib.artist import allow_rasterization
import matplotlib.cbook as cbook
import matplotlib.font_manager as font_manager
import matplotlib.lines as mlines
@@ -176,6 +177,7 @@
'Return the tick location (data coords) as a scalar'
return self._loc
+ @allow_rasterization
def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__name__)
@@ -719,6 +721,7 @@
bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
return bbox, bbox2
+ @allow_rasterization
def draw(self, renderer, *args, **kwargs):
'Draw the axis lines, grid lines, tick lines and labels'
ticklabelBoxes = []
Index: lib/matplotlib/collections.py
===================================================================
--- lib/matplotlib/collections.py (revision 7085)
+++ lib/matplotlib/collections.py (working copy)
@@ -17,6 +17,7 @@
import matplotlib.cm as cm
import matplotlib.transforms as transforms
import matplotlib.artist as artist
+from matplotlib.artist import allow_rasterization
import matplotlib.backend_bases as backend_bases
import matplotlib.path as mpath
import matplotlib.mlab as mlab
@@ -190,6 +191,7 @@
return transform, transOffset, offsets, paths
+ @allow_rasterization
def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__class__.__name__)
@@ -594,6 +596,7 @@
def get_datalim(self, transData):
return self._bbox
+ @allow_rasterization
def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__class__.__name__)
@@ -781,6 +784,7 @@
__init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
+ @allow_rasterization
def draw(self, renderer):
self._transforms = [
transforms.Affine2D().rotate(-self._rotation).scale(
Index: lib/matplotlib/figure.py
===================================================================
--- lib/matplotlib/figure.py (revision 7085)
+++ lib/matplotlib/figure.py (working copy)
@@ -15,7 +15,7 @@
import time
import artist
-from artist import Artist
+from artist import Artist, allow_rasterization
from axes import Axes, SubplotBase, subplot_class_factory
from cbook import flatten, allequal, Stack, iterable, dedent
import _image
@@ -727,6 +727,7 @@
"""
self.clf()
+ @allow_rasterization
def draw(self, renderer):
"""
Render the figure using :class:`matplotlib.backend_bases.RendererBase` instance renderer
Index: lib/matplotlib/image.py
===================================================================
--- lib/matplotlib/image.py (revision 7085)
+++ lib/matplotlib/image.py (working copy)
@@ -11,6 +11,7 @@
from matplotlib import rcParams
import matplotlib.artist as martist
+from matplotlib.artist import allow_rasterization
import matplotlib.colors as mcolors
import matplotlib.cm as cm
import matplotlib.cbook as cbook
@@ -225,7 +226,7 @@
norm=self._filternorm, radius=self._filterrad)
return im
-
+ @allow_rasterization
def draw(self, renderer, *args, **kwargs):
if not self.get_visible(): return
if (self.axes.get_xscale() != 'linear' or
@@ -571,6 +572,7 @@
im.is_grayscale = self.is_grayscale
return im
+ @allow_rasterization
def draw(self, renderer, *args, **kwargs):
if not self.get_visible(): return
im = self.make_image(renderer.get_image_magnification())
@@ -723,6 +725,7 @@
return im
+ @allow_rasterization
def draw(self, renderer, *args, **kwargs):
if not self.get_visible(): return
# todo: we should be able to do some cacheing here
Index: lib/matplotlib/backends/backend_ps.py
===================================================================
--- lib/matplotlib/backends/backend_ps.py (revision 7085)
+++ lib/matplotlib/backends/backend_ps.py (working copy)
@@ -33,6 +33,9 @@
from matplotlib.path import Path
from matplotlib.transforms import Affine2D
+from matplotlib.backends.backend_mixed import MixedModeRenderer
+
+
import numpy as npy
import binascii
import re
@@ -843,8 +846,13 @@
def print_eps(self, outfile, *args, **kwargs):
return self._print_ps(outfile, 'eps', *args, **kwargs)
+
+
+
+
+
def _print_ps(self, outfile, format, *args, **kwargs):
- papertype = kwargs.get("papertype", rcParams['ps.papersize'])
+ papertype = kwargs.pop("papertype", rcParams['ps.papersize'])
papertype = papertype.lower()
if papertype == 'auto':
pass
@@ -852,25 +860,28 @@
raise RuntimeError( '%s is not a valid papertype. Use one \
of %s'% (papertype, ', '.join( papersize.keys() )) )
- orientation = kwargs.get("orientation", "portrait").lower()
+ orientation = kwargs.pop("orientation", "portrait").lower()
if orientation == 'landscape': isLandscape = True
elif orientation == 'portrait': isLandscape = False
else: raise RuntimeError('Orientation must be "portrait" or "landscape"')
self.figure.set_dpi(72) # Override the dpi kwarg
- imagedpi = kwargs.get("dpi", 72)
- facecolor = kwargs.get("facecolor", "w")
- edgecolor = kwargs.get("edgecolor", "w")
+ imagedpi = kwargs.pop("dpi", 72)
+ facecolor = kwargs.pop("facecolor", "w")
+ edgecolor = kwargs.pop("edgecolor", "w")
if rcParams['text.usetex']:
self._print_figure_tex(outfile, format, imagedpi, facecolor, edgecolor,
- orientation, isLandscape, papertype)
+ orientation, isLandscape, papertype,
+ **kwargs)
else:
self._print_figure(outfile, format, imagedpi, facecolor, edgecolor,
- orientation, isLandscape, papertype)
+ orientation, isLandscape, papertype,
+ **kwargs)
def _print_figure(self, outfile, format, dpi=72, facecolor='w', edgecolor='w',
- orientation='portrait', isLandscape=False, papertype=None):
+ orientation='portrait', isLandscape=False, papertype=None,
+ **kwargs):
"""
Render the figure to hardcopy. Set the figure patch face and
edge colors. This is useful because some of the GUIs have a
@@ -939,10 +950,30 @@
self.figure.set_facecolor(facecolor)
self.figure.set_edgecolor(edgecolor)
- self._pswriter = StringIO()
- renderer = RendererPS(width, height, self._pswriter, imagedpi=dpi)
+
+ dryrun = kwargs.get("dryrun", False)
+ if dryrun:
+ class NullWriter(object):
+ def write(self, *kl, **kwargs):
+ pass
+
+ self._pswriter = NullWriter()
+ else:
+ self._pswriter = StringIO()
+
+
+ # mixed mode rendering
+ _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None)
+ ps_renderer = RendererPS(width, height, self._pswriter, imagedpi=dpi)
+ renderer = MixedModeRenderer(self.figure,
+ width, height, dpi, ps_renderer,
+ bbox_inches_restore=_bbox_inches_restore)
+
self.figure.draw(renderer)
+ if dryrun: # return immediately if dryrun (tightbbox=True)
+ return
+
self.figure.set_facecolor(origfacecolor)
self.figure.set_edgecolor(origedgecolor)
@@ -962,7 +993,7 @@
Ndict = len(psDefs)
print >>fh, "%%BeginProlog"
if not rcParams['ps.useafm']:
- Ndict += len(renderer.used_characters)
+ Ndict += len(ps_renderer.used_characters)
print >>fh, "/mpldict %d dict def"%Ndict
print >>fh, "mpldict begin"
for d in psDefs:
@@ -970,7 +1001,7 @@
for l in d.split('\n'):
print >>fh, l.strip()
if not rcParams['ps.useafm']:
- for font_filename, chars in renderer.used_characters.values():
+ for font_filename, chars in ps_renderer.used_characters.values():
if len(chars):
font = FT2Font(font_filename)
cmap = font.get_charmap()
@@ -1019,7 +1050,8 @@
shutil.move(tmpfile, outfile)
def _print_figure_tex(self, outfile, format, dpi, facecolor, edgecolor,
- orientation, isLandscape, papertype):
+ orientation, isLandscape, papertype,
+ **kwargs):
"""
If text.usetex is True in rc, a temporary pair of tex/eps files
are created to allow tex to manage the text layout via the PSFrags
@@ -1051,10 +1083,29 @@
self.figure.set_facecolor(facecolor)
self.figure.set_edgecolor(edgecolor)
- self._pswriter = StringIO()
- renderer = RendererPS(width, height, self._pswriter, imagedpi=dpi)
+ dryrun = kwargs.get("dryrun", False)
+ if dryrun:
+ class NullWriter(object):
+ def write(self, *kl, **kwargs):
+ pass
+
+ self._pswriter = NullWriter()
+ else:
+ self._pswriter = StringIO()
+
+
+ # mixed mode rendering
+ _bbox_inches_restore = kwargs.pop("bbox_inches_restore", None)
+ ps_renderer = RendererPS(width, height, self._pswriter, imagedpi=dpi)
+ renderer = MixedModeRenderer(self.figure,
+ width, height, dpi, ps_renderer,
+ bbox_inches_restore=_bbox_inches_restore)
+
self.figure.draw(renderer)
+ if dryrun: # return immediately if dryrun (tightbbox=True)
+ return
+
self.figure.set_facecolor(origfacecolor)
self.figure.set_edgecolor(origedgecolor)
@@ -1117,11 +1168,11 @@
paper will be used to prevent clipping.'%(papertype, temp_papertype), 'helpful')
- texmanager = renderer.get_texmanager()
+ texmanager = ps_renderer.get_texmanager()
font_preamble = texmanager.get_font_preamble()
custom_preamble = texmanager.get_custom_preamble()
- convert_psfrags(tmpfile, renderer.psfrag, font_preamble,
+ convert_psfrags(tmpfile, ps_renderer.psfrag, font_preamble,
custom_preamble, paperWidth, paperHeight,
orientation)
Index: lib/matplotlib/table.py
===================================================================
--- lib/matplotlib/table.py (revision 7085)
+++ lib/matplotlib/table.py (working copy)
@@ -23,7 +23,7 @@
import warnings
import artist
-from artist import Artist
+from artist import Artist, allow_rasterization
from patches import Rectangle
from cbook import is_string_like
from text import Text
@@ -90,6 +90,7 @@
return fontsize
+ @allow_rasterization
def draw(self, renderer):
if not self.get_visible(): return
# draw the rectangle
@@ -215,6 +216,7 @@
def _approx_text_height(self):
return self.FONTSIZE/72.0*self.figure.dpi/self._axes.bbox.height * 1.2
+ @allow_rasterization
def draw(self, renderer):
# Need a renderer to do hit tests on mouseevent; assume the last one will do
if renderer is None:
------------------------------------------------------------------------------
The NEW KODAK i700 Series Scanners deliver under ANY circumstances! Your
production scanning environment may not be a perfect world - but thanks to
Kodak, there's a perfect scanner to get the job done! With the NEW KODAK i700
Series Scanner you'll get full speed at 300 dpi even with all image
processing features enabled. http://p.sf.net/sfu/kodak-com
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel