Revision: 3852
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3852&view=rev
Author:   mdboom
Date:     2007-09-14 10:57:52 -0700 (Fri, 14 Sep 2007)

Log Message:
-----------
Sends paths to backend only once, and after that uses the "native" path by
reference with a changing transform.  Started recongfiguring
patches.py to use only Paths under the hood (to take advantage of this
caching).  Removed many methods from backend_agg that should
eventually be replaced by draw_path, at least in theory.

Modified Paths:
--------------
    branches/transforms/lib/matplotlib/backend_bases.py
    branches/transforms/lib/matplotlib/backends/backend_agg.py
    branches/transforms/lib/matplotlib/lines.py
    branches/transforms/lib/matplotlib/patches.py
    branches/transforms/lib/matplotlib/transforms.py
    branches/transforms/src/_backend_agg.cpp
    branches/transforms/src/_backend_agg.h

Added Paths:
-----------
    branches/transforms/lib/matplotlib/path.py

Modified: branches/transforms/lib/matplotlib/backend_bases.py
===================================================================
--- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-14 13:03:31 UTC 
(rev 3851)
+++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-14 17:57:52 UTC 
(rev 3852)
@@ -4,7 +4,7 @@
 """
 
 from __future__ import division
-import os, sys, warnings, copy
+import os, sys, warnings, copy, weakref
 
 import numpy as npy
 import matplotlib.numerix.npyma as ma
@@ -17,7 +17,10 @@
 class RendererBase:
     """An abstract base class to handle drawing/rendering operations
     """
-
+    # This will cache paths across rendering instances
+    # Each subclass of RenderBase should define this -->
+    # _paths = weakref.WeakKeyDictionary()
+    
     def __init__(self):
         self._texmanager = None
 
@@ -33,7 +36,35 @@
         """
         pass
 
+    def draw_path(self, gc, path, transform, rgbFace = None):
+       """
+       Handles the caching of the native path associated with the
+       given path and calls the underlying backend's _draw_path to
+       actually do the drawing.
+       """
+       native_path = self._native_paths.get(path)
+       if native_path is None:
+           import matplotlib.patches
+           print "CACHE MISS", path
+           native_path = self.convert_to_native_path(path)
+           self._native_paths[path] = native_path
+       self._draw_path(gc, native_path, transform, rgbFace)
 
+    def _draw_path(self, gc, native_path, transform, rgbFace):
+       """
+       Draw the native path object with the given GraphicsContext and
+       transform.  The transform passed in will always be affine.
+       """
+       raise NotImplementedError
+       
+    def convert_to_native_path(self, path):
+       """
+       Backends will normally will override this, but if they don't need any
+       special optimizations, they can just have the generic path data
+       passed to them in draw_path.
+       """
+       return path
+       
     def draw_arc(self, gc, rgbFace, x, y, width, height, angle1, angle2,
                  rotation):
         """
@@ -75,6 +106,9 @@
         """
         return False
 
+    def draw_markers(self, gc, marker_path, marker_trans, path, trans, 
rgbFace=None):
+       pass
+    
     def _draw_markers(self, bgc, path, rgbFace, x, y, trans):
         """
         This method is currently underscore hidden because the

Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py
===================================================================
--- branches/transforms/lib/matplotlib/backends/backend_agg.py  2007-09-14 
13:03:31 UTC (rev 3851)
+++ branches/transforms/lib/matplotlib/backends/backend_agg.py  2007-09-14 
17:57:52 UTC (rev 3852)
@@ -69,7 +69,7 @@
 
 """
 from __future__ import division
-import os, sys
+import os, sys, weakref
 
 import numpy as npy
 
@@ -95,7 +95,15 @@
     The renderer handles all the drawing primitives using a graphics
     context instance that controls the colors/styles
     """
-
+    # MGDTODO: Renderers seem to get created and destroyed fairly
+    # often so the paths are cached at the class (not instance) level.
+    # However, this dictionary is only directly used by RendererBase,
+    # so it seems funny to define it here.  However, if we didn't, the
+    # native paths would be shared across renderers, which is
+    # obviously bad.  Seems like a good use of metaclasses, but that
+    # also seems like a heavy solution for a minor problem.
+    _native_paths = weakref.WeakKeyDictionary()
+    
     debug=1
     texd = {}  # a cache of tex image rasters
     def __init__(self, width, height, dpi):
@@ -129,6 +137,12 @@
         if __debug__: verbose.report('RendererAgg.__init__ done',
                                      'debug-annoying')
 
+    def convert_to_native_path(self, path):
+       return self._renderer.convert_to_native_path(path.vertices, path.codes)
+
+    def _draw_path(self, gc, native_path, transform, rgbFace):
+       return self._renderer.draw_path(gc, native_path, transform.to_values(), 
rgbFace)
+       
     def draw_arc(self, gcEdge, rgbFace, x, y, width, height, angle1, angle2, 
rotation):
         """
         Draw an arc centered at x,y with width and height and angles

