From: <md...@us...> - 2007-09-14 18:01:00
|
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. |