From: <md...@us...> - 2007-11-28 18:27:50
|
Revision: 4489 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4489&view=rev Author: mdboom Date: 2007-11-28 10:27:43 -0800 (Wed, 28 Nov 2007) Log Message: ----------- Speed improvements -- determine path extents in C Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/colorbar.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/transforms.py branches/transforms/src/_path.cpp Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-11-28 18:26:40 UTC (rev 4488) +++ branches/transforms/lib/matplotlib/axes.py 2007-11-28 18:27:43 UTC (rev 4489) @@ -425,10 +425,10 @@ """ name = "rectilinear" - + _shared_x_axes = cbook.Grouper() _shared_y_axes = cbook.Grouper() - + def __str__(self): return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) def __init__(self, fig, rect, @@ -496,7 +496,7 @@ # this call may differ for non-sep axes, eg polar self._init_axis() - + if axisbg is None: axisbg = rcParams['axes.facecolor'] self._axisbg = axisbg self._frameon = frameon @@ -679,12 +679,12 @@ return (self._yaxis_transform + mtransforms.Affine2D().translate(pad_pixels, 0), "center", "left") - + def _update_transScale(self): self.transScale.set( mtransforms.blended_transform_factory( self.xaxis.get_transform(), self.yaxis.get_transform())) - + def get_position(self, original=False): 'Return the a copy of the axes rectangle as a Bbox' if original: @@ -692,7 +692,7 @@ else: return self._position.frozen() - + def set_position(self, pos, which='both'): """ Set the axes position with pos = [left, bottom, width, height] @@ -734,7 +734,7 @@ Intended to be overridden by new projection types. """ return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0) - + def cla(self): 'Clear the current axes' @@ -780,7 +780,7 @@ self.title.set_clip_box(None) self._set_artist_props(self.title) - + self.axesPatch = self.get_axes_patch() self.axesPatch.set_figure(self.figure) self.axesPatch.set_facecolor(self._axisbg) @@ -904,7 +904,7 @@ ymin,ymax = self.get_ybound() ysize = max(math.fabs(ymax-ymin), 1e-30) return ysize/xsize - + def apply_aspect(self): ''' Use self._aspect and self._adjustable to modify the @@ -938,7 +938,7 @@ xsize = max(math.fabs(xmax-xmin), 1e-30) ymin,ymax = self.get_ybound() ysize = max(math.fabs(ymax-ymin), 1e-30) - + l,b,w,h = self.get_position(original=True).bounds box_aspect = fig_aspect * (h/w) data_ratio = box_aspect / A @@ -1125,7 +1125,6 @@ a.set_axes(self) self.artists.append(a) self._set_artist_props(a) - # MGDTODO: We may not want to do this -- the old trunk didn't a.set_clip_path(self.axesPatch) a._remove_method = lambda h: self.artists.remove(h) @@ -1168,7 +1167,7 @@ self._update_patch_limits(p) self.patches.append(p) p._remove_method = lambda h: self.patches.remove(h) - + def _update_patch_limits(self, p): 'update the datalimits for patch p' vertices = p.get_patch_transform().transform(p.get_path().vertices) @@ -1181,7 +1180,6 @@ 'Add a table instance to the list of axes tables' self._set_artist_props(tab) self.tables.append(tab) - # MGDTODO: We may not want to do this (the old version in trunk didn't) tab.set_clip_path(self.axesPatch) tab._remove_method = lambda h: self.tables.remove(h) @@ -1202,7 +1200,8 @@ # and the data in xydata if not ma.isMaskedArray(xys): xys = npy.asarray(xys) - self.update_datalim_numerix(xys[:, 0], xys[:, 1]) + self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits) + self.ignore_existing_data_limits = False def update_datalim_numerix(self, x, y): 'Update the data lim bbox with seq of xy tups' @@ -1216,7 +1215,7 @@ def update_datalim_bounds(self, bounds): 'Update the datalim to include the given Bbox' self.dataLim.set(Bbox.union([self.dataLim, bounds])) - + def _process_unit_info(self, xdata=None, ydata=None, kwargs=None): 'look for unit kwargs and update the axis instances as necessary' @@ -1303,7 +1302,7 @@ if self.axison and self._frameon: self.axesPatch.draw(renderer) - + artists = [] if len(self.images)<=1 or renderer.option_image_nocomposite(): @@ -1513,7 +1512,7 @@ self.axesPatch.set_facecolor(color) ### data limits, ticks, tick labels, and formatting - + def invert_xaxis(self): "Invert the x-axis." left, right = self.get_xlim() @@ -1601,7 +1600,7 @@ xmin, xmax = self.xaxis.limit_range_for_scale(xmin, xmax) self.viewLim.intervalx = (xmin, xmax) - + if emit: self.callbacks.process('xlim_changed', self) # Call all of the other x-axes that are shared with this one @@ -1610,7 +1609,7 @@ other.set_xlim(self.viewLim.intervalx, emit=False) if other.figure != self.figure and other.figure.canvas is not None: other.figure.canvas.draw_idle() - + return xmin, xmax def get_xscale(self): @@ -1638,7 +1637,7 @@ """ % {'scale': ' | '.join([repr(x) for x in mscale.get_scale_names()])} self.xaxis.set_scale(value, **kwargs) self._update_transScale() - + def get_xticks(self, minor=False): 'Return the x ticks as a list of locations' return self.xaxis.get_ticklocs(minor=minor) @@ -1777,7 +1776,7 @@ 'return the xaxis scale string: %s' % ( ", ".join(mscale.get_scale_names())) return self.yaxis.get_scale() - + def set_yscale(self, value, **kwargs): """ SET_YSCALE(value, basey=10, subsy=None) @@ -1798,7 +1797,7 @@ """ % {'scale': ' | '.join([repr(x) for x in mscale.get_scale_names()])} self.yaxis.set_scale(value, **kwargs) self._update_transScale() - + def get_yticks(self, minor=False): 'Return the y ticks as a list of locations' return self.yaxis.get_ticklocs(minor=minor) @@ -1903,7 +1902,7 @@ xs = self.format_xdata(x) ys = self.format_ydata(y) return 'x=%s, y=%s'%(xs,ys) - + #### Interactive manipulation def can_zoom(self): @@ -1911,7 +1910,7 @@ Return True if this axes support the zoom box """ return True - + def get_navigate(self): """ Get whether the axes responds to navigation commands @@ -1949,7 +1948,7 @@ 1: LEFT 2: MIDDLE 3: RIGHT - + Intended to be overridden by new projection types. """ self._pan_start = cbook.Bunch( @@ -1968,7 +1967,7 @@ Intended to be overridden by new projection types. """ del self._pan_start - + def drag_pan(self, button, key, x, y): """ Called when the mouse moves during a pan operation. @@ -2029,10 +2028,10 @@ except OverflowError: warnings.warn('Overflow while panning') return - + self.set_xlim(*result.intervalx) self.set_ylim(*result.intervaly) - + def get_cursor_props(self): """return the cursor props as a linewidth, color tuple where linewidth is a float and color is an RGBA tuple""" @@ -3158,7 +3157,7 @@ if where not in ('pre', 'post', 'mid'): raise ValueError("'where' argument to step must be 'pre', 'post' or 'mid'") kwargs['linestyle'] = 'steps-' + where - + return self.plot(x, y, *args, **kwargs) @@ -3828,7 +3827,7 @@ # using list comps rather than arrays to preserve units lower = [thisy-thiserr for (thisy, thiserr) in cbook.safezip(y,yerr)] upper = [thisy+thiserr for (thisy, thiserr) in cbook.safezip(y,yerr)] - + barcols.append( self.vlines(x, lower, upper, **lines_kw) ) if capsize > 0: @@ -4334,7 +4333,7 @@ def quiver(self, *args, **kw): """ - MGDTODO: Document me + TODO: Document me """ q = mquiver.Quiver(self, *args, **kw) self.add_collection(q, False) @@ -4516,7 +4515,7 @@ return im - + def _pcolorargs(self, funcname, *args): if len(args)==1: C = args[0] @@ -4790,7 +4789,7 @@ shading = kwargs.pop('shading', 'flat') edgecolors = kwargs.pop('edgecolors', 'None') antialiased = kwargs.pop('antialiased', False) - + X, Y, C = self._pcolorargs('pcolormesh', *args) Ny, Nx = X.shape @@ -5519,7 +5518,7 @@ # _axes_class is set in the subplot_class_factory self._axes_class.__init__(self, fig, self.figbox, **kwargs) - + def get_geometry(self): 'get the subplot geometry, eg 2,2,3' return self._rows, self._cols, self._num+1 @@ -5608,24 +5607,24 @@ for label in self.get_yticklabels(): label.set_visible(firstcol) -_subplot_classes = {} +_subplot_classes = {} def subplot_class_factory(axes_class=None): # This makes a new class that inherits from SubclassBase and the # given axes_class (which is assumed to be a subclass of Axes). - + # This is perhaps a little bit roundabout to make a new class on # the fly like this, but it means that a new Subplot class does # not have to be created for every type of Axes. if axes_class is None: axes_class = Axes - + new_class = _subplot_classes.get(axes_class) if new_class is None: new_class = new.classobj("%sSubplot" % (axes_class.__name__), (SubplotBase, axes_class), {'_axes_class': axes_class}) _subplot_classes[axes_class] = new_class - + return new_class # This is provided for backward compatibility Modified: branches/transforms/lib/matplotlib/colorbar.py =================================================================== --- branches/transforms/lib/matplotlib/colorbar.py 2007-11-28 18:26:40 UTC (rev 4488) +++ branches/transforms/lib/matplotlib/colorbar.py 2007-11-28 18:27:43 UTC (rev 4489) @@ -198,17 +198,17 @@ ax = self.ax ax.set_frame_on(False) ax.set_navigate(False) - x, y = self._outline(X, Y) - ax.set_xlim(npy.amin(x), npy.amax(x)) - ax.set_ylim(npy.amin(y), npy.amax(y)) - ax.update_datalim_numerix(x, y) - self.outline = lines.Line2D(x, y, color=mpl.rcParams['axes.edgecolor'], + xy = self._outline(X, Y) + ax.update_datalim(xy) + ax.set_xlim(*ax.dataLim.intervalx) + ax.set_ylim(*ax.dataLim.intervaly) + self.outline = lines.Line2D(xy[:, 0], xy[:, 1], color=mpl.rcParams['axes.edgecolor'], linewidth=mpl.rcParams['axes.linewidth']) ax.add_artist(self.outline) self.outline.set_clip_box(None) self.outline.set_clip_path(None) c = mpl.rcParams['axes.facecolor'] - self.patch = patches.Polygon(zip(x,y), edgecolor=c, + self.patch = patches.Polygon(xy, edgecolor=c, facecolor=c, linewidth=0.01, zorder=-1) @@ -250,9 +250,11 @@ ii = [0, 1, N-2, N-1, 2*N-1, 2*N-2, N+1, N, 0] x = npy.take(npy.ravel(npy.transpose(X)), ii) y = npy.take(npy.ravel(npy.transpose(Y)), ii) + x = x.reshape((len(x), 1)) + y = y.reshape((len(y), 1)) if self.orientation == 'horizontal': - return y,x - return x,y + return npy.hstack((y, x)) + return npy.hstack((x, y)) def _edges(self, X, Y): ''' @@ -510,7 +512,7 @@ N = len(b) ii = npy.minimum(npy.searchsorted(b, xn), N-1) i0 = npy.maximum(ii - 1, 0) - #db = b[ii] - b[i0] + #db = b[ii] - b[i0] db = npy.take(b, ii) - npy.take(b, i0) db = npy.where(i0==ii, 1.0, db) #dy = y[ii] - y[i0] Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-11-28 18:26:40 UTC (rev 4488) +++ branches/transforms/lib/matplotlib/path.py 2007-11-28 18:27:43 UTC (rev 4489) @@ -38,10 +38,10 @@ MOVETO : 1 vertex Pick up the pen and move to the given vertex. - + LINETO : 1 vertex Draw a line from the current position to the given vertex. - + CURVE3 : 1 control point, 1 endpoint Draw a quadratic Bezier curve from the current position, with the given control point, to the given end point. @@ -60,7 +60,7 @@ store a codes array at all, but have a default one provided for them by iter_segments. """ - + # Path codes STOP = 0 # 1 vertex MOVETO = 1 # 1 vertex @@ -70,7 +70,7 @@ CLOSEPOLY = 5 # 1 vertex NUM_VERTICES = [1, 1, 1, 2, 3, 1] - + code_type = npy.uint8 def __init__(self, vertices, codes=None): @@ -124,7 +124,7 @@ assert vertices.ndim == 2 assert vertices.shape[1] == 2 - + self.codes = codes self.vertices = vertices @@ -142,22 +142,22 @@ vertices = npy.vstack([x.vertices for x in args]) vertices.reshape((total_length, 2)) - + codes = Path.LINETO * npy.ones(total_length) i = 0 for length in lengths: codes[i] = Path.MOVETO i += length - + return Path(vertices, codes) make_compound_path = staticmethod(make_compound_path) - + def __repr__(self): return "Path(%s, %s)" % (self.vertices, self.codes) def __len__(self): return len(self.vertices) - + def iter_segments(self): """ Iterates over all of the curve segments in the path. @@ -171,10 +171,10 @@ LINETO = self.LINETO CLOSEPOLY = self.CLOSEPOLY STOP = self.STOP - + if not len(vertices): return - + if codes is None: yield vertices[0], MOVETO for v in vertices[1:]: @@ -192,7 +192,7 @@ num_vertices = NUM_VERTICES[code] yield vertices[i:i+num_vertices].flatten(), code i += num_vertices - + def transformed(self, transform): """ Return a transformed copy of the path. @@ -223,7 +223,7 @@ from transforms import IdentityTransform transform = IdentityTransform() return path_in_path(self, IdentityTransform(), path, transform) - + def get_extents(self, transform=None): """ Returns the extents (xmin, ymin, xmax, ymax) of the path. @@ -235,7 +235,7 @@ from transforms import Affine2D, Bbox if transform is None: transform = Affine2D() - return Bbox.from_extents(*get_path_extents(self, transform)) + return Bbox(get_path_extents(self, transform)) def intersects_path(self, other): """ @@ -252,7 +252,7 @@ BboxTransformTo(bbox)) result = self.intersects_path(rectangle) return result - + def interpolated(self, steps): """ Returns a new path resampled to length N x steps. @@ -266,7 +266,7 @@ else: new_codes = None return Path(vertices, new_codes) - + _unit_rectangle = None #@classmethod def unit_rectangle(cls): @@ -329,7 +329,7 @@ """ return cls.unit_regular_star(numVertices, 0.0) unit_regular_asterisk = classmethod(unit_regular_asterisk) - + _unit_circle = None #@classmethod def unit_circle(cls): @@ -341,19 +341,19 @@ offset = KAPPA vertices = npy.array( [[-1.0, 0.0], - + [-1.0, offset], [-offset, 1.0], [0.0, 1.0], - + [offset, 1.0], [1.0, offset], [1.0, 0.0], - + [1.0, -offset], [offset, -1.0], [0.0, -1.0], - + [-offset, -1.0], [-1.0, -offset], [-1.0, 0.0], @@ -383,10 +383,10 @@ # degrees to radians theta1 *= math.pi / 180.0 theta2 *= math.pi / 180.0 - + twopi = math.pi * 2.0 halfpi = math.pi * 0.5 - + eta1 = math.atan2(math.sin(theta1), math.cos(theta1)) eta2 = math.atan2(math.sin(theta2), math.cos(theta2)) eta2 -= twopi * math.floor((eta2 - eta1) / twopi) @@ -423,13 +423,13 @@ t = math.tan(0.5 * deta) alpha = math.sin(deta) * (math.sqrt(4.0 + 3.0 * t * t) - 1) / 3.0 - + for i in xrange(n): xA = xB yA = yB xA_dot = xB_dot yA_dot = yB_dot - + etaB += deta cos_etaB = math.cos(etaB) sin_etaB = math.sin(etaB) @@ -446,7 +446,7 @@ if is_wedge: codes[-2:] = [Path.LINETO, Path.CLOSEPOLY] - + return Path(vertices, codes) arc = classmethod(arc) Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-11-28 18:26:40 UTC (rev 4488) +++ branches/transforms/lib/matplotlib/text.py 2007-11-28 18:27:43 UTC (rev 4489) @@ -258,7 +258,7 @@ xys -= (offsetx, offsety) xs, ys = xys[:, 0], xys[:, 1] - + ret = bbox, zip(lines, whs, xs, ys) self.cached[key] = ret return ret @@ -274,7 +274,6 @@ """ self._bbox = rectprops - # MGDTODO: The clipping here could be generalized def draw(self, renderer): #return if renderer is not None: @@ -297,7 +296,7 @@ posx, posy = self.get_position() posx, posy = trans.transform_point((posx, posy)) canvasw, canvash = renderer.get_canvas_width_height() - + if rcParams['text.usetex']: for line, wh, x, y in info: x = x + posx @@ -314,11 +313,11 @@ y = y + posy if renderer.flipy(): y = canvash-y - + renderer.draw_text(gc, x, y, line, self._fontproperties, angle, ismath=self.is_math_text(line)) - + def get_color(self): "Return the color of the text" return self._color @@ -1035,7 +1034,6 @@ """ Text.set_clip_box(self, clipbox) - # MGDTODO: Abstract this out -- this seems weird to have this big "switch" here def _get_xy(self, x, y, s): if s=='data': trans = self.axes.transData Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-11-28 18:26:40 UTC (rev 4488) +++ branches/transforms/lib/matplotlib/transforms.py 2007-11-28 18:27:43 UTC (rev 4489) @@ -29,17 +29,18 @@ from numpy.linalg import inv from weakref import WeakKeyDictionary +import warnings import cbook from path import Path -from _path import count_bboxes_overlapping_bbox +from _path import count_bboxes_overlapping_bbox, update_path_extents DEBUG = False if DEBUG: import warnings MaskedArray = ma.MaskedArray - + class TransformNode(object): """ TransformNode is the base class for anything that participates in @@ -56,7 +57,7 @@ INVALID_NON_AFFINE = 1 INVALID_AFFINE = 2 INVALID = INVALID_NON_AFFINE | INVALID_AFFINE - + # Some metadata about the transform, used to determine whether an # invalidation is affine-only is_affine = False @@ -65,7 +66,7 @@ # If pass_through is True, all ancestors will always be # invalidated, even if 'self' is already invalid. pass_through = False - + def __init__(self): """ Creates a new TransformNode. @@ -79,16 +80,12 @@ # computed for the first time. self._invalid = 1 - # A list of the children is kept around for debugging purposes - # only. - self._children = [] - def __copy__(self, *args): raise NotImplementedError( "TransformNode instances can not be copied. " + "Consider using frozen() instead.") __deepcopy__ = __copy__ - + def invalidate(self): """ Invalidate this transform node and all of its parents. Should @@ -104,7 +101,7 @@ # are as well, so we don't need to do anything. if self._invalid == value or not len(self._parents): return - + # Invalidate all ancestors of self using pseudo-recursion. parent = None stack = [self] @@ -122,8 +119,13 @@ """ for child in children: child._parents[self] = None - self._children = children + if DEBUG: + _set_children = set_children + def set_children(self, *children): + self._set_children(*children) + self._children = children + def frozen(self): """ Returns a frozen copy of this transform node. The frozen copy @@ -169,17 +171,18 @@ fobj.write('%s [%s];\n' % (hash(root), props)) - for child in root._children: - name = '?' - for key, val in root.__dict__.items(): - if val is child: - name = key - break - fobj.write('%s -> %s [label="%s", fontsize=10];\n' % ( - hash(root), - hash(child), - name)) - recurse(child) + if hasattr(root, '_children'): + for child in root._children: + name = '?' + for key, val in root.__dict__.items(): + if val is child: + name = key + break + fobj.write('%s -> %s [label="%s", fontsize=10];\n' % ( + hash(root), + hash(child), + name)) + recurse(child) fobj.write("digraph G {\n") recurse(self) @@ -187,8 +190,8 @@ else: def write_graphviz(self, fobj, highlight=[]): return - - + + class BboxBase(TransformNode): """ This is the base class of all bounding boxes, and provides @@ -209,21 +212,21 @@ points[1,1] - points[0,1] == 0): warnings.warn("Singular Bbox.") _check = staticmethod(_check) - + def frozen(self): return Bbox(self.get_points().copy()) frozen.__doc__ = TransformNode.__doc__ - + def __array__(self, *args, **kwargs): return self.get_points() def is_unit(self): return list(self.get_points().flatten()) == [0., 0., 1., 1.] - + def _get_x0(self): return self.get_points()[0, 0] x0 = property(_get_x0) - + def _get_y0(self): return self.get_points()[0, 1] y0 = property(_get_y0) @@ -243,7 +246,7 @@ def _get_p1(self): return self.get_points()[1] p1 = property(_get_p1) - + def _get_xmin(self): return min(self.get_points()[:, 0]) xmin = property(_get_xmin) @@ -259,17 +262,17 @@ def _get_ymax(self): return max(self.get_points()[:, 1]) ymax = property(_get_ymax) - + def _get_min(self): return [min(self.get_points()[:, 0]), min(self.get_points()[:, 1])] min = property(_get_min) - + def _get_max(self): return [max(self.get_points()[:, 0]), max(self.get_points()[:, 1])] max = property(_get_max) - + def _get_intervalx(self): return self.get_points()[:, 0] intervalx = property(_get_intervalx) @@ -277,7 +280,7 @@ def _get_intervaly(self): return self.get_points()[:, 1] intervaly = property(_get_intervaly) - + def _get_width(self): points = self.get_points() return points[1, 0] - points[0, 0] @@ -292,7 +295,7 @@ points = self.get_points() return points[1] - points[0] size = property(_get_size) - + def _get_bounds(self): x0, y0, x1, y1 = self.get_points().flatten() return (x0, y0, x1 - x0, y1 - y0) @@ -301,10 +304,10 @@ def _get_extents(self): return self.get_points().flatten().copy() extents = property(_get_extents) - + def get_points(self): return NotImplementedError() - + def containsx(self, x): x0, x1 = self.intervalx return ((x0 < x1 @@ -316,7 +319,7 @@ return ((y0 < y1 and (y >= y0 and y <= y1)) or (y >= y1 and y <= y0)) - + def contains(self, x, y): return self.containsx(x) and self.containsy(y) @@ -337,7 +340,7 @@ (by2 < ay1) or (bx1 > ax2) or (by1 > ay2)) - + def fully_containsx(self, x): x0, x1 = self.intervalx return ((x0 < x1 @@ -349,7 +352,7 @@ return ((y0 < y1 and (x > y0 and x < y1)) or (x > y1 and x < y0)) - + def fully_contains(self, x, y): return self.fully_containsx(x) \ and self.fully_containsy(y) @@ -529,7 +532,7 @@ """ points = self._points return Bbox(points + [[-p, -p], [p, p]]) - + def translated(self, tx, ty): """ Return a copy of the Bbox, translated by tx and ty. @@ -543,7 +546,7 @@ """ l, b, r, t = self.get_points().flatten() return npy.array([[l, b], [l, t], [r, b], [r, t]]) - + def rotated(self, radians): """ Return a new bounding box that bounds a rotated version of this @@ -555,7 +558,7 @@ bbox = Bbox.unit() bbox.update_from_data(corners_rotated, ignore=True) return bbox - + #@staticmethod def union(bboxes): """ @@ -582,8 +585,8 @@ return Bbox.from_extents(x0, y0, x1, y1) union = staticmethod(union) - - + + class Bbox(BboxBase): def __init__(self, points): """ @@ -598,13 +601,13 @@ self._points = npy.asarray(points, npy.float_) self._minpos = npy.array([0.0000001, 0.0000001]) self._ignore = True - + if DEBUG: ___init__ = __init__ def __init__(self, points): self._check(points) self.___init__(points) - + def invalidate(self): self._check(self._points) TransformNode.invalidate(self) @@ -638,7 +641,7 @@ points = npy.array(args, dtype=npy.float_).reshape(2, 2) return Bbox(points) from_extents = staticmethod(from_extents) - + def __repr__(self): return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ @@ -654,7 +657,7 @@ include the existing bounds of the Bbox. """ self._ignore = value - + def update_from_data(self, x, y, ignore=None): """ Update the bounds of the Bbox based on the passed in data. @@ -666,45 +669,10 @@ when False, include the existing bounds of the Bbox. when None, use the last value passed to Bbox.ignore(). """ - if ignore is None: - ignore = self._ignore + warnings.warn("update_from_data requires a memory copy -- please replace with update_from_data_xy") + xy = npy.hstack((x.reshape((len(x), 1)), y.reshape((len(y), 1)))) + return self.update_from_data_xy(xy, ignore) - if len(x) == 0 or len(y) == 0: - return - - if ma.isMaskedArray(x) or ma.isMaskedArray(y): - xpos = ma.where(x > 0.0, x, npy.inf) - ypos = ma.where(y > 0.0, y, npy.inf) - else: - xpos = npy.where(x > 0.0, x, npy.inf) - ypos = npy.where(y > 0.0, y, npy.inf) - if len(xpos) and len(ypos): - minpos = npy.array( - [xpos.min(), - ypos.min()], - npy.float_) - else: - minpos = npy.array([-npy.inf, -npy.inf], npy.float_) - - if ignore: - points = npy.array( - [[x.min(), y.min()], [x.max(), y.max()]], - npy.float_) - self._minpos = minpos - else: - x0, y0, x1, y1 = self._get_extents() - points = npy.array( - [[min(x.min(), x0, x1), - min(y.min(), y0, y1)], - [max(x.max(), x0, x1), - max(y.max(), y0, y1)]], - npy.float_) - self._minpos = npy.minimum(minpos, self._minpos) - - if npy.any(self._points != points): - self._points = points - self.invalidate() - def update_from_data_xy(self, xy, ignore=None): """ Update the bounds of the Bbox based on the passed in data. @@ -715,8 +683,20 @@ when False, include the existing bounds of the Bbox. when None, use the last value passed to Bbox.ignore(). """ - return self.update_from_data(xy[:, 0], xy[:, 1], ignore) - + if ignore is None: + ignore = self._ignore + + if len(xy) == 0: + return + + points, minpos, changed = update_path_extents( + Path(xy), None, self._points, self._minpos, ignore) + + if changed: + self._points = points + self._minpos = minpos + self.invalidate() + def _set_x0(self, val): self._points[0, 0] = val self.invalidate() @@ -746,7 +726,7 @@ self._points[1] = val self.invalidate() p1 = property(BboxBase._get_p1, _set_p1) - + def _set_intervalx(self, interval): self._points[:, 0] = interval self.invalidate() @@ -776,7 +756,7 @@ def _get_minposy(self): return self._minpos[1] minposy = property(_get_minposy) - + def get_points(self): """ Set the points of the bounding box directly as a numpy array @@ -803,7 +783,7 @@ self._points = other.get_points() self.invalidate() - + class TransformedBbox(BboxBase): """ A Bbox that is automatically transformed by a given Transform. When @@ -829,7 +809,7 @@ def __repr__(self): return "TransformedBbox(%s, %s)" % (self._bbox, self._transform) __str__ = __repr__ - + def get_points(self): if self._invalid: points = self._transform.transform(self._bbox.get_points()) @@ -846,7 +826,7 @@ points = self._get_points() self._check(points) return points - + class Transform(TransformNode): """ The base class of all TransformNodes that actually perform a @@ -854,7 +834,7 @@ All non-affine transformations should be subclass this class. New affine transformations should subclass Affine2D. - + Subclasses of this class should override the following members (at minimum): @@ -907,28 +887,28 @@ Used by C/C++ -based backends to get at the array matrix data. """ return self.frozen().__array__() - + def transform(self, values): """ Performs the transformation on the given array of values. - + Accepts a numpy array of shape (N x self.input_dims) and returns a numpy array of shape (N x self.output_dims). """ raise NotImplementedError() - + def transform_affine(self, values): """ Performs only the affine part of this transformation on the given array of values. - + transform(values) is equivalent to transform_affine(transform_non_affine(values)). In non-affine transformations, this is generally a no-op. In affine transformations, this is equivalent to transform(values). - + Accepts a numpy array of shape (N x self.input_dims) and returns a numpy array of shape (N x self.output_dims). """ @@ -944,7 +924,7 @@ In non-affine transformations, this is generally equivalent to transform(values). In affine transformations, this is a no-op. - + Accepts a numpy array of shape (N x self.input_dims) and returns a numpy array of shape (N x self.output_dims). """ @@ -955,7 +935,7 @@ Get the affine part of this transform. """ return IdentityTransform() - + def transform_point(self, point): """ A convenience function that returns the transformed copy of a @@ -973,7 +953,7 @@ Returns a transformed copy of path. path: a Path instance. - + In some cases, this transform may insert curves into the path that began as line segments. """ @@ -1002,7 +982,7 @@ transform_path_affine(transform_path_non_affine(values)). """ return Path(self.transform_non_affine(path.vertices), path.codes) - + def inverted(self): """ Return the corresponding inverse transformation. @@ -1036,7 +1016,7 @@ with set(). """ assert isinstance(child, Transform) - + Transform.__init__(self) self.input_dims = child.input_dims self.output_dims = child.output_dims @@ -1063,7 +1043,7 @@ self.transform_path_non_affine = child.transform_path_non_affine self.get_affine = child.get_affine self.inverted = child.inverted - + def set(self, child): """ Replace the current child of this transform with another one. @@ -1075,11 +1055,11 @@ assert child.output_dims == self.output_dims self._set(child) - + self._invalid = 0 self.invalidate() self._invalid = 0 - + def _get_is_affine(self): return self._child.is_affine is_affine = property(_get_is_affine) @@ -1091,22 +1071,22 @@ def _get_has_inverse(self): return self._child.has_inverse has_inverse = property(_get_has_inverse) - - + + class AffineBase(Transform): """ The base class of all affine transformations of any number of dimensions. """ is_affine = True - + def __init__(self): Transform.__init__(self) self._inverted = None def __array__(self, *args, **kwargs): return self.get_matrix() - + #@staticmethod def _concat(a, b): """ @@ -1133,28 +1113,28 @@ def transform_path_non_affine(self, path): return path transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ - + def get_affine(self): return self get_affine.__doc__ = Transform.get_affine.__doc__ - + class Affine2DBase(AffineBase): """ The base class of all 2D affine transformations. 2D affine transformations are performed using a 3x3 numpy array: - + a c e b d f 0 0 1 - + Provides the read-only interface. Subclasses of this class will generally only need to override a constructor and 'get_matrix' that generates a custom 3x3 matrix. """ - + input_dims = 2 output_dims = 2 @@ -1166,22 +1146,22 @@ def frozen(self): return Affine2D(self.get_matrix().copy()) frozen.__doc__ = AffineBase.frozen.__doc__ - + def _get_is_separable(self): mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 is_separable = property(_get_is_separable) - + def __array__(self, *args, **kwargs): return self.get_matrix() - + def to_values(self): """ Return the values of the matrix as a sequence (a,b,c,d,e,f) """ mtx = self.get_matrix() return tuple(mtx[:2].swapaxes(0, 1).flatten()) - + #@staticmethod def matrix_from_values(a, b, c, d, e, f): """ @@ -1204,7 +1184,7 @@ def transform_point(self, point): mtx = self.get_matrix() return affine_transform(point, mtx) - + if DEBUG: _transform = transform def transform(self, points): @@ -1219,10 +1199,10 @@ % type(values)) return self._transform(points) transform.__doc__ = AffineBase.transform.__doc__ - + transform_affine = transform transform_affine.__doc__ = AffineBase.transform_affine.__doc__ - + def inverted(self): if self._inverted is None or self._invalid: mtx = self.get_matrix() @@ -1230,13 +1210,13 @@ self._invalid = 0 return self._inverted inverted.__doc__ = AffineBase.inverted.__doc__ - - + + class Affine2D(Affine2DBase): def __init__(self, matrix = None): """ Initialize an Affine transform from a 3x3 numpy float array: - + a c e b d f 0 0 1 @@ -1261,7 +1241,7 @@ (self.get_matrix() == other.get_matrix()).all()): return 0 return -1 - + #@staticmethod def from_values(a, b, c, d, e, f): """ @@ -1306,7 +1286,7 @@ assert isinstance(other, Affine2DBase) self._mtx = other.get_matrix() self.invalidate() - + #@staticmethod def identity(): """ @@ -1325,7 +1305,7 @@ self._mtx = npy.identity(3) self.invalidate() return self - + def rotate(self, theta): """ Add a rotation (in radians) to this transform in place. @@ -1368,7 +1348,7 @@ calls to rotate(), rotate_deg(), translate() and scale(). """ return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) - + def translate(self, tx, ty): """ Adds a translation in place. @@ -1407,7 +1387,7 @@ return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 is_separable = property(_get_is_separable) - + class IdentityTransform(Affine2DBase): """ A special class that does on thing, the identity transform, in a @@ -1418,19 +1398,19 @@ def frozen(self): return self frozen.__doc__ = Affine2DBase.frozen.__doc__ - + def __repr__(self): return "IdentityTransform()" __str__ = __repr__ - + def get_matrix(self): return self._mtx get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ - + def transform(self, points): return points transform.__doc__ = Affine2DBase.transform.__doc__ - + transform_affine = transform transform_affine.__doc__ = Affine2DBase.transform_affine.__doc__ @@ -1443,18 +1423,18 @@ transform_path_affine = transform_path transform_path_affine.__doc__ = Affine2DBase.transform_path_affine.__doc__ - + transform_path_non_affine = transform_path transform_path_non_affine.__doc__ = Affine2DBase.transform_path_non_affine.__doc__ - + def get_affine(self): return self get_affine.__doc__ = Affine2DBase.get_affine.__doc__ - + inverted = get_affine inverted.__doc__ = Affine2DBase.inverted.__doc__ - - + + class BlendedGenericTransform(Transform): """ A "blended" transform uses one transform for the x-direction, and @@ -1467,7 +1447,7 @@ output_dims = 2 is_separable = True pass_through = True - + def __init__(self, x_transform, y_transform): """ Create a new "blended" transform using x_transform to @@ -1479,13 +1459,13 @@ create. """ # Here we ask: "Does it blend?" - + Transform.__init__(self) self._x = x_transform self._y = y_transform self.set_children(x_transform, y_transform) self._affine = None - + def _get_is_affine(self): return self._x.is_affine and self._y.is_affine is_affine = property(_get_is_affine) @@ -1493,7 +1473,7 @@ def frozen(self): return blended_transform_factory(self._x.frozen(), self._y.frozen()) frozen.__doc__ = Transform.frozen.__doc__ - + def __repr__(self): return "BlendedGenericTransform(%s,%s)" % (self._x, self._y) __str__ = __repr__ @@ -1510,7 +1490,7 @@ else: x_points = x.transform(points[:, 0]) x_points = x_points.reshape((len(x_points), 1)) - + if y.input_dims == 2: y_points = y.transform(points)[:, 1:] else: @@ -1555,8 +1535,8 @@ self._invalid = 0 return self._affine get_affine.__doc__ = Transform.get_affine.__doc__ - - + + class BlendedAffine2D(Affine2DBase): """ A "blended" transform uses one transform for the x-direction, and @@ -1567,14 +1547,14 @@ """ is_separable = True - + def __init__(self, x_transform, y_transform): """ Create a new "blended" transform using x_transform to transform the x-axis and y_transform to transform the y_axis. Both x_transform and y_transform must be 2D affine transforms. - + You will generally not call this constructor directly but use the blended_transform_factory function instead, which can determine automatically which kind of blended transform to @@ -1589,14 +1569,14 @@ self._x = x_transform self._y = y_transform self.set_children(x_transform, y_transform) - + Affine2DBase.__init__(self) self._mtx = None def __repr__(self): return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - + def get_matrix(self): if self._invalid: if self._x == self._y: @@ -1612,8 +1592,8 @@ self._invalid = 0 return self._mtx get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ - - + + def blended_transform_factory(x_transform, y_transform): """ Create a new "blended" transform using x_transform to @@ -1635,7 +1615,7 @@ This "generic" version can handle any two arbitrary transformations. """ pass_through = True - + def __init__(self, a, b): """ Create a new composite transform that is the result of @@ -1649,7 +1629,7 @@ assert a.output_dims == b.input_dims self.input_dims = a.input_dims self.output_dims = b.output_dims - + Transform.__init__(self) self._a = a self._b = b @@ -1662,15 +1642,15 @@ return frozen.frozen() return frozen frozen.__doc__ = Transform.frozen.__doc__ - + def _get_is_affine(self): return self._a.is_affine and self._b.is_affine is_affine = property(_get_is_affine) - + def _get_is_separable(self): return self._a.is_separable and self._b.is_separable is_separable = property(_get_is_separable) - + def __repr__(self): return "CompositeGenericTransform(%s, %s)" % (self._a, self._b) __str__ = __repr__ @@ -1679,7 +1659,7 @@ return self._b.transform( self._a.transform(points)) transform.__doc__ = Transform.transform.__doc__ - + def transform_affine(self, points): return self.get_affine().transform(points) transform_affine.__doc__ = Transform.transform_affine.__doc__ @@ -1695,7 +1675,7 @@ return self._b.transform_path( self._a.transform_path(path)) transform_path.__doc__ = Transform.transform_path.__doc__ - + def transform_path_affine(self, path): return self._b.transform_path_affine( self._a.transform_path(path)) @@ -1707,7 +1687,7 @@ return self._b.transform_path_non_affine( self._a.transform_path(path)) transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ - + def get_affine(self): if self._a.is_affine and self._b.is_affine: return Affine2D(npy.dot(self._b.get_affine().get_matrix(), @@ -1715,12 +1695,12 @@ else: return self._b.get_affine() get_affine.__doc__ = Transform.get_affine.__doc__ - + def inverted(self): return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) inverted.__doc__ = Transform.inverted.__doc__ - + class CompositeAffine2D(Affine2DBase): """ A composite transform formed by applying transform a then transform b. @@ -1734,7 +1714,7 @@ applying transform a then transform b. Both a and b must be instances of Affine2DBase. - + You will generally not call this constructor directly but use the composite_transform_factory function instead, which can automatically choose the best kind of composite transform @@ -1765,8 +1745,8 @@ self._invalid = 0 return self._mtx get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ - - + + def composite_transform_factory(a, b): """ Create a new composite transform that is the result of applying @@ -1787,8 +1767,8 @@ elif isinstance(a, AffineBase) and isinstance(b, AffineBase): return CompositeAffine2D(a, b) return CompositeGenericTransform(a, b) - - + + class BboxTransform(Affine2DBase): """ BboxTransform linearly transforms points from one Bbox to another Bbox. @@ -1802,7 +1782,7 @@ """ assert boxin.is_bbox assert boxout.is_bbox - + Affine2DBase.__init__(self) self._boxin = boxin self._boxout = boxout @@ -1813,7 +1793,7 @@ def __repr__(self): return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - + def get_matrix(self): if self._invalid: inl, inb, inw, inh = self._boxin.bounds @@ -1845,7 +1825,7 @@ from the unit Bbox to boxout. """ assert boxout.is_bbox - + Affine2DBase.__init__(self) self._boxout = boxout self.set_children(boxout) @@ -1855,7 +1835,7 @@ def __repr__(self): return "BboxTransformTo(%s)" % (self._boxout) __str__ = __repr__ - + def get_matrix(self): if self._invalid: outl, outb, outw, outh = self._boxout.bounds @@ -1884,7 +1864,7 @@ from boxin to the unit Bbox. """ assert boxin.is_bbox - + Affine2DBase.__init__(self) self._boxin = boxin self.set_children(boxin) @@ -1894,7 +1874,7 @@ def __repr__(self): return "BboxTransformFrom(%s)" % (self._boxin) __str__ = __repr__ - + def get_matrix(self): if self._invalid: inl, inb, inw, inh = self._boxin.bounds @@ -1910,8 +1890,8 @@ self._invalid = 0 return self._mtx get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ - + class TransformedPath(TransformNode): """ A TransformedPath caches a non-affine transformed copy of the @@ -1924,7 +1904,7 @@ """ assert isinstance(transform, Transform) TransformNode.__init__(self) - + self._path = path self._transform = transform self.set_children(transform) @@ -1957,7 +1937,7 @@ def get_affine(self): return self._transform.get_affine() - + def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): ''' Ensure the endpoints of a range are not too close together. @@ -1996,7 +1976,7 @@ return ( ((a < b) and (a < val and b > val)) or (b < val and a > val)) - + if __name__ == '__main__': import copy from random import random @@ -2019,10 +1999,10 @@ assert bbox.bounds == (10, 15, 10, 10) assert tuple(npy.asarray(bbox).flatten()) == (10, 15, 20, 25) - + bbox.intervalx = (11, 21) bbox.intervaly = (16, 26) - + assert bbox.bounds == (11, 16, 10, 10) bbox.x0 = 12 @@ -2040,7 +2020,7 @@ bbox_copy.p1 = (14, 15) assert bbox.bounds == (10, 11, 12, 13) assert bbox_copy.bounds == (10, 11, 4, 4) - + bbox1 = Bbox([[10., 15.], [20., 25.]]) bbox2 = Bbox([[30., 35.], [40., 45.]]) trans = BboxTransform(bbox1, bbox2) @@ -2055,7 +2035,7 @@ assert rotation.to_values() == (0.86602540378443871, 0.49999999999999994, -0.49999999999999994, 0.86602540378443871, 0.0, 0.0) - + points = npy.array([[1, 2], [3, 4], [5, 6], [7, 8]], npy.float_) translated_points = translation.transform(points) assert (translated_points == [[11., 22.], [13., 24.], [15., 26.], [17., 28.]]).all() @@ -2071,7 +2051,7 @@ assert (tpoints1.round() == tpoints2.round()).all() print points - + # Here are some timing tests points = npy.asarray([(random(), random()) for i in xrange(10000)]) t = timeit.Timer("trans_sum.transform(points)", "from __main__ import trans_sum, points") Modified: branches/transforms/src/_path.cpp =================================================================== --- branches/transforms/src/_path.cpp 2007-11-28 18:26:40 UTC (rev 4488) +++ branches/transforms/src/_path.cpp 2007-11-28 18:27:43 UTC (rev 4489) @@ -31,6 +31,8 @@ "point_on_path(x, y, r, path, trans)"); add_varargs_method("get_path_extents", &_path_module::get_path_extents, "get_path_extents(path, trans)"); + add_varargs_method("update_path_extents", &_path_module::update_path_extents, + "update_path_extents(path, trans, bbox, minpos)"); add_varargs_method("get_path_collection_extents", &_path_module::get_path_collection_extents, "get_path_collection_extents(trans, paths, transforms, offsets, offsetTrans)"); add_varargs_method("point_in_path_collection", &_path_module::point_in_path_collection, @@ -45,7 +47,7 @@ "count_bboxes_overlapping_bbox(bbox, bboxes)"); add_varargs_method("path_intersects_path", &_path_module::path_intersects_path, "path_intersects_path(p1, p2)"); - + initialize("Helper functions for paths"); } @@ -56,6 +58,7 @@ Py::Object point_in_path(const Py::Tuple& args); Py::Object point_on_path(const Py::Tuple& args); Py::Object get_path_extents(const Py::Tuple& args); + Py::Object update_path_extents(const Py::Tuple& args); Py::Object get_path_collection_extents(const Py::Tuple& args); Py::Object point_in_path_collection(const Py::Tuple& args); Py::Object path_in_path(const Py::Tuple& args); @@ -71,7 +74,7 @@ // just polygons. The original comments have been kept intact. // -- Michael Droettboom 2007-10-02 // -//======= Crossings Multiply algorithm of InsideTest ======================== +//======= Crossings Multiply algorithm of InsideTest ======================== // // By Eric Haines, 3D/Eye Inc, er...@ey... // @@ -164,10 +167,10 @@ yflag0 = yflag1; vtx0 = vtx1; vty0 = vty1; - + vtx1 = x; vty1 = y; - } while (code != agg::path_cmd_stop && + } while (code != agg::path_cmd_stop && (code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); yflag1 = (vty1 >= ty); @@ -188,7 +191,7 @@ inline bool point_in_path(double x, double y, PathIterator& path, const agg::trans_affine& trans) { typedef agg::conv_transform<PathIterator> transformed_path_t; typedef agg::conv_curve<transformed_path_t> curve_t; - + if (path.total_vertices() < 3) return false; @@ -211,7 +214,7 @@ Py::Object _path_module::point_in_path(const Py::Tuple& args) { args.verify_length(4); - + double x = Py::Float(args[0]); double y = Py::Float(args[1]); PathIterator path(args[2]); @@ -224,7 +227,7 @@ Py::Object _path_module::point_on_path(const Py::Tuple& args) { args.verify_length(5); - + double x = Py::Float(args[0]); double y = Py::Float(args[1]); double r = Py::Float(args[2]); @@ -236,8 +239,9 @@ return Py::Int(0); } -void get_path_extents(PathIterator& path, const agg::trans_affine& trans, - double* x0, double* y0, double* x1, double* y1) { +void get_path_extents(PathIterator& path, const agg::trans_affine& trans, + double* x0, double* y0, double* x1, double* y1, + double* xm, double* ym) { typedef agg::conv_transform<PathIterator> transformed_path_t; typedef agg::conv_curve<transformed_path_t> curve_t; double x, y; @@ -251,8 +255,16 @@ while ((code = curved_path.vertex(&x, &y)) != agg::path_cmd_stop) { if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) continue; - if (x < *x0) *x0 = x; - if (y < *y0) *y0 = y; + if (x < *x0) { + *x0 = x; + if (x > 0.0) + *xm = x; + } + if (y < *y0) { + *y0 = y; + if (y > 0.0) + *ym = y; + } if (x > *x1) *x1 = x; if (y > *y1) *y1 = y; } @@ -260,22 +272,121 @@ Py::Object _path_module::get_path_extents(const Py::Tuple& args) { args.verify_length(2); - + PathIterator path(args[0]); - agg::trans_affine trans = py_to_agg_transformation_matrix(args[1]); + agg::trans_affine trans = py_to_agg_transformation_matrix(args[1], false); - double x0 = std::numeric_limits<double>::infinity(); - double y0 = std::numeric_limits<double>::infinity(); - double x1 = -std::numeric_limits<double>::infinity(); - double y1 = -std::numeric_limits<double>::infinity(); + npy_intp extent_dims[] = { 2, 2, 0 }; + double* extents_data = new double[4]; + double xm, ym; + PyArrayObject* extents = NULL; + try { + extents_data[0] = std::numeric_limits<double>::infinity(); + extents_data[1] = std::numeric_limits<double>::infinity(); + extents_data[2] = -std::numeric_limits<double>::infinity(); + extents_data[3] = -std::numeric_limits<double>::infinity(); - ::get_path_extents(path, trans, &x0, &y0, &x1, &y1); + ::get_path_extents(path, trans, + &extents_data[0], &extents_data[1], &extents_data[2], &extents_data[3], + &xm, &ym); - Py::Tuple result(4); - result[0] = Py::Float(x0); - result[1] = Py::Float(y0); - result[2] = Py::Float(x1); - result[3] = Py::Float(y1); + extents = (PyArrayObject*)PyArray_SimpleNewFromData + (2, extent_dims, PyArray_DOUBLE, extents_data); + } catch (...) { + if (extents) + Py_XDECREF(extents); + else + delete[] extents_data; + throw; + } + + return Py::Object((PyObject*)extents); +} + +Py::Object _path_module::update_path_extents(const Py::Tuple& args) { + args.verify_length(5); + + double x0, y0, x1, y1; + PathIterator path(args[0]); + agg::trans_affine trans = py_to_agg_transformation_matrix(args[1], false); + if (!py_convert_bbox(args[2].ptr(), x0, y0, x1, y1)) { + throw Py::ValueError("Must pass Bbox object as arg 3 of update_path_extents"); + } + Py::Object minpos_obj = args[3]; + bool ignore = bool(Py::Int(args[4])); + + double xm, ym; + PyArrayObject* input_minpos = NULL; + try { + input_minpos = (PyArrayObject*)PyArray_FromObject(minpos_obj.ptr(), PyArray_DOUBLE, 1, 1); + if (!input_minpos || PyArray_DIM(input_minpos, 0) != 2) { + throw Py::TypeError("Argument 4 to update_path_extents must be a length-2 numpy array."); + } + xm = *(double*)PyArray_GETPTR1(input_minpos, 0); + ym = *(double*)PyArray_GETPTR1(input_minpos, 1); + } catch (...) { + Py_XDECREF(input_minpos); + throw; + } + Py_XDECREF(input_minpos); + + npy_intp extent_dims[] = { 2, 2, 0 }; + double* extents_data = new double[4]; + npy_intp minpos_dims[] = { 2, 0 }; + double* minpos_data = new double[2]; + PyArrayObject* extents = NULL; + PyArrayObject* minpos = NULL; + bool changed = false; + + try { + if (ignore) { + extents_data[0] = std::numeric_limits<double>::infinity(); + extents_data[1] = std::numeric_limits<double>::infinity(); + extents_data[2] = -std::numeric_limits<double>::infinity(); + extents_data[3] = -std::numeric_limits<double>::infinity(); + minpos_data[0] = std::numeric_limits<double>::infinity(); + minpos_data[1] = std::numeric_limits<double>::infinity(); + } else { + extents_data[0] = std::min(x0, x1); + extents_data[1] = std::min(y0, y1); + extents_data[2] = std::max(x0, x1); + extents_data[3] = std::max(y0, y1); + minpos_data[0] = xm; + minpos_data[1] = ym; + } + + ::get_path_extents(path, trans, + &extents_data[0], &extents_data[1], &extents_data[2], &extents_data[3], + &minpos_data[0], &minpos_data[1]); + + changed = (extents_data[0] != x0 || + extents_data[1] != y0 || + extents_data[2] != x1 || + extents_data[3] != y1 || + minpos_data[0] != xm || + minpos_data[1] != ym); + + extents = (PyArrayObject*)PyArray_SimpleNewFromData + (2, extent_dims, PyArray_DOUBLE, extents_data); + minpos = (PyArrayObject*)PyArray_SimpleNewFromData + (1, minpos_dims, PyArray_DOUBLE, minpos_data); + } catch(...) { + if (extents) + Py_XDECREF(extents); + else + delete[] extents_data; + if (minpos) + Py_XDECREF(minpos); + else + delete[] minpos_data; + throw; + } + + Py::Tuple result(3); + result[0] = Py::Object((PyObject*) extents); + result[1] = Py::Object((PyObject*) minpos); + result[2] = Py::Int(changed ? 1 : 0); + return result; } @@ -290,12 +401,12 @@ agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[4], false); PyArrayObject* offsets = NULL; - double x0, y0, x1, y1; + double x0, y0, x1, y1, xm, ym; try { offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 0, 2); - if (!offsets || - (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) != 2) || + if (!offsets || + (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) != 2) || (PyArray_NDIM(offsets) == 1 && PyArray_DIM(offsets, 0) != 0)) { throw Py::ValueError("Offsets array must be Nx2"); } @@ -316,7 +427,7 @@ trans *= master_transform; transforms.push_back(trans); } - + // The offset each of those and collect the mins/maxs x0 = std::numeric_limits<double>::infinity(); y0 = std::numeric_limits<double>::infinity(); @@ -339,7 +450,7 @@ trans *= agg::trans_affine_translation(xo, yo); } - ::get_path_extents(path, trans, &x0, &y0, &x1, &y1); + ::get_path_extents(path, trans, &x0, &y0, &x1, &y1, &xm, &ym); } } catch (...) { Py_XDECREF(offsets); @@ -369,10 +480,10 @@ Py::SeqBase<Py::Object> offsets_obj = args[6]; agg::trans_affine offset_trans = py_to_agg_transformation_matrix(args[7]); bool filled = Py::Int(args[8]); - + PyArrayObject* offsets = (PyArrayObject*)PyArray_FromObject(offsets_obj.ptr(), PyArray_DOUBLE, 0, 2); - if (!offsets || - (PyArray_NDIM(offsets) == 2 && PyArray_DIM(offsets, 1) !=... [truncated message content] |