Modified: branches/transforms/lib/matplotlib/lines.py
===================================================================
--- branches/transforms/lib/matplotlib/lines.py 2007-09-14 13:03:31 UTC (rev 
3851)
+++ branches/transforms/lib/matplotlib/lines.py 2007-09-14 17:57:52 UTC (rev 
3852)
@@ -10,14 +10,14 @@
 
 import numpy as npy
 
-import agg
 import numerix.ma as ma
 from matplotlib import verbose
 import artist
 from artist import Artist, setp
 from cbook import iterable, is_string_like, is_numlike
 from colors import colorConverter
-from transforms import Bbox
+from path import Path
+from transforms import Affine2D, Bbox
 
 from matplotlib import rcParams
 
@@ -284,9 +284,6 @@
         self.set_data(xdata, ydata)
         self._logcache = None
 
-        # TODO: do we really need 'newstyle'
-        self._newstyle = False
-
     def contains(self, mouseevent):
         """Test whether the mouse event occurred on the line.  The pick radius 
determines
         the precision of the location test (usually within five points of the 
value).  Use
@@ -427,6 +424,7 @@
         if len(x) != len(y):
             raise RuntimeError('xdata and ydata must be the same length')
 
+       # MGDTODO: Deal with segments
         mx = ma.getmask(x)
         my = ma.getmask(y)
         mask = ma.mask_or(mx, my)
@@ -439,7 +437,9 @@
 
         self._x = npy.asarray(x, float)
         self._y = npy.asarray(y, float)
-
+       self._path = Path(npy.vstack((self._x, self._y)).transpose(),
+                         closed=False)
+       
         self._logcache = None
 
 
@@ -508,30 +508,19 @@
         gc.set_joinstyle(join)
         gc.set_capstyle(cap)
 
-        if self._newstyle:
-            # transform in backend
-            xt = self._x
-            yt = self._y
-        else:
-            x, y = self._get_plottable()
-            if len(x)==0: return
-            xt, yt = self.get_transform().numerix_x_y(x, y)
-
-
-
         funcname = self._lineStyles.get(self._linestyle, '_draw_nothing')
         lineFunc = getattr(self, funcname)
 
+       # MGDTODO: Deal with self._segments
         if self._segments is not None:
             for ii in self._segments:
                 lineFunc(renderer, gc, xt[ii[0]:ii[1]], yt[ii[0]:ii[1]])
 
         else:
-            lineFunc(renderer, gc, xt, yt)
-
-
-        if self._marker is not None:
-
+            lineFunc(renderer, gc, self._path)
+           
+       # MGDTODO: Deal with markers
+        if self._marker is not None and False:
             gc = renderer.new_gc()
             self._set_gc_clip(gc)
             gc.set_foreground(self.get_markeredgecolor())
@@ -539,7 +528,7 @@
             gc.set_alpha(self._alpha)
             funcname = self._markers.get(self._marker, '_draw_nothing')
             markerFunc = getattr(self, funcname)
-            markerFunc(renderer, gc, xt, yt)
+            markerFunc(renderer, gc, self._path)
 
         #renderer.close_group('line2d')
 
@@ -720,7 +709,7 @@
             self.set_linestyle('--')
         self._dashSeq = seq  # TODO: offset ignored for now
 
-    def _draw_nothing(self, renderer, gc, xt, yt):
+    def _draw_nothing(self, renderer, gc, path):
         pass
 
     def _draw_steps(self, renderer, gc, xt, yt):
@@ -737,13 +726,10 @@
         else:
             renderer.draw_lines(gc, xt2, yt2)
 
-    def _draw_solid(self, renderer, gc, xt, yt):
-        if len(xt)<2: return
+    def _draw_solid(self, renderer, gc, path):
+        # if len(xt)<2: return
         gc.set_linestyle('solid')
-        if self._newstyle:
-            renderer.draw_lines(gc, xt, yt, self.get_transform())
-        else:
-            renderer.draw_lines(gc, xt, yt)
+       renderer.draw_path(gc, path, self.get_transform())
 
 
     def _draw_dashed(self, renderer, gc, xt, yt):
@@ -1103,16 +1089,12 @@
             for (x,y) in zip(xt, yt):
                 renderer.draw_line(gc, x, y, x+offset, y)
 
+    _tickup_path = Path([[-0.5, 0.0], [-0.5, 1.0]])
     def _draw_tickup(self, renderer, gc, xt, yt):
         offset = renderer.points_to_pixels(self._markersize)
-        if self._newstyle:
-            path = agg.path_storage()
-            path.move_to(-0.5, 0)
-            path.line_to(-0.5, offset)
-            renderer.draw_markers(gc, path, None, xt, yt, self.get_transform())
-        else:
-            for (x,y) in zip(xt, yt):
-                renderer.draw_line(gc, x, y, x, y+offset)
+       marker_transform = Affine2D().scale(1.0, offset)
+       renderer.draw_markers(gc, self._tickup_path, marker_transform,
+                             self._path, self.get_transform())
 
     def _draw_tickdown(self, renderer, gc, xt, yt):
         offset = renderer.points_to_pixels(self._markersize)

Modified: branches/transforms/lib/matplotlib/patches.py
===================================================================
--- branches/transforms/lib/matplotlib/patches.py       2007-09-14 13:03:31 UTC 
(rev 3851)
+++ branches/transforms/lib/matplotlib/patches.py       2007-09-14 17:57:52 UTC 
(rev 3852)
@@ -11,8 +11,8 @@
 import matplotlib.nxutils as nxutils
 import matplotlib.mlab as mlab
 import matplotlib.artist as artist
+from matplotlib.path import Path
 
-
 # these are not available for the object inspector until after the
 # class is build so we define an initial set here for the init
 # function and they will be overridden after object defn
@@ -73,7 +73,7 @@
         self._antialiased = antialiased
         self._hatch = hatch
         self.fill = fill
-
+       
         if len(kwargs): artist.setp(self, **kwargs)
     __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
 
@@ -84,8 +84,10 @@
 
         Returns T/F, {}
         """
