From: <md...@us...> - 2009-01-29 16:51:17
|
Revision: 6847 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6847&view=rev Author: mdboom Date: 2009-01-29 16:51:12 +0000 (Thu, 29 Jan 2009) Log Message: ----------- Rework the nan-handling/clipping/quantizing/simplification framework so each is an independent part of a pipeline. Expose the C++-implementation of all of this so it can be used from all Python backends. Add rcParam "path.simplify_threshold" to control the threshold of similarity below which vertices will be removed. Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/examples/api/quad_bezier.py trunk/matplotlib/examples/pylab_examples/simplification_clipping_test.py trunk/matplotlib/lib/matplotlib/backend_bases.py trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py trunk/matplotlib/lib/matplotlib/backends/backend_ps.py trunk/matplotlib/lib/matplotlib/backends/backend_svg.py trunk/matplotlib/lib/matplotlib/backends/backend_wx.py trunk/matplotlib/lib/matplotlib/config/mplconfig.py trunk/matplotlib/lib/matplotlib/config/rcsetup.py trunk/matplotlib/lib/matplotlib/path.py trunk/matplotlib/lib/matplotlib/rcsetup.py trunk/matplotlib/matplotlibrc.template trunk/matplotlib/src/_backend_agg.cpp trunk/matplotlib/src/_backend_agg.h trunk/matplotlib/src/_macosx.m trunk/matplotlib/src/_path.cpp trunk/matplotlib/src/agg_py_path_iterator.h Added Paths: ----------- trunk/matplotlib/src/path_converters.h Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/CHANGELOG 2009-01-29 16:51:12 UTC (rev 6847) @@ -1,3 +1,10 @@ +2009-01-29 Rework the nan-handling/clipping/quantizing/simplification + framework so each is an independent part of a pipeline. + Expose the C++-implementation of all of this so it can be + used from all Python backends. Add rcParam + "path.simplify_threshold" to control the threshold of + similarity below which vertices will be removed. + 2009-01-26 Improved tight bbox option of the savefig. - JJL 2009-01-26 Make curves and NaNs play nice together - MGD Modified: trunk/matplotlib/examples/api/quad_bezier.py =================================================================== --- trunk/matplotlib/examples/api/quad_bezier.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/examples/api/quad_bezier.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -3,10 +3,13 @@ import matplotlib.patches as mpatches import matplotlib.pyplot as plt +Path = mpath.Path + fig = plt.figure() ax = fig.add_subplot(111) pp1 = mpatches.PathPatch( - mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)], [1, 3, 3, 5]), + Path([(0, 0), (1, 0), (1, 1), (0, 0)], + [Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY]), fc="none", transform=ax.transData) ax.add_patch(pp1) Modified: trunk/matplotlib/examples/pylab_examples/simplification_clipping_test.py =================================================================== --- trunk/matplotlib/examples/pylab_examples/simplification_clipping_test.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/examples/pylab_examples/simplification_clipping_test.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -1,4 +1,5 @@ from pylab import * +import numpy as np t = arange(0.0, 2.0, 0.01) s = sin(2*pi*t) @@ -7,5 +8,51 @@ ylim((-0.20, -0.28)) title('Should see four lines extending from bottom to top') -grid(True) + +figure() + +x = np.array([1.0,2.0,3.0,2.0e5]) +y = np.arange(len(x)) +plot(x,y) +xlim(xmin=2,xmax=6) +title("Should be monotonically increasing") + +figure() + +x = np.array([0.0, 1.0, 0.0, -1.0, 0.0]) +y = np.array([1.0, 0.0, -1.0, 0.0, 1.0]) +plot(x, y) +xlim(xmin=-0.6, xmax=0.6) +ylim(ymin=-0.6, ymax=0.6) +title("Diamond shape, with segments visible in all four corners") + +figure() + +np.random.seed(0) +x = np.random.uniform(size=(5000,)) * 50 + +rcParams['path.simplify'] = True +p1 = plot(x,solid_joinstyle='round',linewidth=2.0) + +path = p1[0].get_path() +transform = p1[0].get_transform() +path = transform.transform_path(path) +simplified = list(path.iter_segments(simplify=(800, 600))) + +title("Original length: %d, simplified length: %d" % (len(path.vertices), len(simplified))) + +figure() + +x = np.sin(np.linspace(0, np.pi * 2.0, 1000)) + np.random.uniform(size=(1000,)) * 0.01 + +rcParams['path.simplify'] = True +p1 = plot(x,solid_joinstyle='round',linewidth=2.0) + +path = p1[0].get_path() +transform = p1[0].get_transform() +path = transform.transform_path(path) +simplified = list(path.iter_segments(simplify=(800, 600))) + +title("Original length: %d, simplified length: %d" % (len(path.vertices), len(simplified))) + show() Modified: trunk/matplotlib/lib/matplotlib/backend_bases.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -99,8 +99,7 @@ want to override this method in order to draw the marker only once and reuse it multiple times. """ - tpath = trans.transform_path(path) - for vertices, codes in tpath.iter_segments(): + for vertices, codes in path.iter_segments(trans, simplify=False): if len(vertices): x,y = vertices[-2:] self.draw_path(gc, marker_path, Modified: trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -122,8 +122,8 @@ @staticmethod - def convert_path(ctx, tpath): - for points, code in tpath.iter_segments(): + def convert_path(ctx, path, transform): + for points, code in path.iter_segments(transform): if code == Path.MOVETO: ctx.move_to(*points) elif code == Path.LINETO: @@ -145,10 +145,9 @@ ctx = gc.ctx transform = transform + \ Affine2D().scale(1.0, -1.0).translate(0, self.height) - tpath = transform.transform_path(path) ctx.new_path() - self.convert_path(ctx, tpath) + self.convert_path(ctx, path, transform) self._fill_and_stroke(ctx, rgbFace, gc.get_alpha()) @@ -343,8 +342,7 @@ ctx = self.ctx ctx.new_path() affine = affine + Affine2D().scale(1.0, -1.0).translate(0.0, self.renderer.height) - tpath = affine.transform_path(tpath) - RendererCairo.convert_path(ctx, tpath) + RendererCairo.convert_path(ctx, path, affine) ctx.clip() Modified: trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -415,10 +415,6 @@ self.endStream() self.width, self.height = width, height - if rcParams['path.simplify']: - self.simplify = (width * 72, height * 72) - else: - self.simplify = None contentObject = self.reserveObject('page contents') thePage = { 'Type': Name('Page'), 'Parent': self.pagesObject, @@ -1140,12 +1136,10 @@ self.endStream() @staticmethod - def pathOperations(path, transform, simplify=None): - tpath = transform.transform_path(path) - + def pathOperations(path, transform, clip=None): cmds = [] last_points = None - for points, code in tpath.iter_segments(simplify): + for points, code in path.iter_segments(transform, clip=clip): if code == Path.MOVETO: cmds.extend(points) cmds.append(Op.moveto) @@ -1164,8 +1158,12 @@ last_points = points return cmds - def writePath(self, path, transform): - cmds = self.pathOperations(path, transform, self.simplify) + def writePath(self, path, transform, clip=False): + if clip: + clip = (0.0, 0.0, self.width * 72, self.height * 72) + else: + clip = None + cmds = self.pathOperations(path, transform, clip) self.output(*cmds) def reserveObject(self, name=''): @@ -1282,7 +1280,7 @@ def draw_path(self, gc, path, transform, rgbFace=None): self.check_gc(gc, rgbFace) - stream = self.file.writePath(path, transform) + stream = self.file.writePath(path, transform, rgbFace is None) self.file.output(self.gc.paint()) def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): @@ -1292,11 +1290,10 @@ output = self.file.output marker = self.file.markerObject( marker_path, marker_trans, fillp, self.gc._linewidth) - tpath = trans.transform_path(path) output(Op.gsave) lastx, lasty = 0, 0 - for vertices, code in tpath.iter_segments(): + for vertices, code in path.iter_segments(trans, simplify=False): if len(vertices): x, y = vertices[-2:] dx, dy = x - lastx, y - lasty @@ -1796,9 +1793,9 @@ if self._cliprect != cliprect: cmds.extend([cliprect, Op.rectangle, Op.clip, Op.endpath]) if self._clippath != clippath: + path, affine = clippath.get_transformed_path_and_affine() cmds.extend( - PdfFile.pathOperations( - *clippath.get_transformed_path_and_affine()) + + PdfFile.pathOperations(path, affine) + [Op.clip, Op.endpath]) return cmds Modified: trunk/matplotlib/lib/matplotlib/backends/backend_ps.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_ps.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/backends/backend_ps.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -150,10 +150,6 @@ self.textcnt = 0 self.psfrag = [] self.imagedpi = imagedpi - if rcParams['path.simplify']: - self.simplify = (width * imagedpi, height * imagedpi) - else: - self.simplify = None # current renderer state (None=uninitialised) self.color = None @@ -428,12 +424,15 @@ # unflip im.flipud_out() - def _convert_path(self, path, transform, simplify=None): - path = transform.transform_path(path) - + def _convert_path(self, path, transform, clip=False): ps = [] last_points = None - for points, code in path.iter_segments(simplify): + if clip: + clip = (0.0, 0.0, self.width * self.imagedpi, + self.height * self.imagedpi) + else: + clip = None + for points, code in path.iter_segments(transform, clip=clip): if code == Path.MOVETO: ps.append("%g %g m" % tuple(points)) elif code == Path.LINETO: @@ -466,7 +465,7 @@ """ Draws a Path instance using the given affine transform. """ - ps = self._convert_path(path, transform, self.simplify) + ps = self._convert_path(path, transform, clip=(rgbFace is None)) self._draw_ps(ps, gc, rgbFace) def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): @@ -494,8 +493,7 @@ ps_cmd.extend(['stroke', 'grestore', '} bind def']) - tpath = trans.transform_path(path) - for vertices, code in tpath.iter_segments(): + for vertices, code in path.iter_segments(trans, simplify=False): if len(vertices): x, y = vertices[-2:] ps_cmd.append("%g %g o" % (x, y)) Modified: trunk/matplotlib/lib/matplotlib/backends/backend_svg.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_svg.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/backends/backend_svg.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -42,10 +42,6 @@ self.width=width self.height=height self._svgwriter = svgwriter - if rcParams['path.simplify']: - self.simplify = (width, height) - else: - self.simplify = None self._groupd = {} if not rcParams['svg.image_inline']: @@ -209,14 +205,16 @@ .scale(1.0, -1.0) .translate(0.0, self.height)) - def _convert_path(self, path, transform, simplify=None): - tpath = transform.transform_path(path) - + def _convert_path(self, path, transform, clip=False): path_data = [] appender = path_data.append path_commands = self._path_commands currpos = 0 - for points, code in tpath.iter_segments(simplify): + if clip: + clip = (0.0, 0.0, self.width, self.height) + else: + clip = None + for points, code in path.iter_segments(transform, clip=clip): if code == Path.CLOSEPOLY: segment = 'z' else: @@ -231,7 +229,7 @@ def draw_path(self, gc, path, transform, rgbFace=None): trans_and_flip = self._make_flip_transform(transform) - path_data = self._convert_path(path, trans_and_flip, self.simplify) + path_data = self._convert_path(path, trans_and_flip, clip=(rgbFace is None)) self._draw_svg_element('path', 'd="%s"' % path_data, gc, rgbFace) def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): @@ -252,8 +250,7 @@ write('<g %s>' % clippath) trans_and_flip = self._make_flip_transform(trans) - tpath = trans_and_flip.transform_path(path) - for vertices, code in tpath.iter_segments(): + for vertices, code in path.iter_segments(trans_and_flip, simplify=False): if len(vertices): x, y = vertices[-2:] details = 'xlink:href="#%s" x="%f" y="%f"' % (name, x, y) Modified: trunk/matplotlib/lib/matplotlib/backends/backend_wx.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_wx.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/backends/backend_wx.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -304,9 +304,9 @@ new_bounds[2], new_bounds[3]) @staticmethod - def convert_path(gfx_ctx, tpath): + def convert_path(gfx_ctx, path, transform): wxpath = gfx_ctx.CreatePath() - for points, code in tpath.iter_segments(): + for points, code in path.iter_segments(transform): if code == Path.MOVETO: wxpath.MoveToPoint(*points) elif code == Path.LINETO: @@ -324,8 +324,7 @@ self.handle_clip_rectangle(gc) gfx_ctx = gc.gfx_ctx transform = transform + Affine2D().scale(1.0, -1.0).translate(0.0, self.height) - tpath = transform.transform_path(path) - wxpath = self.convert_path(gfx_ctx, tpath) + wxpath = self.convert_path(gfx_ctx, path, transform) if rgbFace is not None: gfx_ctx.SetBrush(wx.Brush(gc.get_wxcolour(rgbFace))) gfx_ctx.DrawPath(wxpath) Modified: trunk/matplotlib/lib/matplotlib/config/mplconfig.py =================================================================== --- trunk/matplotlib/lib/matplotlib/config/mplconfig.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/config/mplconfig.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -118,6 +118,7 @@ class path(TConfig): simplify = T.false + simplify_threshold = T.float(1.0 / 9.0) class patch(TConfig): linewidth = T.Float(1.0) @@ -442,7 +443,8 @@ 'svg.embed_char_paths' : (self.tconfig.backend.svg, 'embed_char_paths'), # Path properties - 'path.simplify' : (self.tconfig.path, 'simplify') + 'path.simplify' : (self.tconfig.path, 'simplify'), + 'path.simplify_threshold' : (self.tconfig.path, 'simplify_threshold') } def __setitem__(self, key, val): Modified: trunk/matplotlib/lib/matplotlib/config/rcsetup.py =================================================================== --- trunk/matplotlib/lib/matplotlib/config/rcsetup.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/config/rcsetup.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -479,7 +479,8 @@ 'svg.embed_char_paths' : [True, validate_bool], # True to save all characters as paths in the SVG 'plugins.directory' : ['.matplotlib_plugins', str], # where plugin directory is locate - 'path.simplify' : [True, validate_bool] + 'path.simplify' : [True, validate_bool], + 'path.simplify_threshold' : [1.0 / 9.0, ValidateInterval(0.0, 1.0)] } if __name__ == '__main__': Modified: trunk/matplotlib/lib/matplotlib/path.py =================================================================== --- trunk/matplotlib/lib/matplotlib/path.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/path.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -10,7 +10,8 @@ from matplotlib._path import point_in_path, get_path_extents, \ point_in_path_collection, get_path_collection_extents, \ - path_in_path, path_intersects_path, convert_path_to_polygons + path_in_path, path_intersects_path, convert_path_to_polygons, \ + cleanup_path from matplotlib.cbook import simple_linear_interpolation, maxdict from matplotlib import rcParams @@ -65,14 +66,17 @@ """ # Path codes - STOP = 0 # 1 vertex - MOVETO = 1 # 1 vertex - LINETO = 2 # 1 vertex - CURVE3 = 3 # 2 vertices - CURVE4 = 4 # 3 vertices - CLOSEPOLY = 5 # 1 vertex + STOP = 0 # 1 vertex + MOVETO = 1 # 1 vertex + LINETO = 2 # 1 vertex + CURVE3 = 3 # 2 vertices + CURVE4 = 4 # 3 vertices + CLOSEPOLY = 0x4f # 1 vertex - NUM_VERTICES = [1, 1, 1, 2, 3, 1] + NUM_VERTICES = [1, 1, 1, 2, + 3, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1] code_type = np.uint8 @@ -113,6 +117,7 @@ self.should_simplify = (rcParams['path.simplify'] and (len(vertices) >= 128 and (codes is None or np.all(codes <= Path.LINETO)))) + self.simplify_threshold = rcParams['path.simplify_threshold'] self.has_nonfinite = not np.isfinite(vertices).all() self.codes = codes self.vertices = vertices @@ -146,31 +151,43 @@ def __len__(self): return len(self.vertices) - def iter_segments(self, simplify=None): + def iter_segments(self, transform=None, remove_nans=True, clip=None, + quantize=False, simplify=None, curves=True): """ Iterates over all of the curve segments in the path. Each iteration returns a 2-tuple (*vertices*, *code*), where *vertices* is a sequence of 1 - 3 coordinate pairs, and *code* is one of the :class:`Path` codes. - If *simplify* is provided, it must be a tuple (*width*, - *height*) defining the size of the figure, in native units - (e.g. pixels or points). Simplification implies both removing - adjacent line segments that are very close to parallel, and - removing line segments outside of the figure. The path will - be simplified *only* if :attr:`should_simplify` is True, which - is determined in the constructor by this criteria: + Additionally, this method can provide a number of standard + cleanups and conversions to the path. - - No curves - - More than 128 vertices + *transform*: if not None, the given affine transformation will + be applied to the path. + + *remove_nans*: if True, will remove all NaNs from the path and + insert MOVETO commands to skip over them. + + *clip*: if not None, must be a four-tuple (x1, y1, x2, y2) + defining a rectangle in which to clip the path. + + *quantize*: if None, auto-quantize. If True, force quantize, + and if False, don't quantize. + + *simplify*: if True, perform simplification, to remove + vertices that do not affect the appearance of the path. If + False, perform no simplification. If None, use the + should_simplify member variable. + + *curves*: If True, curve segments will be returned as curve + segments. If False, all curves will be converted to line + segments. """ vertices = self.vertices if not len(vertices): return codes = self.codes - len_vertices = len(vertices) - isfinite = np.isfinite NUM_VERTICES = self.NUM_VERTICES MOVETO = self.MOVETO @@ -178,47 +195,20 @@ CLOSEPOLY = self.CLOSEPOLY STOP = self.STOP - if simplify is not None and self.should_simplify: - polygons = self.to_polygons(None, *simplify) - for vertices in polygons: - yield vertices[0], MOVETO - for v in vertices[1:]: - yield v, LINETO - elif codes is None: - if self.has_nonfinite: - next_code = MOVETO - for v in vertices: - if np.isfinite(v).all(): - yield v, next_code - next_code = LINETO - else: - next_code = MOVETO + vertices, codes = cleanup_path(self, transform, remove_nans, clip, + quantize, simplify, curves) + len_vertices = len(vertices) + + i = 0 + while i < len_vertices: + code = codes[i] + if code == STOP: + return else: - yield vertices[0], MOVETO - for v in vertices[1:]: - yield v, LINETO - else: - i = 0 - was_nan = False - while i < len_vertices: - code = codes[i] - if code == CLOSEPOLY: - yield [], code - i += 1 - elif code == STOP: - return - else: - num_vertices = NUM_VERTICES[int(code)] - curr_vertices = vertices[i:i+num_vertices].flatten() - if not isfinite(curr_vertices).all(): - was_nan = True - elif was_nan: - yield curr_vertices[:2], MOVETO - yield curr_vertices, code - was_nan = False - else: - yield curr_vertices, code - i += num_vertices + num_vertices = NUM_VERTICES[int(code) & 0xf] + curr_vertices = vertices[i:i+num_vertices].flatten() + yield curr_vertices, code + i += num_vertices def transformed(self, transform): """ Modified: trunk/matplotlib/lib/matplotlib/rcsetup.py =================================================================== --- trunk/matplotlib/lib/matplotlib/rcsetup.py 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/lib/matplotlib/rcsetup.py 2009-01-29 16:51:12 UTC (rev 6847) @@ -520,6 +520,7 @@ 'plugins.directory' : ['.matplotlib_plugins', str], # where plugin directory is locate 'path.simplify' : [True, validate_bool], + 'path.simplify_threshold' : [1.0 / 9.0, ValidateInterval(0.0, 1.0)], 'agg.path.chunksize' : [0, validate_int] # 0 to disable chunking; # recommend about 20000 to # enable. Experimental. Modified: trunk/matplotlib/matplotlibrc.template =================================================================== --- trunk/matplotlib/matplotlibrc.template 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/matplotlibrc.template 2009-01-29 16:51:12 UTC (rev 6847) @@ -290,7 +290,11 @@ # A value of 20000 is probably a good # starting point. ### SAVING FIGURES -#path.simplify : False # When True, simplify paths in vector backends, such as PDF, PS and SVG +#path.simplify : False # When True, simplify paths in vector backends, such as + # PDF, PS and SVG +#path.simplify_threshold : 0.1 # The threshold of similarity below which + # vertices will be removed in the simplification + # process # the default savefig params can be different from the display params # Eg, you may want a higher resolution, or to make the figure Modified: trunk/matplotlib/src/_backend_agg.cpp =================================================================== --- trunk/matplotlib/src/_backend_agg.cpp 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/src/_backend_agg.cpp 2009-01-29 16:51:12 UTC (rev 6847) @@ -267,11 +267,11 @@ Py::Callable method(method_obj); Py::Object py_snap = method.apply(Py::Tuple()); if (py_snap.isNone()) { - snap = SNAP_AUTO; + quantize_mode = QUANTIZE_AUTO; } else if (py_snap.isTrue()) { - snap = SNAP_TRUE; + quantize_mode = QUANTIZE_TRUE; } else { - snap = SNAP_FALSE; + quantize_mode = QUANTIZE_FALSE; } } @@ -366,55 +366,6 @@ return face; } -template<class Path> -bool should_snap(GCAgg& gc, Path& path, const agg::trans_affine& trans) { - // If this contains only straight horizontal or vertical lines, it should be - // quantized to the nearest pixels - double x0, y0, x1, y1; - unsigned code; - - switch (gc.snap) { - case GCAgg::SNAP_AUTO: - if (path.total_vertices() > 15) - return false; - - code = path.vertex(&x0, &y0); - if (code == agg::path_cmd_stop) { - path.rewind(0); - return false; - } - trans.transform(&x0, &y0); - - while ((code = path.vertex(&x1, &y1)) != agg::path_cmd_stop) { - trans.transform(&x1, &y1); - - switch (code) { - case agg::path_cmd_curve3: - case agg::path_cmd_curve4: - path.rewind(0); - return false; - case agg::path_cmd_line_to: - if (!(fabs(x0 - x1) < 1e-4 || fabs(y0 - y1) < 1e-4)) { - path.rewind(0); - return false; - } - } - - x0 = x1; - y0 = y1; - } - - path.rewind(0); - gc.isaa = false; - return true; - case GCAgg::SNAP_FALSE: - return false; - case GCAgg::SNAP_TRUE: - return true; - } - return false; -} - Py::Object RendererAgg::copy_from_bbox(const Py::Tuple& args) { //copy region in bbox to buffer and return swig/agg buffer object @@ -509,8 +460,8 @@ Py::Object RendererAgg::draw_markers(const Py::Tuple& args) { typedef agg::conv_transform<PathIterator> transformed_path_t; - typedef SimplifyPath<transformed_path_t> simplify_t; - typedef agg::conv_curve<simplify_t> curve_t; + typedef PathQuantizer<transformed_path_t> quantize_t; + typedef agg::conv_curve<quantize_t> curve_t; typedef agg::conv_stroke<curve_t> stroke_t; typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type; typedef agg::renderer_base<pixfmt_amask_type> amask_ren_type; @@ -534,15 +485,12 @@ trans *= agg::trans_affine_scaling(1.0, -1.0); trans *= agg::trans_affine_translation(0.0, (double)height); - PathIterator marker_path(marker_path_obj); - // The built-in markers look better if snapping is turned on, but - // unfortunately, it can cause really small things to disappear. - // Disabling for now to revisit at a later date. - // const bool marker_snap = true; - bool marker_snap = should_snap(gc, marker_path, marker_trans); + PathIterator marker_path(marker_path_obj); transformed_path_t marker_path_transformed(marker_path, marker_trans); - simplify_t marker_path_simplified(marker_path_transformed, marker_snap, false, width, height); - curve_t marker_path_curve(marker_path_simplified); + quantize_t marker_path_quantized(marker_path_transformed, + gc.quantize_mode, + marker_path.total_vertices()); + curve_t marker_path_curve(marker_path_quantized); PathIterator path(path_obj); transformed_path_t path_transformed(path, trans); @@ -900,8 +848,7 @@ // Create and transform the path typedef agg::conv_transform<PathIterator> hatch_path_trans_t; - typedef SimplifyPath<hatch_path_trans_t> hatch_path_simplify_t; - typedef agg::conv_curve<hatch_path_simplify_t> hatch_path_curve_t; + typedef agg::conv_curve<hatch_path_trans_t> hatch_path_curve_t; typedef agg::conv_stroke<hatch_path_curve_t> hatch_path_stroke_t; PathIterator hatch_path(gc.hatchpath); @@ -910,8 +857,7 @@ hatch_trans *= agg::trans_affine_translation(0.0, 1.0); hatch_trans *= agg::trans_affine_scaling(HATCH_SIZE, HATCH_SIZE); hatch_path_trans_t hatch_path_trans(hatch_path, hatch_trans); - hatch_path_simplify_t hatch_path_simplify(hatch_path_trans, false, false, HATCH_SIZE, HATCH_SIZE); - hatch_path_curve_t hatch_path_curve(hatch_path_simplify); + hatch_path_curve_t hatch_path_curve(hatch_path_trans); hatch_path_stroke_t hatch_path_stroke(hatch_path_curve); hatch_path_stroke.width(1.0); hatch_path_stroke.line_cap(agg::square_cap); @@ -1005,9 +951,12 @@ Py::Object RendererAgg::draw_path(const Py::Tuple& args) { - typedef agg::conv_transform<PathIterator> transformed_path_t; - typedef SimplifyPath<transformed_path_t> simplify_t; - typedef agg::conv_curve<simplify_t> curve_t; + typedef agg::conv_transform<PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + typedef PathClipper<nan_removed_t> clipped_t; + typedef PathQuantizer<clipped_t> quantized_t; + typedef PathSimplifier<quantized_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; _VERBOSE("RendererAgg::draw_path"); args.verify_length(3, 4); @@ -1030,12 +979,15 @@ trans *= agg::trans_affine_scaling(1.0, -1.0); trans *= agg::trans_affine_translation(0.0, (double)height); - bool snap = should_snap(gc, path, trans); + bool clip = !face.first; bool simplify = path.should_simplify() && !face.first; transformed_path_t tpath(path, trans); - simplify_t simplified(tpath, snap, simplify, width, height); - curve_t curve(simplified); + nan_removed_t nan_removed(tpath, true, path.has_curves()); + clipped_t clipped(nan_removed, clip, width, height); + quantized_t quantized(clipped, gc.quantize_mode, path.total_vertices()); + simplify_t simplified(quantized, simplify, path.simplify_threshold()); + curve_t curve(simplified); try { _draw_path(curve, has_clippath, face, gc); @@ -1063,9 +1015,11 @@ const Py::SeqBase<Py::Object>& linestyles_obj, const Py::SeqBase<Py::Int>& antialiaseds) { typedef agg::conv_transform<typename PathGenerator::path_iterator> transformed_path_t; - typedef SimplifyPath<transformed_path_t> simplify_t; - typedef agg::conv_curve<simplify_t> simplified_curve_t; - typedef agg::conv_curve<transformed_path_t> curve_t; + typedef PathNanRemover<transformed_path_t> nan_removed_t; + typedef PathClipper<nan_removed_t> clipped_t; + typedef PathQuantizer<clipped_t> quantized_t; + typedef agg::conv_curve<quantized_t> quantized_curve_t; + typedef agg::conv_curve<clipped_t> curve_t; GCAgg gc(dpi); @@ -1144,7 +1098,6 @@ facepair_t face; face.first = Nfacecolors != 0; agg::trans_affine trans; - bool snap = false; for (i = 0; i < N; ++i) { typename PathGenerator::path_iterator path = path_generator(i); @@ -1192,26 +1145,29 @@ } if (check_snap) { - snap = should_snap(gc, path, trans); gc.isaa = bool(Py::Int(antialiaseds[i % Naa])); transformed_path_t tpath(path, trans); - simplify_t simplified(tpath, snap, false, width, height); + nan_removed_t nan_removed(tpath, true, has_curves); + clipped_t clipped(nan_removed, !face.first, width, height); + quantized_t quantized(clipped, gc.quantize_mode, path.total_vertices()); if (has_curves) { - simplified_curve_t curve(simplified); + quantized_curve_t curve(quantized); _draw_path(curve, has_clippath, face, gc); } else { - _draw_path(simplified, has_clippath, face, gc); + _draw_path(quantized, has_clippath, face, gc); } } else { gc.isaa = bool(Py::Int(antialiaseds[i % Naa])); transformed_path_t tpath(path, trans); + nan_removed_t nan_removed(tpath, true, has_curves); + clipped_t clipped(nan_removed, !face.first, width, height); if (has_curves) { - curve_t curve(tpath); + curve_t curve(clipped); _draw_path(curve, has_clippath, face, gc); } else { - _draw_path(tpath, has_clippath, face, gc); + _draw_path(clipped, has_clippath, face, gc); } } } Modified: trunk/matplotlib/src/_backend_agg.h =================================================================== --- trunk/matplotlib/src/_backend_agg.h 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/src/_backend_agg.h 2009-01-29 16:51:12 UTC (rev 6847) @@ -39,6 +39,7 @@ #include "agg_vcgen_markers_term.h" #include "agg_py_path_iterator.h" +#include "path_converters.h" // These are copied directly from path.py, and must be kept in sync #define STOP 0 @@ -121,13 +122,8 @@ typedef std::vector<std::pair<double, double> > dash_t; double dashOffset; dash_t dashes; + e_quantize_mode quantize_mode; - enum { - SNAP_AUTO, - SNAP_FALSE, - SNAP_TRUE - } snap; - Py::Object hatchpath; protected: Modified: trunk/matplotlib/src/_macosx.m =================================================================== --- trunk/matplotlib/src/_macosx.m 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/src/_macosx.m 2009-01-29 16:51:12 UTC (rev 6847) @@ -1,4 +1,4 @@ -#include <Cocoa/Cocoa.h> +#include <Cocoa/Cocoa.h> #include <ApplicationServices/ApplicationServices.h> #include <sys/socket.h> #include <Python.h> @@ -23,7 +23,7 @@ * [ a b 0] * [ c d 0] * [ tx ty 1] - */ + */ typedef struct { double a; @@ -47,7 +47,7 @@ #define LINETO 2 #define CURVE3 3 #define CURVE4 4 -#define CLOSEPOLY 5 +#define CLOSEPOLY 0x4f /* Hatching */ #define HATCH_SIZE 72 @@ -132,7 +132,7 @@ sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault, channel[0], - kCFSocketReadCallBack, + kCFSocketReadCallBack, _callback, NULL); if (sigint_socket) @@ -470,8 +470,8 @@ } else { - CGContextSetLineWidth(cr, 1.0); - CGContextSetLineCap(cr, kCGLineCapSquare); + CGContextSetLineWidth(cr, 1.0); + CGContextSetLineCap(cr, kCGLineCapSquare); CGContextDrawPath(cr, kCGPathFillStroke); } } @@ -608,7 +608,7 @@ static PyObject* GraphicsContext_set_alpha (GraphicsContext* self, PyObject* args) -{ +{ float alpha; if (!PyArg_ParseTuple(args, "f", &alpha)) return NULL; CGContextRef cr = self->cr; @@ -624,8 +624,8 @@ static PyObject* GraphicsContext_set_antialiased (GraphicsContext* self, PyObject* args) -{ - int shouldAntialias; +{ + int shouldAntialias; if (!PyArg_ParseTuple(args, "i", &shouldAntialias)) return NULL; CGContextRef cr = self->cr; if (!cr) @@ -640,7 +640,7 @@ static PyObject* GraphicsContext_set_capstyle (GraphicsContext* self, PyObject* args) -{ +{ char* string; CGLineCap cap; @@ -662,7 +662,7 @@ return NULL; } CGContextSetLineCap(cr, cap); - + Py_INCREF(Py_None); return Py_None; } @@ -964,7 +964,7 @@ static PyObject* GraphicsContext_set_dashes (GraphicsContext* self, PyObject* args) -{ +{ PyObject* offset; PyObject* dashes; @@ -1026,7 +1026,7 @@ static PyObject* GraphicsContext_set_linewidth (GraphicsContext* self, PyObject* args) -{ +{ float width; if (!PyArg_ParseTuple(args, "f", &width)) return NULL; @@ -1067,7 +1067,7 @@ return NULL; } CGContextSetLineJoin(cr, join); - + Py_INCREF(Py_None); return Py_None; } @@ -1128,7 +1128,7 @@ PyObject* rgbFace; int ok; - + CGContextRef cr = self->cr; if (!cr) @@ -1150,7 +1150,7 @@ int n = _draw_path(cr, path, affine); if (n==-1) return NULL; - + if (n > 0) { PyObject* hatchpath; @@ -1235,7 +1235,7 @@ int ok; float r, g, b; double x, y; - + CGContextRef cr = self->cr; if (!cr) @@ -1322,7 +1322,7 @@ } static BOOL _clip(CGContextRef cr, PyObject* object) -{ +{ if (object == Py_None) return true; PyArrayObject* array = NULL; @@ -1362,8 +1362,8 @@ static PyObject* GraphicsContext_draw_path_collection (GraphicsContext* self, PyObject* args) { - PyObject* master_transform_obj; - PyObject* cliprect; + PyObject* master_transform_obj; + PyObject* cliprect; PyObject* clippath; PyObject* clippath_transform; PyObject* paths; @@ -1538,10 +1538,10 @@ if (Nlinewidths==1) { double linewidth = PyFloat_AsDouble(PySequence_ITEM(linewidths, 0)); - CGContextSetLineWidth(cr, (CGFloat)linewidth); + CGContextSetLineWidth(cr, (CGFloat)linewidth); } else if (Nlinewidths==0) - CGContextSetLineWidth(cr, 0.0); + CGContextSetLineWidth(cr, 0.0); if (Nlinestyles==1) { @@ -1613,7 +1613,7 @@ if (Nlinewidths > 1) { double linewidth = PyFloat_AsDouble(PySequence_ITEM(linewidths, i % Nlinewidths)); - CGContextSetLineWidth(cr, (CGFloat)linewidth); + CGContextSetLineWidth(cr, (CGFloat)linewidth); } if (Nlinestyles > 1) @@ -2057,15 +2057,15 @@ {"Chicago", /* 22 */ "", "", - ""}, + ""}, {"Charcoal", /* 23 */ "", "", - ""}, + ""}, {"Impact", /* 24 */ "", "", - ""}, + ""}, {"Playbill", /* 25 */ "", "", @@ -2073,7 +2073,7 @@ {"AndaleMono", /* 26 */ "", "", - ""}, + ""}, {"BitstreamVeraSansMono-Roman", /* 27 */ "BitstreamVeraSansMono-Bold", "BitstreamVeraSansMono-Oblique", @@ -2091,7 +2091,7 @@ "CourierNewPS-ItalicMT", "CourierNewPS-Bold-ItalicMT"}, }; - + if(!PyList_Check(family)) return 0; n = PyList_GET_SIZE(family); @@ -2106,7 +2106,7 @@ break; } } - /* If the font name is not found in mapping, we assume */ + /* If the font name is not found in mapping, we assume */ /* that the user specified the Postscript name directly */ /* Check if this font can be found on the system */ @@ -2135,7 +2135,7 @@ static PyObject* GraphicsContext_draw_text (GraphicsContext* self, PyObject* args) -{ +{ float x; float y; const UniChar* text; @@ -2335,7 +2335,7 @@ static PyObject* GraphicsContext_get_text_width_height_descent(GraphicsContext* self, PyObject* args) -{ +{ const UniChar* text; int n; PyObject* family; @@ -2449,7 +2449,7 @@ if(!PyArg_ParseTuple(args, "ffiiOOOO", &x, &y, - &nrows, + &nrows, &ncols, &image, &cliprect, @@ -2661,7 +2661,7 @@ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - GraphicsContext_methods, /* tp_methods */ + GraphicsContext_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -2695,7 +2695,7 @@ int height; if(!self->view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } @@ -2736,7 +2736,7 @@ View* view = self->view; if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } [view setNeedsDisplay: YES]; @@ -2752,7 +2752,7 @@ NSRect rubberband; if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } if(!PyArg_ParseTuple(args, "iiii", &x0, &y0, &x1, &y1)) return NULL; @@ -2787,7 +2787,7 @@ View* view = self->view; if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } [view removeRubberband]; @@ -2845,7 +2845,7 @@ if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } if(!PyArg_ParseTuple(args, "u#ff", @@ -2891,7 +2891,7 @@ else if ([extension isEqualToString: @"png"]) filetype = NSPNGFileType; else - { PyErr_SetString(PyExc_ValueError, "Unknown file type"); + { PyErr_SetString(PyExc_ValueError, "Unknown file type"); return NULL; } @@ -2914,7 +2914,7 @@ if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return NULL; } if(!PyArg_ParseTuple(args, "u#", &characters, &n)) return NULL; @@ -2960,7 +2960,7 @@ context.info = &interrupted; sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault, channel[0], - kCFSocketReadCallBack, + kCFSocketReadCallBack, _callback, &context); if (sigint_socket) @@ -3100,7 +3100,7 @@ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - FigureCanvas_methods, /* tp_methods */ + FigureCanvas_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -3147,7 +3147,7 @@ if(!self->window) { - PyErr_SetString(PyExc_RuntimeError, "NSWindow* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSWindow* is NULL"); return -1; } @@ -3157,7 +3157,7 @@ view = canvas->view; if (!view) /* Something really weird going on */ { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } @@ -3276,7 +3276,7 @@ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - FigureManager_methods, /* tp_methods */ + FigureManager_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -3471,7 +3471,7 @@ view = canvas->view; if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } @@ -3748,7 +3748,7 @@ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - NavigationToolbar_methods, /* tp_methods */ + NavigationToolbar_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -3902,7 +3902,7 @@ view = ((FigureCanvas*)canvas)->view; if (!view) /* Something really weird going on */ { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); PyErr_Print(); Py_DECREF(canvas); Py_DECREF(master); @@ -4003,7 +4003,7 @@ view = canvas->view; if(!view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); + PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; } @@ -4173,7 +4173,7 @@ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - NavigationToolbar2_methods, /* tp_methods */ + NavigationToolbar2_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ Modified: trunk/matplotlib/src/_path.cpp =================================================================== --- trunk/matplotlib/src/_path.cpp 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/src/_path.cpp 2009-01-29 16:51:12 UTC (rev 6847) @@ -1,5 +1,6 @@ #include "agg_py_path_iterator.h" #include "agg_py_transforms.h" +#include "path_converters.h" #include <limits> #include <math.h> @@ -53,6 +54,8 @@ "path_intersects_path(p1, p2)"); add_varargs_method("convert_path_to_polygons", &_path_module::convert_path_to_polygons, "convert_path_to_polygons(path, trans, width, height)"); + add_varargs_method("cleanup_path", &_path_module::cleanup_path, + "cleanup_path(path, trans, remove_nans, clip, quantize, simplify, curves)"); initialize("Helper functions for paths"); } @@ -72,6 +75,7 @@ Py::Object count_bboxes_overlapping_bbox(const Py::Tuple& args); Py::Object path_intersects_path(const Py::Tuple& args); Py::Object convert_path_to_polygons(const Py::Tuple& args); + Py::Object cleanup_path(const Py::Tuple& args); }; // @@ -1105,17 +1109,21 @@ PathIterator p1(args[0]); PathIterator p2(args[1]); bool filled = false; - if (args.size() == 3) { - filled = args[2].isTrue(); + if (args.size() == 3) + { + filled = args[2].isTrue(); } - if (!filled) { - return Py::Int(::path_intersects_path(p1, p2)); - } else { - return Py::Int(::path_intersects_path(p1, p2) - || ::path_in_path(p1, agg::trans_affine(), p2, agg::trans_affine()) - || ::path_in_path(p2, agg::trans_affine(), p1, agg::trans_affine())); + if (!filled) + { + return Py::Int(::path_intersects_path(p1, p2)); } + else + { + return Py::Int(::path_intersects_path(p1, p2) + || ::path_in_path(p1, agg::trans_affine(), p2, agg::trans_affine()) + || ::path_in_path(p2, agg::trans_affine(), p1, agg::trans_affine())); + } } void _add_polygon(Py::List& polygons, const std::vector<double>& polygon) { @@ -1134,9 +1142,11 @@ Py::Object _path_module::convert_path_to_polygons(const Py::Tuple& args) { - typedef agg::conv_transform<PathIterator> transformed_path_t; - typedef SimplifyPath<transformed_path_t> simplify_t; - typedef agg::conv_curve<simplify_t> curve_t; + typedef agg::conv_transform<PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removal_t; + typedef PathClipper<nan_removal_t> clipped_t; + typedef PathSimplifier<clipped_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; typedef std::vector<double> vertices_t; @@ -1147,11 +1157,15 @@ double width = Py::Float(args[2]); double height = Py::Float(args[3]); - bool simplify = path.should_simplify() && width != 0.0 && height != 0.0; + bool do_clip = width != 0.0 && height != 0.0; + bool simplify = path.should_simplify(); + transformed_path_t tpath(path, trans); - simplify_t simplified(tpath, false, simplify, width, height); - curve_t curve(simplified); + nan_removal_t nan_removed(tpath, true, path.has_curves()); + clipped_t clipped(nan_removed, do_clip, width, height); + simplify_t simplified(clipped, simplify, path.simplify_threshold()); + curve_t curve(simplified); Py::List polygons; vertices_t polygon; @@ -1162,7 +1176,8 @@ while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) { - if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { + if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) + { if (polygon.size() >= 2) { polygon.push_back(polygon[0]); @@ -1170,8 +1185,11 @@ _add_polygon(polygons, polygon); } polygon.clear(); - } else { - if (code == agg::path_cmd_move_to) { + } + else + { + if (code == agg::path_cmd_move_to) + { _add_polygon(polygons, polygon); polygon.clear(); } @@ -1185,6 +1203,152 @@ return polygons; } +template<class VertexSource> +void __cleanup_path(VertexSource& source, + std::vector<double>& vertices, + std::vector<uint8_t>& codes) { + unsigned code; + double x, y; + do + { + code = source.vertex(&x, &y); + vertices.push_back(x); + vertices.push_back(y); + codes.push_back((uint8_t)code); + } while (code != agg::path_cmd_stop); +} + +void _cleanup_path(PathIterator& path, const agg::trans_affine& trans, + bool remove_nans, bool do_clip, + const agg::rect_base<double>& rect, + e_quantize_mode quantize_mode, bool do_simplify, + bool return_curves, std::vector<double>& vertices, + std::vector<uint8_t>& codes) { + typedef agg::conv_transform<PathIterator> transformed_path_t; + typedef PathNanRemover<transformed_path_t> nan_removal_t; + typedef PathClipper<nan_removal_t> clipped_t; + typedef PathQuantizer<clipped_t> quantized_t; + typedef PathSimplifier<quantized_t> simplify_t; + typedef agg::conv_curve<simplify_t> curve_t; + + transformed_path_t tpath(path, trans); + nan_removal_t nan_removed(tpath, remove_nans, path.has_curves()); + clipped_t clipped(nan_removed, do_clip, rect); + quantized_t quantized(clipped, quantize_mode, path.total_vertices()); + simplify_t simplified(quantized, do_simplify, path.simplify_threshold()); + + vertices.reserve(path.total_vertices() * 2); + codes.reserve(path.total_vertices()); + + if (return_curves) + { + __cleanup_path(simplified, vertices, codes); + } + else + { + curve_t curve(simplified); + __cleanup_path(curve, vertices, codes); + } +} + +Py::Object _path_module::cleanup_path(const Py::Tuple& args) +{ + args.verify_length(7); + + PathIterator path(args[0]); + agg::trans_affine trans = py_to_agg_transformation_matrix(args[1], false); + bool remove_nans = args[2].isTrue(); + + Py::Object clip_obj = args[3]; + bool do_clip; + agg::rect_base<double> clip_rect; + if (clip_obj.isNone()) + { + do_clip = false; + } + else + { + double x1, y1, x2, y2; + Py::Tuple clip_tuple(clip_obj); + x1 = Py::Float(clip_tuple[0]); + y1 = Py::Float(clip_tuple[1]); + x2 = Py::Float(clip_tuple[2]); + y2 = Py::Float(clip_tuple[3]); + clip_rect.init(x1, y1, x2, y2); + do_clip = true; + } + + Py::Object quantize_obj = args[4]; + e_quantize_mode quantize_mode; + if (quantize_obj.isNone()) + { + quantize_mode = QUANTIZE_AUTO; + } + else if (quantize_obj.isTrue()) + { + quantize_mode = QUANTIZE_TRUE; + } + else + { + quantize_mode = QUANTIZE_FALSE; + } + + bool simplify; + Py::Object simplify_obj = args[5]; + if (simplify_obj.isNone()) + { + simplify = path.should_simplify(); + } + else + { + simplify = simplify_obj.isTrue(); + } + + bool return_curves = args[6].isTrue(); + + std::vector<double> vertices; + std::vector<uint8_t> codes; + + _cleanup_path(path, trans, remove_nans, do_clip, clip_rect, quantize_mode, + simplify, return_curves, vertices, codes); + + npy_intp length = codes.size(); + npy_intp dims[] = { length, 2, 0 }; + + PyArrayObject* vertices_obj = NULL; + PyArrayObject* codes_obj = NULL; + Py::Tuple result(2); + try { + vertices_obj = (PyArrayObject*)PyArray_SimpleNew + (2, dims, PyArray_DOUBLE); + if (vertices_obj == NULL) + { + throw Py::MemoryError("Could not allocate result array"); + } + + codes_obj = (PyArrayObject*)PyArray_SimpleNew + (1, dims, PyArray_UINT8); + if (codes_obj == NULL) + { + throw Py::MemoryError("Could not allocate result array"); + } + + memcpy(PyArray_DATA(vertices_obj), &vertices[0], sizeof(double) * 2 * length); + memcpy(PyArray_DATA(codes_obj), &codes[0], sizeof(uint8_t) * length); + + result[0] = Py::Object((PyObject*)vertices_obj, true); + result[1] = Py::Object((PyObject*)codes_obj, true); + } + catch (...) + { + Py_XDECREF(vertices_obj); + Py_XDECREF(codes_obj); + throw; + } + + return result; +} + extern "C" DL_EXPORT(void) init_path(void) Modified: trunk/matplotlib/src/agg_py_path_iterator.h =================================================================== --- trunk/matplotlib/src/agg_py_path_iterator.h 2009-01-29 16:16:14 UTC (rev 6846) +++ trunk/matplotlib/src/agg_py_path_iterator.h 2009-01-29 16:51:12 UTC (rev 6847) @@ -5,28 +5,48 @@ #define PY_ARRAY_TYPES_PREFIX NumPy #include "numpy/arrayobject.h" #include "agg_path_storage.h" -#include "MPL_isnan.h" -#include "mplutils.h" -#include <queue> +/* + This file contains a vertex source to adapt Python Numpy arrays to + Agg paths. It works as an iterator, and converts on-the-fly without + the need for a full copy of the data. + */ + +/************************************************************ + PathIterator acts as a bridge between Numpy and Agg. Given a pair of + Numpy arrays, vertices and codes, it iterates over those vertices and + codes, using the standard Agg vertex source interface: + + unsigned vertex(double* x, double* y) + */ class PathIterator { + /* We hold references to the Python objects, not just the + underlying data arrays, so that Python reference counting can + work. + */ PyArrayObject* m_vertices; PyArrayObject* m_codes; + size_t m_iterator; size_t m_total_vertices; - size_t m_ok; + + /* This class doesn't actually do any simplification, but we + store the value here, since it is obtained from the Python object. + */ bool m_should_simplify; - static const unsigned char num_extra_points_map[16]; - static const unsigned code_map[]; + double m_simplify_threshold; public: + /* path_obj is an instance of the class Path as defined in path.py */ PathIterator(const Py::Object& path_obj) : - m_vertices(NULL), m_codes(NULL), m_iterator(0), m_should_simplify(false) + m_vertices(NULL), m_codes(NULL), m_iterator(0), m_should_simplify(false), + m_simplify_threshold(1.0 / 9.0) { - Py::Object vertices_obj = path_obj.getAttr("vertices"); - Py::Object codes_obj = path_obj.getAttr("codes"); - Py::Object should_simplify_obj = path_obj.getAttr("should_simplify"); + Py::Object vertices_obj = path_obj.getAttr("vertices"); + Py::Object codes_obj = path_obj.getAttr("codes"); + Py::Object should_simplify_obj = path_obj.getAttr("should_simplify"); + Py::Object simplify_threshold_obj = path_obj.getAttr("simplify_threshold"); m_vertices = (PyArrayObject*)PyArray_FromObject (vertices_obj.ptr(), PyArray_DOUBLE, 2, 2); @@ -44,11 +64,11 @@ throw Py::ValueError("Invalid codes array."); if (PyArray_DIM(m_codes, 0) != PyArray_DIM(m_vertices, 0)) throw Py::ValueError("Codes array is wrong length"); - m_ok = 0; } - m_should_simplify = should_simplify_obj.isTrue(); - m_total_vertices = m_vertices->dimensions[0]; + m_should_simplify = should_simplify_obj.isTrue(); + m_total_vertices = PyArray_DIM(m_vertices, 0); + m_simplify_threshold = Py::Float(simplify_threshold_obj); } ~PathIterator() @@ -57,20 +77,19 @@ Py_XDECREF(m_codes); } -private: - inline void vertex(const unsigned idx, double* x, double* y) + inline unsigned vertex(double* x, double* y) { + if (m_iterator >= m_total_vertices) return agg::path_cmd_stop; + + const size_t idx = m_iterator++; + char* pair = (char*)PyArray_GETPTR2(m_vertices, idx, 0); *x = *(double*)pair; *y = *(double*)(pair + PyArray_STRIDE(m_vertices, 1)); - } - inline unsigned vertex_with_code(const unsigned idx, double* x, double* y) - { - vertex(idx, x, y); if (m_codes) { - return code_map[(int)*(char *)PyArray_GETPTR1(m_codes, idx)]; + return (unsigned)(*(char *)PyArray_GETPTR1(m_codes, idx)); } else { @@ -78,97 +97,6 @@ } } -public: - inline unsigned vertex(double* x, double* y) - { - if (m_iterator >= m_total_vertices) return agg::path_cmd_stop; - unsigned code = vertex_with_code(m_iterator++, x, y); - - if (!m_codes) { - // This is the fast path for when we know we have no curves - if (MPL_notisfinite64(*x) || MPL_notisfinite64(*y)) - { - do - { - if (m_iterator < m_total_vertices) - { - vertex(m_iterator++, x, y); - } - else - { - return agg::path_cmd_stop; - } - } while (MPL_notisfinite64(*x) || MPL_notisfinite64(*y)); - return agg::path_cmd_move_to; - } - } - else - { - // This is the slow method for when there might be curves. - - ... [truncated message content] |