|
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.
|