-        if callable(self._contains): return self._contains(self,mouseevent)
+       # MGDTODO: This will probably need to be implemented in C++
 
+       if callable(self._contains): return self._contains(self,mouseevent)
+
         try:
             # TODO: make this consistent with patch collection algorithm
             x, y = self.get_transform().inverse_xy_tup((mouseevent.x, 
mouseevent.y))
@@ -107,7 +109,6 @@
         self.set_figure(other.get_figure())
         self.set_alpha(other.get_alpha())
 
-
     def get_antialiased(self):
         return self._antialiased
 
@@ -210,22 +211,16 @@
         if self._hatch:
             gc.set_hatch(self._hatch )
 
-        verts = self.get_verts()
-        tverts = self.get_transform()(verts)
+        path = self.get_path()
+        transform = self.get_transform()
 
-       # MGDTODO: This result is an Nx2 numpy array, which could be passed
-       # directly to renderer.draw_polygon since it currently expects
-       # a list of tuples so we're converting it to that now.
-       tverts = [tuple(x) for x in tverts]
-       
-        renderer.draw_polygon(gc, rgbFace, tverts)
+        renderer.draw_path(gc, path, transform, rgbFace)
 
-
         #renderer.close_group('patch')
 
-    def get_verts(self):
+    def get_path(self):
         """
-        Return the vertices of the patch
+        Return the path of this patch
         """
         raise NotImplementedError('Derived must override')
 
@@ -286,9 +281,10 @@
         %(Patch)s
         """
         Patch.__init__(self)
-        self.ox, self.oy = ox, oy
         self.patch = patch
         self.props = props
+       self.ox, self.oy = ox, oy
+       self._shadow_transform = transforms.Affine2D.translate(self.ox, self.oy)
         self._update()
     __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
 
@@ -306,18 +302,13 @@
 
             self.set_facecolor((r,g,b))
             self.set_edgecolor((r,g,b))
+           
+    def get_path(self):
+        return self.patch.get_path()
 
-    def get_verts(self):
-        verts = self.patch.get_verts()
-        xs = self.convert_xunits([x+self.ox for x,y in verts])
-        ys = self.convert_yunits([y+self.oy for x,y in verts])
-        return zip(xs, ys)
-
-    def _draw(self, renderer):
-        'draw the shadow'
-        self._update()
-        Patch.draw(self, renderer)
-
+    def get_transform(self):
+       return self._transform + self._shadow_transform
+    
 class Rectangle(Patch):
     """
     Draw a rectangle with lower left at xy=(x,y) with specified
