package ij.gui; import java.awt.*; import java.awt.image.*; import java.awt.geom.*; import java.awt.event.KeyEvent; import java.util.*; import ij.*; import ij.process.*; import ij.measure.*; import ij.plugin.frame.Recorder; import ij.plugin.filter.Analyzer; import ij.util.Tools; /**A subclass of ij.gui.Roi (2D Regions Of Interest) implemented in terms of java.awt.Shape. * A ShapeRoi is constructed from a ij.gui.Roi object, or as a result of logical operators * (i.e., union, intersection, exclusive or, and subtraction) provided by this class. These operators use the package * java.awt.geom as a backend.
* This code is in the public domain. * @author Cezar M.Tigaret */ public class ShapeRoi extends Roi { /***/ static final int NO_TYPE = 128; /**The maximum tolerance allowed in calculating the length of the curve segments of this ROI's shape.*/ static final double MAXERROR = 1.0e-3; /**Coefficient used to obtain a flattened version of this ROI's shape. A flattened shape is the * closest approximation of the original shape's curve segments with line segments.*/ static final double FLATNESS = 0.1; /**Parsing a shape composed of linear segments less than this value will result in Roi objects of type * {@link ij.gui.Roi#POLYLINE} and {@link ij.gui.Roi#POLYGON} for open and closed shapes, respectively. * Conversion of shapes open and closed with more than MAXPOLY line segments will result, * respectively, in {@link ij.gui.Roi#FREELINE} and {@link ij.gui.Roi#FREEROI} (or * {@link ij.gui.Roi#TRACED_ROI} if {@link #forceTrace} flag is true. */ private static final int MAXPOLY = 10; // I hate arbitrary values !!!! private static final int OR=0, AND=1, XOR=2, NOT=3; private static final double SHAPE_TO_ROI=-1.0; /**The java.awt.Shape encapsulated by this object.*/ private Shape shape; /**The instance value of the maximum tolerance (MAXERROR) allowed in calculating the * length of the curve segments of this ROI's shape. */ private double maxerror = ShapeRoi.MAXERROR; /**The instance value of the coefficient (FLATNESS) used to * obtain a flattened version of this ROI's shape. */ private double flatness = ShapeRoi.FLATNESS; /**The instance value of MAXPOLY.*/ private int maxPoly = ShapeRoi.MAXPOLY; /**If true then methods that manipulate this ROI's shape will work on * a flattened version of the shape. */ private boolean flatten; /**Flag which specifies how Roi objects will be constructed from closed (sub)paths having more than * MAXPOLY and composed exclusively of line segments. * If true then (sub)path will be parsed into a * {@link ij.gui.Roi#TRACED_ROI}; else, into a {@link ij.gui.Roi#FREEROI}. */ private boolean forceTrace = false; /**Flag which specifies if Roi objects constructed from open (sub)paths composed of only two line segments * will be of type {@link ij.gui.Roi#ANGLE}. * If true then (sub)path will be parsed into a {@link ij.gui.Roi#ANGLE}; * else, into a {@link ij.gui.Roi#POLYLINE}. */ private boolean forceAngle = false; private Vector savedRois; /** Constructs a ShapeRoi from an Roi. */ public ShapeRoi(Roi r) { this(r, ShapeRoi.FLATNESS, ShapeRoi.MAXERROR, false, false, false, ShapeRoi.MAXPOLY); } /** Constructs a ShapeRoi from a Shape. */ public ShapeRoi(Shape s) { super(s.getBounds()); AffineTransform at = new AffineTransform(); at.translate(-x, -y); shape = new GeneralPath(at.createTransformedShape(s)); type = COMPOSITE; } /** Constructs a ShapeRoi from a Shape. */ public ShapeRoi(int x, int y, Shape s) { super(x, y, s.getBounds().width, s.getBounds().height); shape = new GeneralPath(s); type = COMPOSITE; } /**Creates a ShapeRoi object from a "classical" ImageJ ROI. * @param r An ij.gui.Roi object * @param flatness The flatness factor used in convertion of curve segments into line segments. * @param maxerror Error correction for calculating length of Bezeir curves. * @param forceAngle flag used in the conversion of Shape objects to Roi objects (see {@link #shapeToRois()}. * @param forceTrace flag for conversion of Shape objects to Roi objects (see {@link #shapeToRois()}. * @param flatten if true then the shape of this ROI will be flattened * (i.e., curve segments will be aproximated by line segments). * @param maxPoly Roi objects constructed from shapes composed of linear segments fewer than this * value will be of type {@link ij.gui.Roi#POLYLINE} or {@link ij.gui.Roi#POLYGON}; conversion of * shapes with linear segments more than this value will result in Roi objects of type * {@link ij.gui.Roi#FREELINE} or {@link ij.gui.Roi#FREEROI} (see {@link #shapeToRois()}). */ ShapeRoi(Roi r, double flatness, double maxerror, boolean forceAngle, boolean forceTrace, boolean flatten, int maxPoly) { super(r.startX, r.startY, r.width, r.height); this.type = COMPOSITE; this.flatness = flatness; this.maxerror = maxerror; this.forceAngle = forceAngle; this.forceTrace = forceTrace; this.maxPoly= maxPoly; this.flatten = flatten; state = r.getState(); setImage(r.imp); shape = roiToShape((Roi)r.clone()); //IJ.log("ShapeRoi:"+x+" "+y); if(ic!=null) { Graphics g = ic.getGraphics(); draw(g); g.dispose(); } } /** Constructs a ShapeRoi from an array of variable length path segments. Each segment consists of the segment type followed by 0-3 end points and control points. Depending on the type, a segment uses from 1 to 7 elements of the array. */ public ShapeRoi(float[] shapeArray) { super(0,0,null); shape = makeShapeFromArray(shapeArray); Rectangle r = shape.getBounds(); x = r.x; y = r.y; width = r.width; height = r.height; state = NORMAL; oldX=x; oldY=y; oldWidth=width; oldHeight=height; AffineTransform at = new AffineTransform(); at.translate(-x, -y); shape = new GeneralPath(at.createTransformedShape(shape)); flatness = ShapeRoi.FLATNESS; maxerror = ShapeRoi.MAXERROR; maxPoly = ShapeRoi.MAXPOLY; flatten = false; type = COMPOSITE; } /**Returns a deep copy of this. */ public synchronized Object clone() { // the equivalent of "operator=" ? ShapeRoi sr = (ShapeRoi)super.clone(); sr.type = COMPOSITE; sr.flatness = flatness; sr.maxerror = maxerror; sr.forceAngle = forceAngle; sr.forceTrace = forceTrace; //sr.setImage(imp); //wsr sr.setShape(ShapeRoi.cloneShape(shape)); return sr; } /**Returns a deep copy of the argument. */ static Shape cloneShape(Shape rhs) { if(rhs==null) return null; if(rhs instanceof Rectangle2D.Double) { return (Rectangle2D.Double)((Rectangle2D.Double)rhs).clone(); } else if(rhs instanceof Ellipse2D.Double) { return (Ellipse2D.Double)((Ellipse2D.Double)rhs).clone(); } else if(rhs instanceof Line2D.Double) { return (Line2D.Double)((Line2D.Double)rhs).clone(); } else if(rhs instanceof Polygon) { return new Polygon(((Polygon)rhs).xpoints, ((Polygon)rhs).ypoints, ((Polygon)rhs).npoints); } else if(rhs instanceof GeneralPath) { return (GeneralPath)((GeneralPath)rhs).clone(); } return new GeneralPath(); // dodgy !!! } /**********************************************************************************/ /*** Logical operations on shaped rois ****/ /**********************************************************************************/ /**Unary union operator. * The caller is set to its union with the argument. * @return the union of this and sr */ public ShapeRoi or(ShapeRoi sr) {return unaryOp(sr, OR);} /**Unary intersection operator. * The caller is set to its intersection with the argument (i.e., the overlapping regions between the * operands). * @return the overlapping regions between this and sr */ public ShapeRoi and(ShapeRoi sr) {return unaryOp(sr, AND);} /**Unary exclusive or operator. * The caller is set to the non-overlapping regions between the operands. * @return the union of the non-overlapping regions of this and sr */ public ShapeRoi xor(ShapeRoi sr) {return unaryOp(sr, XOR);} /**Unary subtraction operator. * The caller is set to the result of the operation between the operands. * @return this subtracted from sr */ public ShapeRoi not(ShapeRoi sr) {return unaryOp(sr, NOT);} ShapeRoi unaryOp(ShapeRoi sr, int op) { AffineTransform at = new AffineTransform(); at.translate(x, y); Area a1 = new Area(at.createTransformedShape(getShape())); at = new AffineTransform(); at.translate(sr.x, sr.y); Area a2 = new Area(at.createTransformedShape(sr.getShape())); switch (op) { case OR: a1.add(a2); break; case AND: a1.intersect(a2); break; case XOR: a1.exclusiveOr(a2); break; case NOT: a1.subtract(a2); break; } Rectangle r = a1.getBounds(); at = new AffineTransform(); at.translate(-r.x, -r.y); setShape(new GeneralPath(at.createTransformedShape(a1))); x = r.x; y = r.y; return this; } /**********************************************************************************/ /*** Interconversions between "regular" rois and shaped rois ****/ /**********************************************************************************/ /**Converts the Roi argument to an instance of java.awt.Shape. * Currently, the following conversions are supported:
Roi class Roi type Shape Winding
rule
Flag
forceAngle
Flag
forceTrace
Flag
complexShape
ij.gui.Roi Roi.RECTANGLE java.awt.geom.Rectangle2D.Double false false false
ij.gui.OvalRoi Roi.OVAL java.awt.geom.Ellipse2D.Double false false false
ij.gui.Line Roi.LINE java.awt.geom.Line2D.Double false false false
ij.gui.PolygonRoi Roi.POLYGON java.awt.Polygon false false false
ij.gui.PolygonRoi Roi.FREEROI closed java.awt.geom.GeneralPath GeneralPath.WIND_EVEN_ODD false false false
ij.gui.PolygonRoi Roi.TRACED_ROI closed java.awt.geom.GeneralPath GeneralPath.WIND_EVEN_ODD false true false
ij.gui.PolygonRoi Roi.POLYLINE open java.awt.geom.GeneralPath GeneralPath.WIND_NON_ZERO false false false
ij.gui.PolygonRoi Roi.FREELINE open java.awt.geom.GeneralPath GeneralPath.WIND_NON_ZERO false false false
ij.gui.PolygonRoi Roi.ANGLE open java.awt.geom.GeneralPath GeneralPath.WIND_NON_ZERO true false false
ij.gui.ShapeRoi Roi.COMPOSITE shape of argument winding rule of
argument
flag of
argument
flag of
argument
flag of
argument
ij.gui.ShapeRoi ShapeRoi.NO_TYPE null false false false
* * @return A java.awt.geom.* object that inherits from java.awt.Shape interface. * */ private Shape roiToShape(Roi roi) { Shape shape = null; Rectangle r = roi.getBounds(); int[] xCoords = null; int[] yCoords = null; int nCoords = 0; switch(roi.getType()) { case Roi.LINE: Line line = (Line)roi; shape = new Line2D.Double ((double)(line.x1-r.x), (double)(line.y1-r.y), (double)(line.x2-r.x), (double)(line.y2-r.y) ); break; case Roi.RECTANGLE: shape = new Rectangle2D.Double(0.0, 0.0, (double)r.width, (double)r.height); break; case Roi.OVAL: Polygon p = roi.getPolygon(); for (int i=0; i=len) return -1; seg[0]=array[index++]; int type = (int)seg[0]; if (type==PathIterator.SEG_CLOSE) return 1; if (index>=len) return -1; seg[1]=array[index++]; if (index>=len) return -1; seg[2]=array[index++]; if (type==PathIterator.SEG_MOVETO||type==PathIterator.SEG_LINETO) return 3; if (index>=len) return -1; seg[3]=array[index++]; if (index>=len) return -1; seg[4]=array[index++]; if (type==PathIterator.SEG_QUADTO) return 5; if (index>=len) return -1; seg[5]=array[index++]; if (index>=len) return -1; seg[6]=array[index++]; if (type==PathIterator.SEG_CUBICTO) return 7; return -1; } /** Saves an Roi so it can be retrieved later using getRois(). */ void saveRoi(Roi roi) { if (savedRois==null) savedRois = new Vector(); savedRois.addElement(roi); } /**Converts a Shape into Roi object(s). *
This method parses the shape into (possibly more than one) Roi objects * and returns them in an array. *
A simple, "regular" path results in a single Roi following these simple rules:
Shape type Roi class Roi type
java.awt.geom.Rectangle2D.Double ij.gui.Roi Roi.RECTANGLE
java.awt.geom.Ellipse2D.Double ij.gui.OvalRoi Roi.OVAL
java.awt.geom.Line2D.Double ij.gui.Line Roi.LINE
java.awt.Polygon ij.gui.PolygonRoi Roi.POLYGON
*

Each subpath of a java.awt.geom.GeneralPath is converted following these rules:
Segment
types
Number of
segments
Closed
path
Value of
forceAngle
Value of
forceTrace
Roi type
lines only: 0 ShapeRoi.NO_TYPE
1 ShapeRoi.NO_TYPE
2 Y ShapeRoi.NO_TYPE
N Roi.LINE
3 Y N Roi.POLYGON
N Y Roi.ANGLE
N N Roi.POLYLINE
4 Y Roi.RECTANGLE
N Roi.POLYLINE
<= MAXPOLY Y Roi.POLYGON
N Roi.POLYLINE
> MAXPOLY Y Y Roi.TRACED_ROI
N Roi.FREEROI
N Roi.FREELINE
anything
else:
<= 2 ShapeRoi.NO_TYPE
> 2 ShapeRoi.SHAPE_ROI
* @return an array of ij.gui.Roi objects. */ public Roi[] getRois () { if(shape==null) return new Roi[0]; if (savedRois!=null) return getSavedRois(); Vector rois = new Vector(); if (shape instanceof Rectangle2D.Double) { Roi r = new Roi((int)((Rectangle2D.Double)shape).getX(), (int)((Rectangle2D.Double)shape).getY(), (int)((Rectangle2D.Double)shape).getWidth(), (int)((Rectangle2D.Double)shape).getHeight()); rois.addElement(r); } else if (shape instanceof Ellipse2D.Double) { Roi r = new OvalRoi((int)((Ellipse2D.Double)shape).getX(), (int)((Ellipse2D.Double)shape).getY(), (int)((Ellipse2D.Double)shape).getWidth(), (int)((Ellipse2D.Double)shape).getHeight()); rois.addElement(r); } else if (shape instanceof Line2D.Double) { Roi r = new ij.gui.Line((int)((Line2D.Double)shape).getX1(), (int)((Line2D.Double)shape).getY1(), (int)((Line2D.Double)shape).getX2(), (int)((Line2D.Double)shape).getY2()); rois.addElement(r); } else if (shape instanceof Polygon) { Roi r = new PolygonRoi(((Polygon)shape).xpoints, ((Polygon)shape).ypoints, ((Polygon)shape).npoints, Roi.POLYGON); rois.addElement(r); } else if (shape instanceof GeneralPath) { PathIterator pIter; if (flatten) pIter = getFlatteningPathIterator(shape,flatness); else pIter = shape.getPathIterator(new AffineTransform()); parsePath(pIter, null, null, rois, null); } Roi[] array = new Roi[rois.size()]; rois.copyInto((Roi[])array); return array; } Roi[] getSavedRois () { Roi[] array = new Roi[savedRois.size()]; savedRois.copyInto((Roi[])array); return array; } /**Attempts to convert this ShapeRoi into a non-composite Roi. * @return an ij.gui.Roi object or null */ public Roi shapeToRoi() { if(shape==null || !(shape instanceof GeneralPath)) return null; PathIterator pIter = shape.getPathIterator(new AffineTransform()); Vector rois = new Vector(); double[] params = {SHAPE_TO_ROI}; if (!parsePath(pIter, params, null, rois, null)) return null; if (rois.size()==1) return (Roi)rois.elementAt(0); else return null; } /**Implements the rules of conversion from java.awt.geom.GeneralPath to ij.gui.Roi. * @param segments The number of segments that compose the path * @param linesOnly Indicates wether the GeneralPath object is composed only of SEG_LINETO segments * @param curvesOnly Indicates wether the GeneralPath object is composed only of SEG_CUBICTO and SEG_QUADTO segments * @param closed Indicates a closed GeneralPath * @see #shapeToRois() * @return a type flag */ private int guessType(int segments, boolean linesOnly, boolean curvesOnly, boolean closed) { //IJ.log("guessType: "+segments+" "+linesOnly+" "+curvesOnly+" "+closed); closed = true; // lines currently not supported int roiType = Roi.RECTANGLE; if (linesOnly) { switch(segments) { case 0: roiType = NO_TYPE; break; case 1: roiType = NO_TYPE; break; case 2: roiType = (closed ? NO_TYPE : Roi.LINE); break; case 3: roiType = (closed ? Roi.POLYGON : (forceAngle ? Roi.ANGLE: Roi.POLYLINE)); break; case 4: roiType = (closed ? Roi.RECTANGLE : Roi.POLYLINE); break; default: if (segments <= MAXPOLY) roiType = closed ? Roi.POLYGON : Roi.POLYLINE; else roiType = closed ? (forceTrace ? Roi.TRACED_ROI: Roi.FREEROI): Roi.FREELINE; break; } } else roiType = segments >=2 ? Roi.COMPOSITE : NO_TYPE; return roiType; } /**Creates a Roi object based on the arguments. * @see #shapeToRois() * @param xCoords the x coordinates * @param yCoords the y coordinates * @param type the type flag * @return a ij.gui.Roi object */ private Roi createRoi(Vector xCoords, Vector yCoords, int roiType) { if (roiType==NO_TYPE) return null; Roi roi = null; if(xCoords.size() != yCoords.size() || xCoords.size()==0) { return null; } int[] xPoints = new int[xCoords.size()]; int[] yPoints = new int[yCoords.size()]; for (int i=0; i=15.0) { roi = new PolygonRoi(xPoints, yPoints, n, POLYGON); } } break; } //if(roi!=null && imp!=null) roi.setImage(imp); return roi; } /**********************************************************************************/ /*** Geometry ****/ /**********************************************************************************/ /**Checks whether the specified coordinates are inside a on this ROI's shape boundaries.*/ public boolean contains(int x, int y) { if(shape==null) return false; return shape.contains(x-this.x, y-this.y); } /** Caculates "Feret" (maximum caliper width) and "MinFeret" (minimum caliper width). */ public double[] getFeretValues() { double min=Double.MAX_VALUE, diameter=0.0, angle=0.0; int p1=0, p2=0; double pw=1.0, ph=1.0; if (imp!=null) { Calibration cal = imp.getCalibration(); pw = cal.pixelWidth; ph = cal.pixelHeight; } Shape shape = getShape(); Shape s = null; Rectangle2D r = shape.getBounds2D(); double cx = r.getX() + r.getWidth()/2; double cy = r.getY() + r.getHeight()/2; AffineTransform at = new AffineTransform(); at.translate(cx, cy); for (int i=0; i<181; i++) { at.rotate(Math.PI/180.0); s = at.createTransformedShape(shape); r = s.getBounds2D(); double max2 = Math.max(r.getWidth(), r.getHeight()); if (max2>diameter) { diameter = max2*pw; //angle = i; } double min2 = Math.min(r.getWidth(), r.getHeight()); min = Math.min(min, min2); } if (pw!=ph) { diameter = 0.0; angle = 0.0; } if (pw==ph) min *= pw; else { min = 0.0; angle = 0.0; } double[] a = new double[3]; a[0] = diameter; a[1] = angle; a[2] = min; return a; } /**Returns the length of this shape (perimeter, if shape is closed). */ public double getLength() { if(shape==null) return 0.0; Rectangle2D r2d = shape.getBounds2D(); double w = r2d.getWidth(); double h = r2d.getHeight(); if(w==0 && h==0) return 0.0; PathIterator pIter; flatten = true; if(flatten) pIter = getFlatteningPathIterator(shape, flatness); else pIter = shape.getPathIterator(new AffineTransform()); double[] par = new double[1]; parsePath(pIter, par, null, null, null); flatten = false; return par[0]; } /**Returns a flattened version of the path iterator for this ROi's shape*/ FlatteningPathIterator getFlatteningPathIterator(Shape s, double fl) { return (FlatteningPathIterator)s.getPathIterator(new AffineTransform(),fl); } /**Length of the control polygon of the cubic Bézier curve argument, in double precision.*/ double cplength(CubicCurve2D.Double c) { double result = Math.sqrt(Math.pow((c.ctrlx1-c.x1),2.0)+Math.pow((c.ctrly1-c.y1),2.0)); result += Math.sqrt(Math.pow((c.ctrlx2-c.ctrlx1),2.0)+Math.pow((c.ctrly2-c.ctrly1),2.0)); result += Math.sqrt(Math.pow((c.x2-c.ctrlx2),2.0)+Math.pow((c.y2-c.ctrly2),2.0)); return result; } /**Length of the control polygon of the quadratic Bézier curve argument, in double precision.*/ double qplength(QuadCurve2D.Double c) { double result = Math.sqrt(Math.pow((c.ctrlx-c.x1),2.0)+Math.pow((c.ctrly-c.y1),2.0)); result += Math.sqrt(Math.pow((c.x2-c.ctrlx),2.0)+Math.pow((c.y2-c.ctrly),2.0)); return result; } /**Length of the chord of the arc of the cubic Bézier curve argument, in double precision.*/ double cclength(CubicCurve2D.Double c) { return Math.sqrt(Math.pow((c.x2-c.x1),2.0) + Math.pow((c.y2-c.y1),2.0)); } /**Length of the chord of the arc of the quadratic Bézier curve argument, in double precision.*/ double qclength(QuadCurve2D.Double c) { return Math.sqrt(Math.pow((c.x2-c.x1),2.0) + Math.pow((c.y2-c.y1),2.0)); } /**Calculates the length of a cubic Bézier curve specified in double precision. * The algorithm is based on the theory presented in paper
* "Jens Gravesen. Adaptive subdivision and the length and energy of Bézier curves. Computational Geometry 8:13-31 (1997)" * implemented using java.awt.geom.CubicCurve2D.Double. * Please visit {@link Graphics Gems IV} for * examples of other possible implementations in C and C++. */ double cBezLength(CubicCurve2D.Double c) { double l = 0.0; double cl = cclength(c); double pl = cplength(c); if((pl-cl)/2.0 > maxerror) { CubicCurve2D.Double[] cc = cBezSplit(c); for(int i=0; i<2; i++) l+=cBezLength(cc[i]); return l; } l = 0.5*pl+0.5*cl; return l; } /**Calculates the length of a quadratic Bézier curve specified in double precision. * The algorithm is based on the theory presented in paper
* "Jens Gravesen. Adaptive subdivision and the length and energy of Bézier curves. Computational Geometry 8:13-31 (1997)" * implemented using java.awt.geom.CubicCurve2D.Double. * Please visit {@link Graphics Gems IV} for * examples of other possible implementations in C and C++. */ double qBezLength(QuadCurve2D.Double c) { double l = 0.0; double cl = qclength(c); double pl = qplength(c); if((pl-cl)/2.0 > maxerror) { QuadCurve2D.Double[] cc = qBezSplit(c); for(int i=0; i<2; i++) l+=qBezLength(cc[i]); return l; } l = (2.0*pl+cl)/3.0; return l; } /**Splits a cubic Bézier curve in half. * @param c A cubic Bézier curve to be divided * @return an array with the left and right cubic Bézier subcurves * */ CubicCurve2D.Double[] cBezSplit(CubicCurve2D.Double c) { CubicCurve2D.Double[] cc = new CubicCurve2D.Double[2]; for (int i=0; i<2 ; i++) cc[i] = new CubicCurve2D.Double(); c.subdivide(cc[0],cc[1]); return cc; } /**Splits a quadratic Bézier curve in half. * @param c A quadratic Bézier curve to be divided * @return an array with the left and right quadratic Bézier subcurves * */ QuadCurve2D.Double[] qBezSplit(QuadCurve2D.Double c) { QuadCurve2D.Double[] cc = new QuadCurve2D.Double[2]; for(int i=0; i<2; i++) cc[i] = new QuadCurve2D.Double(); c.subdivide(cc[0],cc[1]); return cc; } // c is an array of even length with x0, y0, x1, y1, ... ,xn, yn coordinate pairs /**Scales a coordinate array with the size calibration of a 2D image. * The array is modified in place. * @param c Array of coordinates in double precision with a fixed structure:
* x0,y0,x1,y1,....,xn,yn and with even length of 2*(n+1). * @param pw The x-scale of the image. * @param ph The y-scale of the image. * */ void scaleCoords(double[] c, double pw, double ph) { int k = c.length/2; if (2*k!=c.length) return; // bail out if array has odd length for(int i=0; i * parsePath(pIter, par, null, null) , *
where par is a double array of length one, then get the length as par[0] * @param pIter the PathIterator to be parsed. * @param params an array with one elemet that will hold the calculated length of path; * @param segments a Vector of Integer objects that will hold the types of the segments composing the shape's path * @param rois a Vector that will hold ij.gui.Roi objects constructed from elements of this path * (see @link #shapeToRois()} for details; * @param handles a Vector of Point2D.Double objects representing vertices (segment joinings) and * control points of the curves segments in the iteration order; * @return true if successful.*/ boolean parsePath(PathIterator pIter, double[] params, Vector segments, Vector rois, Vector handles) { //long start = System.currentTimeMillis(); boolean result = true; if(pIter==null) return false; double pw = 1.0, ph = 1.0; if(imp!=null) { Calibration cal = imp.getCalibration(); pw = cal.pixelWidth; ph = cal.pixelHeight; } Vector xCoords = new Vector(); Vector yCoords = new Vector(); if(segments==null) segments = new Vector(); if(handles==null) handles = new Vector(); //if(rois==null) rois = new Vector(); if(params == null) params = new double[1]; boolean shapeToRoi = params[0]==SHAPE_TO_ROI; int subPaths = 0; // the number of subpaths int count = 0;// the number of segments in each subpath w/o SEG_CLOSE; resets to one after each SEG_MOVETO int roiType = Roi.RECTANGLE; int segType; boolean closed = false; boolean linesOnly = true; boolean curvesOnly = true; //boolean success = false; double[] coords; // scaled coordinates of the path segment double[] ucoords; // unscaled coordinates of the path segment double sX = Double.NaN; // start x of subpath (scaled) double sY = Double.NaN; // start y of subpath (scaled) double x0 = Double.NaN; // last x in the subpath (scaled) double y0 = Double.NaN; // last y in the subpath (scaled) double usX = Double.NaN;// unscaled versions of the above double usY = Double.NaN; double ux0 = Double.NaN; double uy0 = Double.NaN; double pathLength = 0.0; Shape curve; // temporary reference to a curve segment of the path boolean done = false; while (!done) { coords = new double[6]; ucoords = new double[6]; segType = pIter.currentSegment(coords); segments.add(new Integer(segType)); count++; System.arraycopy(coords,0,ucoords,0,coords.length); scaleCoords(coords,pw,ph); switch(segType) { case PathIterator.SEG_MOVETO: if(subPaths>0) { closed = ((int)ux0==(int)usX && (int)uy0==(int)usY); if(closed && (int)ux0!=(int)usX && (int)uy0!=(int)usY) { // this may only happen after a SEG_CLOSE xCoords.add(new Integer(((Integer)xCoords.elementAt(0)).intValue())); yCoords.add(new Integer(((Integer)yCoords.elementAt(0)).intValue())); } if (rois!=null) { roiType = guessType(count, linesOnly, curvesOnly, closed); Roi r = createRoi(xCoords, yCoords, roiType); if (r!=null) rois.addElement(r); } xCoords = new Vector(); yCoords = new Vector(); count = 1; } subPaths++; usX = ucoords[0]; usY = ucoords[1]; ux0 = ucoords[0]; uy0 = ucoords[1]; sX = coords[0]; sY = coords[1]; x0 = coords[0]; y0 = coords[1]; handles.add(new Point2D.Double(ucoords[0],ucoords[1])); xCoords.add(new Integer((int)ucoords[0])); yCoords.add(new Integer((int)ucoords[1])); closed = false; break; case PathIterator.SEG_LINETO: linesOnly = linesOnly & true; curvesOnly = curvesOnly & false; pathLength += Math.sqrt(Math.pow((y0-coords[1]),2.0)+Math.pow((x0-coords[0]),2.0)); ux0 = ucoords[0]; uy0 = ucoords[1]; x0 = coords[0]; y0 = coords[1]; handles.add(new Point2D.Double(ucoords[0],ucoords[1])); xCoords.add(new Integer((int)ucoords[0])); yCoords.add(new Integer((int)ucoords[1])); closed = ((int)ux0==(int)usX && (int)uy0==(int)usY); break; case PathIterator.SEG_QUADTO: linesOnly = linesOnly & false; curvesOnly = curvesOnly & true; curve = new QuadCurve2D.Double(x0,y0,coords[0],coords[2],coords[2],coords[3]); pathLength += qBezLength((QuadCurve2D.Double)curve); ux0 = ucoords[2]; uy0 = ucoords[3]; x0 = coords[2]; y0 = coords[3]; handles.add(new Point2D.Double(ucoords[0],ucoords[1])); handles.add(new Point2D.Double(ucoords[2],ucoords[3])); xCoords.add(new Integer((int)ucoords[2])); yCoords.add(new Integer((int)ucoords[3])); closed = ((int)ux0==(int)usX && (int)uy0==(int)usY); break; case PathIterator.SEG_CUBICTO: linesOnly = linesOnly & false; curvesOnly = curvesOnly & true; curve = new CubicCurve2D.Double(x0,y0,coords[0],coords[1],coords[2],coords[3],coords[4],coords[5]); pathLength += cBezLength((CubicCurve2D.Double)curve); ux0 = ucoords[4]; uy0 = ucoords[5]; x0 = coords[4]; y0 = coords[5]; handles.add(new Point2D.Double(ucoords[0],ucoords[1])); handles.add(new Point2D.Double(ucoords[2],ucoords[3])); handles.add(new Point2D.Double(ucoords[4],ucoords[5])); xCoords.add(new Integer((int)ucoords[4])); yCoords.add(new Integer((int)ucoords[5])); closed = ((int)ux0==(int)usX && (int)uy0==(int)usY); break; case PathIterator.SEG_CLOSE: if((int)ux0 != (int)usX && (int)uy0 != (int)usY) pathLength += Math.sqrt(Math.pow((x0-sX),2.0) + Math.pow((y0-sY),2.0)); closed = true; break; default: break; } pIter.next(); done = pIter.isDone() || (shapeToRoi&&rois!=null&&rois.size()==1); if (done) { if(closed && (int)x0!=(int)sX && (int)y0!=(int)sY) { // this may only happen after a SEG_CLOSE xCoords.add(new Integer(((Integer)xCoords.elementAt(0)).intValue())); yCoords.add(new Integer(((Integer)yCoords.elementAt(0)).intValue())); } if (rois!=null) { roiType = shapeToRoi?TRACED_ROI:guessType(count+1, linesOnly, curvesOnly, closed); Roi r = createRoi(xCoords, yCoords, roiType); if (r!=null) rois.addElement(r); } } } params[0] = pathLength; //IJ.log("parsePath:"+ (System.currentTimeMillis()-start)); return result; } /**********************************************************************************/ /*** Drawing and Image routines ****/ /**********************************************************************************/ /** Non-destructively draws the shape of this object on the associated ImagePlus. */ public void draw(Graphics g) { if(ic==null) return; AffineTransform aTx = (((Graphics2D)g).getDeviceConfiguration()).getDefaultTransform(); g.setColor(instanceColor!=null?instanceColor:ROIColor); if (stroke!=null) ((Graphics2D)g).setStroke(stroke); mag = ic.getMagnification(); Rectangle r = ic.getSrcRect(); aTx.setTransform(mag, 0.0, 0.0, mag, -r.x*mag, -r.y*mag); aTx.translate(x, y); ((Graphics2D)g).draw(aTx.createTransformedShape(shape)); if (Toolbar.getToolId()==Toolbar.OVAL) drawRoiBrush(g); showStatus(); if (updateFullWindow) {updateFullWindow = false; imp.draw();} } public void drawRoiBrush(Graphics g) { int size = Toolbar.getBrushSize(); if (size==0) return; int flags = ic.getModifiers(); if ((flags&16)==0) return; // exit if mouse button up size = (int)(size*mag); Point p = ic.getCursorLoc(); int sx = ic.screenX(p.x); int sy = ic.screenY(p.y); g.drawOval(sx-size/2, sy-size/2, size, size); } /**Draws the shape of this object onto the specified ImageProcessor. *
This method will always draw a flattened version of the actual shape * (i.e., all curve segments will be approximated by line segments). */ public void drawPixels(ImageProcessor ip) { PathIterator pIter = getFlatteningPathIterator(shape,flatness); float[] coords = new float[6]; float sx=0f, sy=0f; while (!pIter.isDone()) { int segType = pIter.currentSegment(coords); switch(segType) { case PathIterator.SEG_MOVETO: sx = coords[0]; sy = coords[1]; ip.moveTo(x+(int)sx, y+(int)sy); break; case PathIterator.SEG_LINETO: ip.lineTo(x+(int)coords[0], y+(int)coords[1]); break; case PathIterator.SEG_CLOSE: ip.lineTo(x+(int)sx, y+(int)sy); break; default: break; } pIter.next(); } } /** Returns this ROI's mask pixels as a ByteProcessor with pixels "in" the mask set to white (255) and pixels "outside" the mask set to black (0). */ public ImageProcessor getMask() { if(shape==null) return null; if (cachedMask!=null && cachedMask.getPixels()!=null) return cachedMask; BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); Graphics2D g2d = bi.createGraphics(); g2d.setColor(Color.white); //AffineTransform at = new AffineTransform(); //at.translate(-x, -y); //g2d.fill(at.createTransformedShape(shape)); g2d.fill(shape); Raster raster = bi.getRaster(); DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer(); byte[] mask = buffer.getData(); cachedMask = new ByteProcessor(width, height, mask, null); return cachedMask; } /**********************************************************************************/ /*** Field accessors ****/ /**********************************************************************************/ /**Retrieves the value of {@link #forceTrace} flag.*/ //public boolean getForceTrace() { return forceTrace; } /**Retrieves the value of {@link #forceAngle} flag.*/ //public boolean getForceAngle() { return forceAngle; } /**Retrieves the value of {@link #flatten} flag.*/ //public boolean getFlatten() { return flatten; } /**Retrieves the value of {@link #flatness}*/ //public double getFlatness() { return flatness; } /**Retrieves the value of {@link #maxerror}*/ //public double getMaxError() { return maxerror;} /**Retrieves the value of {@link #maxPoly}*/ //public int getMaxPoly() { return maxPoly; } /**@return false if type equals {@link #NO_TYPE}. */ //public boolean isValid() { return type!=NO_TYPE; } /**Retrieves a reference to the shape of this. */ Shape getShape(){ return shape; } /**Sets the java.awt.Shape object encapsulated by this * to the argument. *
This object will hold a (shallow) copy of the shape argument. If a deep copy * of the shape argumnt is required, then a clone of the argument should be passed * in; a possible example is setShape(ShapeRoi.cloneShape(shape)). * @return false if the argument is null. */ boolean setShape(Shape rhs) { boolean result = true; if (rhs==null) return false; if(shape.equals(rhs)) return false; shape = rhs; type = Roi.COMPOSITE; Rectangle rect = shape.getBounds(); width = rect.width; height = rect.height; return true; } /**********************************************************************************/ /*** Other helpers ****/ /**********************************************************************************/ /**Returns the element with the smallest value in the array argument.*/ private int min(int[] array) { int val = array[0]; for (int i=1; i