@@ -325,12 +316,16 @@
 
     """
 
+    _path = Path(
+       [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])
+    
     def __str__(self):
         return str(self.__class__).split('.')[-1] \
             + "(%g,%g;%gx%g)"%(self.xy[0],self.xy[1],self.width,self.height)
 
-    def __init__(self, xy, width, height,
-                 **kwargs):
+    # MGDTODO: Perhaps pass in a Bbox here instead, then the updates will
+    # happen automatically (without needing to call set_x etc.
+    def __init__(self, xy, width, height, **kwargs):
         """
         xy is an x,y tuple lower, left
 
@@ -344,38 +339,41 @@
 
         Patch.__init__(self, **kwargs)
 
-        self.xy  = list(xy)
-        self.width, self.height = width, height
+       self._bbox = transforms.Bbox.from_lbwh(xy[0], xy[1], width, height)
+       self._rect_transform = transforms.BboxTransform(
+           transforms.Bbox.unit(), self._bbox)
     __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
 
-
-    def get_verts(self):
+    def get_path(self):
         """
         Return the vertices of the rectangle
         """
-        x, y = self.xy
-        left, right = self.convert_xunits((x, x + self.width))
-        bottom, top = self.convert_yunits((y, y + self.height))
+       # This is a "class-static" variable, so all rectangles in the plot
+       # will be shared (and merely have different transforms)
+       return self._path
 
-        return npy.array([[left, bottom], [left, top],
-                         [right, top], [right, bottom]],
-                        npy.float_)
+        # MGDTODO: Convert units
+#         left, right = self.convert_xunits((x, x + self.width))
+#         bottom, top = self.convert_yunits((y, y + self.height))
 
+    def get_transform(self):
+       return self._rect_transform + self._transform
+    
     def get_x(self):
         "Return the left coord of the rectangle"
-        return self.xy[0]
+        return self._bbox.xmin
 
     def get_y(self):
         "Return the bottom coord of the rectangle"
-        return self.xy[1]
+        return self._bbox.ymin
 
     def get_width(self):
         "Return the width of the  rectangle"
-        return self.width
+        return self._bbox.width
 
     def get_height(self):
         "Return the height of the rectangle"
-        return self.height
+        return self._bbox.height
 
     def set_x(self, x):
         """
@@ -383,7 +381,7 @@
 
         ACCEPTS: float
         """
-        self.xy[0] = x
+        self._bbox.xmin = x
 
     def set_y(self, y):
         """
@@ -391,7 +389,7 @@
 
         ACCEPTS: float
         """
-        self.xy[1] = y
+        self._bbox.ymin = y
 
     def set_width(self, w):
         """
@@ -399,7 +397,7 @@
 
         ACCEPTS: float
         """
-        self.width = w
+        self._bbox.width = w
 
     def set_height(self, h):
         """
@@ -407,7 +405,7 @@
 
         ACCEPTS: float
         """
-        self.height = h
+        self._bbox.height = h
 
     def set_bounds(self, *args):
         """
@@ -419,15 +417,15 @@
             l,b,w,h = args[0]
         else:
             l,b,w,h = args
-        self.xy = [l,b]
-        self.width = w
-        self.height = h
+       self._bbox.bounds = l,b,w,h
 
 
 class RegularPolygon(Patch):
     """
     A regular polygon patch.
     """
+    _polygon_cache = {}
+    
     def __str__(self):
         return "Poly%d(%g,%g)"%(self.numVertices,self.xy[0],self.xy[1])
 
@@ -444,32 +442,27 @@
         """
         Patch.__init__(self, **kwargs)
 
-        self.xy = list(xy)
-        self.numVertices = numVertices
-        self.radius = radius
-        self.orientation = orientation
+       path = self._polygon_cache[numVertices]
+       if path is None:
+           theta = 2*npy.pi/numVertices * npy.arange(numVertices)
+           verts = npy.hstack((npy.cos(theta), npy.sin(theta)))
+           path = Path(verts)
+           self._polygon_cache[numVertices] = path
 
+       self._path = path
+       self._poly_transform = transforms.Affine2D() \
+           .scale(radius) \
+           .rotate(orientation) \
+           .translate(*xy)
+
     __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
 
+    def get_path(self):
+       return self._path
 
-
-    def get_verts(self):
-        theta = 2*npy.pi/self.numVertices*npy.arange(self.numVertices) + \
-                self.orientation
-        r = float(self.radius)
-        x, y = map(float, self.xy)
-
-        xs = x + r*npy.cos(theta)
-        ys = y + r*npy.sin(theta)
-
-        #xs = self.convert_xunits(xs)
-        #ys = self.convert_yunits(ys)
-
-
-        self.verts = zip(xs, ys)
-
-        return self.verts
-
+    def get_transform(self):
+       return self._poly_transform + self._transform
+       
 class Polygon(Patch):
     """
     A general polygon patch.
@@ -485,22 +478,20 @@
         %(Patch)s
         See Patch documentation for additional kwargs
         """
-
+       # MGDTODO: This should encourage the use of numpy arrays of shape Nx2
         Patch.__init__(self, **kwargs)
         if not isinstance(xy, list):
             xy = list(xy)
-        self.xy = xy
+       self._path = Path(xy, closed=False)
     __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd
 
-
-
     def get_verts(self):
-        xs, ys = zip(*self.xy)[:2]
+       return self._path
+
+        # MGDTODO: Convert units
         xs = self.convert_xunits(xs)
         ys = self.convert_yunits(ys)
-        return zip(xs, ys)
 
-
 class Wedge(Polygon):
     def __str__(self):
         return "Wedge(%g,%g)"%self.xy[0]
@@ -516,6 +507,7 @@
         %(Patch)s
 
         """
+       # MGDTODO: Implement me
         xc, yc = center
         rads = (math.pi/180.)*npy.arange(theta1, theta2+0.1*dtheta, dtheta)
         xs = r*npy.cos(rads)+xc
@@ -543,6 +535,7 @@
         Valid kwargs are:
         %(Patch)s
           """
+       # MGDTODO: Implement me
         arrow = npy.array( [
             [ 0.0,  0.1 ], [ 0.0, -0.1],
             [ 0.8, -0.1 ], [ 0.8, -0.3],
@@ -586,6 +579,7 @@
         %(Patch)s
 
         """
+       # MGDTODO: Implement me
         if head_width is None:
             head_width = 3 * width
         if head_length is None:
@@ -659,6 +653,7 @@
         %(Patch)s
 
         """
+       # MGDTODO: Implement me
         self.dpi = dpi
         self.xytip = xytip
         self.xybase = xybase
@@ -731,6 +726,7 @@
         %(Patch)s
 
         """
+       # MGDTODO: Implement me
         self.center = xy
         self.radius = radius
         RegularPolygon.__init__(self, xy,
@@ -767,6 +763,7 @@
         Valid kwargs are:
         %(Patch)s
         """
+       # MGDTODO: Implement me
         Patch.__init__(self, **kwargs)
 
         # self.center  = npy.array(xy, npy.float)
@@ -833,6 +830,7 @@
         %(Patch)s
 
         """
+       # MGDTODO: Implement me
         if kwargs.has_key('resolution'):
             import warnings
             warnings.warn('Circle is now scale free.  Use CirclePolygon 
instead!', DeprecationWarning)

Added: branches/transforms/lib/matplotlib/path.py
===================================================================
--- branches/transforms/lib/matplotlib/path.py                          (rev 0)
+++ branches/transforms/lib/matplotlib/path.py  2007-09-14 17:57:52 UTC (rev 
3852)
@@ -0,0 +1,50 @@
+import numpy as npy
+
+class Path:
+    # Path codes
+    STOP      = 0
+    MOVETO    = 1 # 1 vertex
+    LINETO    = 2 # 1 vertex
+    CURVE3    = 3 # 2 vertices
+    CURVE4    = 4 # 3 vertices
+    ###
+    # MGDTODO: I'm not sure these are supported by PS/PDF/SVG,
+    # so if they don't, we probably shouldn't
+    CURVEN    = 5
+    CATROM    = 6
+    UBSPLINE  = 7
+    ####
+    CLOSEPOLY = 0x0F # 0 vertices
+
+    code_type = npy.uint8
+    
+    def __init__(self, vertices, codes=None, closed=True):
+       self._vertices = npy.asarray(vertices, npy.float_)
+       assert self._vertices.ndim == 2
+       assert self._vertices.shape[1] == 2
+
+       if codes is None:
+           if closed:
+               codes = self.LINETO * npy.ones(
+                   self._vertices.shape[0] + 1, self.code_type)
+               codes[0] = self.MOVETO
+               codes[-1] = self.CLOSEPOLY
+           else:
+               codes = self.LINETO * npy.ones(
+                   self._vertices.shape[0], self.code_type)
+               codes[0] = self.MOVETO
+        else:
+           codes = npy.asarray(codes, self.code_type)
+       self._codes = codes
+           
+       assert self._codes.ndim == 1
+       # MGDTODO: Maybe we should add some quick-and-dirty check that
+       # the number of vertices is correct for the code array
+
+    def _get_codes(self):
+       return self._codes
+    codes = property(_get_codes)
+
+    def _get_vertices(self):
+       return self._vertices
+    vertices = property(_get_vertices)

Modified: branches/transforms/lib/matplotlib/transforms.py
===================================================================
--- branches/transforms/lib/matplotlib/transforms.py    2007-09-14 13:03:31 UTC 
(rev 3851)
+++ branches/transforms/lib/matplotlib/transforms.py    2007-09-14 17:57:52 UTC 
(rev 3852)
@@ -403,6 +403,9 @@
         Transform.__init__(self)
         self._inverted = None
 
+    def __array__(self):
+       return self.get_matrix()
+       
     def _do_invalidation(self):
         result = self._inverted is None
         self._inverted = None

Modified: branches/transforms/src/_backend_agg.cpp
===================================================================
--- branches/transforms/src/_backend_agg.cpp    2007-09-14 13:03:31 UTC (rev 
3851)
+++ branches/transforms/src/_backend_agg.cpp    2007-09-14 17:57:52 UTC (rev 
3852)
@@ -341,6 +341,7 @@
   
 }
 
+// MGDTODO: Remove this method (it has been conglomerated into draw_path
 template <class VS>
 void
 RendererAgg::_fill_and_stroke(VS& path,
@@ -1399,68 +1400,6 @@
 }
 
 
-
-
-// Py::Object
-// RendererAgg::draw_path(const Py::Tuple& args) {
-//   //draw_path(gc, rgbFace, path, transform)
-//   theRasterizer->reset_clipping();
-  
-//   _VERBOSE("RendererAgg::draw_path");
-//   args.verify_length(4);
-  
-//   GCAgg gc = GCAgg(args[0], dpi);
-//   facepair_t face = _get_rgba_face(args[1], gc.alpha);
-  
-//   agg::path_storage *path;
-//   swig_type_info * descr = SWIG_TypeQuery("agg::path_storage *");
-//   assert(descr);
-//   if (SWIG_ConvertPtr(args[2].ptr(),(void **)(&path), descr, 0) == -1)
-//     throw Py::TypeError("Could not convert path_storage");
-  
-  
-//   Transformation* mpltransform = 
static_cast<Transformation*>(args[3].ptr());
-  
-//   double a, b, c, d, tx, ty;
-//   try {
-//     mpltransform->affine_params_api(&a, &b, &c, &d, &tx, &ty);
-//   }
-//   catch(...) {
-//     throw Py::ValueError("Domain error on affine_params_api in 
RendererAgg::draw_path");
-//   }
-  
-//   agg::trans_affine xytrans = agg::trans_affine(a,b,c,d,tx,ty);
-  
-//   double heightd = double(height);
-//   agg::path_storage tpath;  // the mpl transformed path
-//   bool needNonlinear = mpltransform->need_nonlinear_api();
-//   size_t Nx = path->total_vertices();
-//   double x, y;
-//   unsigned cmd;
-//   bool curvy = false;
-//   for (size_t i=0; i<Nx; i++) {
-//     cmd = path->vertex(i, &x, &y);
-//     if (cmd==agg::path_cmd_curve3 || cmd==agg::path_cmd_curve4) curvy=true;
-//     if (needNonlinear)
-//       try {
-//     mpltransform->nonlinear_only_api(&x, &y);
-//       }
-//       catch (...) {
-//     throw Py::ValueError("Domain error on nonlinear_only_api in 
RendererAgg::draw_path");
-       
-//       }
-    
-//     //use agg's transformer?
-//     xytrans.transform(&x, &y);
-//     y = heightd - y; //flipy
-//     tpath.add_vertex(x,y,cmd);
-//   }
-  
-//   _fill_and_stroke(tpath, gc, face, curvy);
-//   return Py::Object();
-  
-// }
-
 /**
  * This is a custom span generator that converts spans in the 
  * 8-bit inverted greyscale font buffer to rgba that agg can use.
@@ -1613,8 +1552,172 @@
   
 }
 
+inline void get_next_vertex(const char* & vertex_i, const char* vertex_end, 
+                           double& x, double& y,
+                           size_t next_vertex_stride, 
+                           size_t next_axis_stride) {
+  if (vertex_i + next_axis_stride >= vertex_end)
+    throw Py::ValueError("Error parsing path.  Read past end of vertices");
+  x = *(double*)vertex_i;
+  y = *(double*)(vertex_i + next_axis_stride);
+  vertex_i += next_vertex_stride;
+}
 
+#define GET_NEXT_VERTEX(x, y) get_next_vertex(vertex_i, vertex_end, x, y, 
next_vertex_stride, next_axis_stride)
+
+
+
 Py::Object
+RendererAgg::convert_to_native_path(const Py::Tuple& args) {
+  _VERBOSE("RendererAgg::draw_image");
+  args.verify_length(2);
+  
+  Py::Object vertices_obj = args[0];
+  Py::Object codes_obj = args[1];
+  
+  PyArrayObject* vertices = NULL;
+  PyArrayObject* codes = NULL;
+  PathAgg* path = NULL; 
+
+  try {
+    vertices = (PyArrayObject*)PyArray_ContiguousFromObject
+      (vertices_obj.ptr(), PyArray_DOUBLE, 2, 2);
+    if (!vertices || vertices->nd != 2 || vertices->dimensions[1] != 2)
+      throw Py::ValueError("Invalid vertices array.");
+    codes = (PyArrayObject*)PyArray_ContiguousFromObject
+      (codes_obj.ptr(), PyArray_UINT8, 1, 1);
+    if (!codes) 
+      throw Py::ValueError("Invalid codes array.");
+
+    path = new PathAgg();
+
+    size_t next_vertex_stride = vertices->strides[0];
+    size_t next_axis_stride = vertices->strides[1];
+    size_t code_stride = codes->strides[0];
+
+    const char* vertex_i = vertices->data;
+    const char* code_i = codes->data;
+    const char* vertex_end = vertex_i + (vertices->dimensions[0] * 
vertices->strides[0]);
+
+    size_t N = codes->dimensions[0];
+    double x0, y0, x1, y1, x2, y2;
+
+    for (size_t i = 0; i < N; ++i) {
+      switch (*(unsigned char*)(code_i)) {
+      case MOVETO:
+       GET_NEXT_VERTEX(x0, y0);
+       path->move_to(x0, y0);
+       _VERBOSE("MOVETO");
+       break;
+      case LINETO:
+       GET_NEXT_VERTEX(x0, y0);
+       path->line_to(x0, y0);
+       _VERBOSE("LINETO");
+       break;
+      case CURVE3:
+       GET_NEXT_VERTEX(x0, y0);
+       GET_NEXT_VERTEX(x1, y1);
+       path->curve3(x0, y0, x1, y1);
+       path->curvy = true;
+       _VERBOSE("CURVE3");
+       break;
+      case CURVE4:
+       GET_NEXT_VERTEX(x0, y0);
+       GET_NEXT_VERTEX(x1, y1);
+       GET_NEXT_VERTEX(x2, y2);
+       path->curve4(x0, y0, x1, y1, x2, y2);
+       path->curvy = true;
+       _VERBOSE("CURVE4");
+       break;
+      case CLOSEPOLY:
+       path->close_polygon();
+       _VERBOSE("CLOSEPOLY");
+       break;
+      }
+      code_i += code_stride;
+    }
+  } catch(...) {
+    Py_XDECREF(vertices);
+    Py_XDECREF(codes);
+    delete path;
+    throw;
+  }
+
+  Py_XDECREF(vertices);
+  Py_XDECREF(codes);
+  
+  return Py::asObject(path);
+}
+
+Py::Object
+RendererAgg::draw_path(const Py::Tuple& args) {
+  typedef agg::conv_transform<agg::path_storage> transformed_path_t;
+  typedef agg::conv_curve<transformed_path_t> curve_t;
+  typedef agg::conv_stroke<curve_t> stroke_t;
+  typedef agg::conv_dash<curve_t> dash_t;
+  typedef agg::conv_stroke<dash_t> stroke_dash_t;
+  //draw_path(gc, rgbFace, path, transform)
+  theRasterizer->reset_clipping();
+  
+  _VERBOSE("RendererAgg::draw_path");
+  args.verify_length(4);
+
+  GCAgg gc = GCAgg(args[0], dpi);
+  Py::Object path_obj = args[1];
+  if (!PathAgg::check(path_obj))
+    throw Py::TypeError("Native path object is not of correct type");
+  PathAgg* path = static_cast<PathAgg*>(path_obj.ptr());
+  agg::trans_affine trans = py_sequence_to_agg_transformation_matrix(args[2]);
+  facepair_t face = _get_rgba_face(args[3], gc.alpha);
+
+  trans *= agg::trans_affine_scaling(1.0, -1.0);
+  trans *= agg::trans_affine_translation(0.0, (double)height);
+
+  transformed_path_t tpath(*path, trans);
+  // MGDTODO: See if there is any advantage to only curving if necessary
+  curve_t curve(tpath);
+
+  set_clipbox_rasterizer(gc.cliprect);
+  
+  if (face.first) {
+    rendererAA->color(face.second);
+    theRasterizer->add_path(curve);
+    agg::render_scanlines(*theRasterizer, *slineP8, *rendererAA);
+  }
+
+  if (gc.linewidth) {
+    if (gc.dasha == NULL) {
+      stroke_t stroke(curve);
+      stroke.width(gc.linewidth);
+      stroke.line_cap(gc.cap);
+      stroke.line_join(gc.join);
+      theRasterizer->add_path(stroke);
+    } else {
+      dash_t dash(curve);
+      for (size_t i = 0; i < (gc.Ndash / 2); ++i)
+       dash.add_dash(gc.dasha[2 * i], gc.dasha[2 * i + 1]);
+      stroke_dash_t stroke(dash);
+      stroke.line_cap(gc.cap);
+      stroke.line_join(gc.join);
+      stroke.width(gc.linewidth);
+      theRasterizer->add_path(stroke); //boyle freeze is herre
+    }
+    
+    if ( gc.isaa ) {
+      rendererAA->color(gc.color);
+      agg::render_scanlines(*theRasterizer, *slineP8, *rendererAA);
+    }
+    else {
+      rendererBin->color(gc.color);
+      agg::render_scanlines(*theRasterizer, *slineBin, *rendererBin);
+    }
+  }
+  
+  return Py::Object();
+}
+
+
+Py::Object
 RendererAgg::write_rgba(const Py::Tuple& args) {
   _VERBOSE("RendererAgg::write_rgba");
   
@@ -1949,6 +2052,10 @@
                     "draw_ellipse(gc, rgbFace, x, y, w, h)\n");
   add_varargs_method("draw_polygon", &RendererAgg::draw_polygon,
                     "draw_polygon(gc, rgbFace, points)\n");
+  add_varargs_method("draw_path", &RendererAgg::draw_path,
+                    "draw_path(gc, rgbFace, native_path, transform)\n");
+  add_varargs_method("convert_to_native_path", 
&RendererAgg::convert_to_native_path,
+                    "convert_to_native_path(vertices, codes)\n");
   add_varargs_method("draw_lines", &RendererAgg::draw_lines,
                     "draw_lines(gc, x, y,)\n");
   add_varargs_method("draw_markers", &RendererAgg::draw_markers,
@@ -1976,10 +2083,13 @@
   
   add_varargs_method("restore_region", &RendererAgg::restore_region,
                     "restore_region(region)");
-  
-  
 }
 
+void PathAgg::init_type()
+{
+  behaviors().name("PathAgg");
+  behaviors().doc("A native Agg path object");
+}
 
 extern "C"
 DL_EXPORT(void)

Modified: branches/transforms/src/_backend_agg.h
===================================================================
--- branches/transforms/src/_backend_agg.h      2007-09-14 13:03:31 UTC (rev 
3851)
+++ branches/transforms/src/_backend_agg.h      2007-09-14 17:57:52 UTC (rev 
3852)
@@ -39,6 +39,14 @@
 #include "agg_scanline_p.h"
 #include "agg_vcgen_markers_term.h"
 
+// These are copied directly from path.py, and must be kept in sync
+#define STOP 0
+#define MOVETO 1
+#define LINETO 2
+#define CURVE3 3
+#define CURVE4 4
+#define CLOSEPOLY 0x0F
+
 typedef agg::pixfmt_rgba32 pixfmt;
 typedef agg::renderer_base<pixfmt> renderer_base;
 typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_aa;
@@ -163,6 +171,8 @@
   Py::Object draw_markers(const Py::Tuple & args);
   Py::Object draw_text_image(const Py::Tuple & args);
   Py::Object draw_image(const Py::Tuple & args);
+  Py::Object draw_path(const Py::Tuple & args);
+  Py::Object convert_to_native_path(const Py::Tuple & args);
 
   Py::Object write_rgba(const Py::Tuple & args);
   Py::Object write_png(const Py::Tuple & args);
@@ -229,7 +239,22 @@
   agg::path_storage *lastclippath;
 };
 
+// A completely opaque data type used only to pass native path
+// data to/from Python.  Python can't do anything with the data
+// other than create and then use it.
+class PathAgg : 
+  public agg::path_storage, 
+  public Py::PythonExtension<PathAgg> {
 
+public:
+  PathAgg() : curvy(false) {}
+
+  static void init_type(void);
+
+  bool curvy;
+};
+
+
 // the extension module
 class _backend_agg_module : public Py::ExtensionModule<_backend_agg_module>
 {
@@ -240,6 +265,7 @@
 
     BufferRegion::init_type();
     RendererAgg::init_type();
+    PathAgg::init_type();
 
     add_keyword_method("RendererAgg", &_backend_agg_module::new_renderer,
                       "RendererAgg(width, height, dpi)");


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to