3 import java.awt.image.*;
4 import java.awt.geom.*;
5 import java.awt.event.KeyEvent;
10 import ij.plugin.frame.Recorder;
11 import ij.plugin.filter.Analyzer;
23 static final int NO_TYPE = 128;
26 static final double MAXERROR = 1.0e-3;
30 static final double FLATNESS = 0.1;
38 private static final int MAXPOLY = 10;
40 private static final int OR=0, AND=1, XOR=2, NOT=3;
48 private double maxerror =
ShapeRoi.MAXERROR;
53 private double flatness =
ShapeRoi.FLATNESS;
56 private int maxPoly =
ShapeRoi.MAXPOLY;
60 private boolean flatten;
66 private boolean forceTrace =
false;
72 private boolean forceAngle =
false;
96 ShapeRoi(
Roi r,
double flatness,
double maxerror,
boolean forceAngle,
boolean forceTrace,
boolean flatten,
int maxPoly) {
97 super(r.startX, r.startY, r.width, r.height);
98 if(!
IJ.isJava2())
return;
99 this.type = COMPOSITE;
100 this.flatness = flatness;
101 this.maxerror = maxerror;
102 this.forceAngle = forceAngle;
103 this.forceTrace = forceTrace;
104 this.maxPoly= maxPoly;
105 this.flatten = flatten;
106 state = r.getState();
111 Graphics g = ic.getGraphics();
122 if(!
IJ.isJava2())
return;
123 shape = makeShapeFromArray(shapeArray);
124 Rectangle r = shape.getBounds();
131 oldX=x; oldY=y; oldWidth=width; oldHeight=height;
133 AffineTransform at =
new AffineTransform();
134 at.translate(-x, -y);
135 shape = at.createTransformedShape(shape);
144 public synchronized Object
clone() {
147 sr.flatness = flatness;
148 sr.maxerror = maxerror;
149 sr.forceAngle = forceAngle;
150 sr.forceTrace = forceTrace;
152 sr.setShape(
ShapeRoi.cloneShape(shape));
157 static Shape cloneShape(Shape rhs) {
158 if(rhs==null)
return null;
159 if(rhs instanceof Rectangle2D.Double) {
return (Rectangle2D.Double)((Rectangle2D.Double)rhs).
clone(); }
160 else if(rhs instanceof Ellipse2D.Double) {
return (Ellipse2D.Double)((Ellipse2D.Double)rhs).clone(); }
161 else if(rhs instanceof Line2D.Double) {
return (Line2D.Double)((Line2D.Double)rhs).clone(); }
162 else if(rhs instanceof Polygon) {
return new Polygon(((Polygon)rhs).xpoints, ((Polygon)rhs).ypoints, ((Polygon)rhs).npoints); }
163 else if(rhs instanceof GeneralPath) {
return (GeneralPath)((GeneralPath)rhs).clone(); }
164 return new GeneralPath();
197 AffineTransform at =
new AffineTransform();
199 Area a1 =
new Area(at.createTransformedShape(getShape()));
200 at =
new AffineTransform();
201 at.translate(sr.x, sr.y);
202 Area a2 =
new Area(at.createTransformedShape(sr.getShape()));
204 case OR: a1.add(a2);
break;
205 case AND: a1.intersect(a2);
break;
206 case XOR: a1.exclusiveOr(a2);
break;
207 case NOT: a1.subtract(a2);
break;
210 at =
new AffineTransform();
211 at.translate(-r.x, -r.y);
212 setShape(
new GeneralPath(at.createTransformedShape(a1)));
246 private Shape roiToShape(
Roi roi) {
248 Rectangle r = roi.getBounds();
249 int[] xCoords = null;
250 int[] yCoords = null;
252 switch(roi.getType()) {
254 shape =
new Line2D.Double ((
double)(((Line)roi).x1), (
double)(((Line)roi).y1), (
double)(((Line)roi).x2), (
double)(((Line)roi).y2) );
257 shape =
new Rectangle2D.Double(0.0, 0.0, (
double)r.width, (
double)r.height);
260 Polygon p = roi.getPolygon();
261 for (
int i=0; i<p.npoints; i++) {
265 shape =
new Polygon(p.xpoints, p.ypoints, p.npoints);
269 nCoords =((PolygonRoi)roi).getNCoordinates();
270 xCoords = ((PolygonRoi)roi).getXCoordinates();
271 yCoords = ((PolygonRoi)roi).getYCoordinates();
272 shape =
new Polygon(xCoords,yCoords,nCoords);
274 case Roi.FREEROI:
case Roi.TRACED_ROI:
275 nCoords =((PolygonRoi)roi).getNCoordinates();
276 xCoords = ((PolygonRoi)roi).getXCoordinates();
277 yCoords = ((PolygonRoi)roi).getYCoordinates();
278 shape =
new GeneralPath(GeneralPath.WIND_EVEN_ODD,nCoords);
279 ((GeneralPath)shape).moveTo((
float)xCoords[0], (
float)yCoords[0]);
280 for (
int i=1; i<nCoords; i++) ((GeneralPath)shape).lineTo((
float)xCoords[i],(
float)yCoords[i]);
281 ((GeneralPath)shape).closePath();
283 case Roi.POLYLINE:
case Roi.FREELINE:
case Roi.ANGLE:
284 nCoords =((PolygonRoi)roi).getNCoordinates();
285 xCoords = ((PolygonRoi)roi).getXCoordinates();
286 yCoords = ((PolygonRoi)roi).getYCoordinates();
287 shape =
new GeneralPath(GeneralPath.WIND_NON_ZERO,nCoords);
288 ((GeneralPath)shape).moveTo((
float)xCoords[0], (
float)yCoords[0]);
289 for (
int i=1; i<nCoords; i++) ((GeneralPath)shape).lineTo((
float)xCoords[i],(
float)yCoords[i]);
292 default: type = NO_TYPE;
break;
298 Rectangle bounds = shape.getBounds();
299 this.width = bounds.width;
300 this.height = bounds.height;
309 Shape makeShapeFromArray(
float[] array) {
310 if(array==null)
return null;
311 Shape s =
new GeneralPath(GeneralPath.WIND_EVEN_ODD);
312 int index=0, type, len;
313 float[] seg =
new float[7];
315 len = getSegment(array, seg, index);
320 case PathIterator.SEG_MOVETO:
321 ((GeneralPath)s).moveTo(seg[1], seg[2]);
323 case PathIterator.SEG_LINETO:
324 ((GeneralPath)s).lineTo(seg[1], seg[2]);
326 case PathIterator.SEG_QUADTO:
327 ((GeneralPath)s).quadTo(seg[1], seg[2],seg[3], seg[4]);
329 case PathIterator.SEG_CUBICTO:
330 ((GeneralPath)s).curveTo(seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]);
332 case PathIterator.SEG_CLOSE:
333 ((GeneralPath)s).closePath();
341 private int getSegment(
float[] array,
float[] seg,
int index) {
342 int len = array.length;
343 if (index>=len)
return -1; seg[0]=array[index++];
344 int type = (int)seg[0];
345 if (type==PathIterator.SEG_CLOSE)
return 1;
346 if (index>=len)
return -1; seg[1]=array[index++];
347 if (index>=len)
return -1; seg[2]=array[index++];
348 if (type==PathIterator.SEG_MOVETO||type==PathIterator.SEG_LINETO)
return 3;
349 if (index>=len)
return -1; seg[3]=array[index++];
350 if (index>=len)
return -1; seg[4]=array[index++];
351 if (type==PathIterator.SEG_QUADTO)
return 5;
352 if (index>=len)
return -1; seg[5]=array[index++];
353 if (index>=len)
return -1; seg[6]=array[index++];
354 if (type==PathIterator.SEG_CUBICTO)
return 7;
400 if(shape==null)
return new Roi[0];
401 Vector rois =
new Vector();
402 if(shape instanceof Rectangle2D.Double) {
403 Roi r =
new Roi((
int)((Rectangle2D.Double)shape).getX(), (int)((Rectangle2D.Double)shape).getY(), (int)((Rectangle2D.Double)shape).getWidth(), (int)((Rectangle2D.Double)shape).getHeight());
405 }
else if(shape instanceof Ellipse2D.Double) {
406 Roi r =
new OvalRoi((
int)((Ellipse2D.Double)shape).getX(), (int)((Ellipse2D.Double)shape).getY(), (int)((Ellipse2D.Double)shape).getWidth(), (int)((Ellipse2D.Double)shape).getHeight());
408 }
else if(shape instanceof Line2D.Double) {
409 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());
411 }
else if(shape instanceof Polygon) {
412 Roi r =
new PolygonRoi(((Polygon)shape).xpoints, ((Polygon)shape).ypoints, ((Polygon)shape).npoints,
Roi.POLYGON);
414 }
else if(shape instanceof GeneralPath) {
416 if (flatten) pIter = getFlatteningPathIterator(shape,flatness);
417 else pIter = shape.getPathIterator(
new AffineTransform());
418 parsePath(pIter, null, null, rois, null);
420 Roi[] array =
new Roi[rois.size()];
421 rois.copyInto((
Roi[])array);
433 private int guessType(
int segments,
boolean linesOnly,
boolean curvesOnly,
boolean closed) {
436 int roiType =
Roi.RECTANGLE;
439 case 0: roiType = NO_TYPE;
break;
440 case 1: roiType = NO_TYPE;
break;
441 case 2: roiType = (closed ? NO_TYPE :
Roi.LINE);
break;
442 case 3: roiType = (closed ?
Roi.POLYGON : (forceAngle ?
Roi.ANGLE:
Roi.POLYLINE));
break;
443 case 4: roiType = (closed ?
Roi.RECTANGLE :
Roi.POLYLINE);
break;
445 if (segments <= MAXPOLY)
446 roiType = closed ?
Roi.POLYGON :
Roi.POLYLINE;
448 roiType = closed ? (forceTrace ?
Roi.TRACED_ROI:
Roi.FREEROI):
Roi.FREELINE;
452 else roiType = segments >=2 ?
Roi.COMPOSITE : NO_TYPE;
463 private Roi createRoi(Vector xCoords, Vector yCoords,
int roiType) {
464 if (roiType==NO_TYPE)
return null;
466 if(xCoords.size() != yCoords.size() || xCoords.size()==0) {
return null; }
468 int[] xPoints =
new int[xCoords.size()];
469 int[] yPoints =
new int[yCoords.size()];
471 for (
int i=0; i<xPoints.length; i++) {
472 xPoints[i] = ((Integer)xCoords.elementAt(i)).intValue() + x;
473 yPoints[i] = ((Integer)yCoords.elementAt(i)).intValue() + y;
482 case Roi.COMPOSITE: roi =
this;
break;
484 startX = xPoints[xPoints.length-4];
485 startY = yPoints[yPoints.length-3];
486 width = max(xPoints)-min(xPoints);
487 height = max(yPoints)-min(yPoints);
488 roi =
new OvalRoi(startX, startY, width, height);
493 width = max(xPoints)-min(xPoints);
494 height = max(yPoints)-min(yPoints);
495 roi =
new Roi(startX, startY, width, height);
497 case Roi.LINE: roi =
new ij.gui.Line(xPoints[0],yPoints[0],xPoints[1],yPoints[1]);
break;
499 int n = xPoints.length;
500 roi =
new PolygonRoi(xPoints, yPoints, n, roiType);
501 if (roiType==FREEROI) {
502 double length = roi.getLength();
503 double mag = ic!=null?ic.getMagnification():1.0;
506 if (length/n>=15.0) {
507 roi =
new PolygonRoi(xPoints, yPoints, n, POLYGON);
522 if(shape==null)
return false;
523 return shape.contains(x-this.x, y-this.y);
532 if(shape == null)
return 0.0;
534 double pw = 1.0, ph = 1.0;
540 Rectangle2D sr = shape.getBounds2D();
541 double cx = sr.getX() + sr.getWidth()/2;
542 double cy = sr.getY() + sr.getHeight()/2;
546 AffineTransform at =
new AffineTransform();
547 for (
int i=0; i<271; i++) {
549 s = at.createTransformedShape(shape);
551 result = Math.max(result, Math.max(pw*r.getWidth(), ph*r.getHeight()));
593 if(shape==null)
return 0.0;
594 Rectangle2D r2d = shape.getBounds2D();
595 double w = r2d.getWidth();
596 double h = r2d.getHeight();
597 if(w==0 && h==0)
return 0.0;
600 if(flatten) pIter = getFlatteningPathIterator(shape, flatness);
601 else pIter = shape.getPathIterator(
new AffineTransform());
602 double[] par =
new double[1];
603 parsePath(pIter, par, null, null, null);
609 FlatteningPathIterator getFlatteningPathIterator(Shape s,
double fl)
610 {
return (FlatteningPathIterator)s.getPathIterator(
new AffineTransform(),fl); }
613 double cplength(CubicCurve2D.Double c) {
614 double result = Math.sqrt(Math.pow((c.ctrlx1-c.x1),2.0)+Math.pow((c.ctrly1-c.y1),2.0));
615 result += Math.sqrt(Math.pow((c.ctrlx2-c.ctrlx1),2.0)+Math.pow((c.ctrly2-c.ctrly1),2.0));
616 result += Math.sqrt(Math.pow((c.x2-c.ctrlx2),2.0)+Math.pow((c.y2-c.ctrly2),2.0));
621 double qplength(QuadCurve2D.Double c) {
622 double result = Math.sqrt(Math.pow((c.ctrlx-c.x1),2.0)+Math.pow((c.ctrly-c.y1),2.0));
623 result += Math.sqrt(Math.pow((c.x2-c.ctrlx),2.0)+Math.pow((c.y2-c.ctrly),2.0));
628 double cclength(CubicCurve2D.Double c)
629 {
return Math.sqrt(Math.pow((c.x2-c.x1),2.0) + Math.pow((c.y2-c.y1),2.0)); }
632 double qclength(QuadCurve2D.Double c)
633 {
return Math.sqrt(Math.pow((c.x2-c.x1),2.0) + Math.pow((c.y2-c.y1),2.0)); }
642 double cBezLength(CubicCurve2D.Double c) {
644 double cl = cclength(c);
645 double pl = cplength(c);
646 if((pl-cl)/2.0 > maxerror)
648 CubicCurve2D.Double[] cc = cBezSplit(c);
649 for(
int i=0; i<2; i++) l+=cBezLength(cc[i]);
663 double qBezLength(QuadCurve2D.Double c) {
665 double cl = qclength(c);
666 double pl = qplength(c);
667 if((pl-cl)/2.0 > maxerror)
669 QuadCurve2D.Double[] cc = qBezSplit(c);
670 for(
int i=0; i<2; i++) l+=qBezLength(cc[i]);
682 CubicCurve2D.Double[] cBezSplit(CubicCurve2D.Double c) {
683 CubicCurve2D.Double[] cc =
new CubicCurve2D.Double[2];
684 for (
int i=0; i<2 ; i++) cc[i] =
new CubicCurve2D.Double();
685 c.subdivide(cc[0],cc[1]);
694 QuadCurve2D.Double[] qBezSplit(QuadCurve2D.Double c) {
695 QuadCurve2D.Double[] cc =
new QuadCurve2D.Double[2];
696 for(
int i=0; i<2; i++) cc[i] =
new QuadCurve2D.Double();
697 c.subdivide(cc[0],cc[1]);
710 void scaleCoords(
double[] c,
double pw,
double ph) {
712 if (2*k!=c.length)
return;
713 for(
int i=0; i<c.length; i+=2)
720 Vector parseSegments(PathIterator pI) {
721 Vector v =
new Vector();
722 if(parsePath(pI, null, v, null, null))
return v;
732 if(shape==null)
return null;
733 PathIterator pIt = shape.getPathIterator(
new AffineTransform());
734 Vector h =
new Vector();
735 Vector s =
new Vector();
736 if(!(parsePath(pIt, null, s, null, h)))
return null;
737 float[] result =
new float[7*s.size()];
742 for (
int i=0; i<s.size(); i++) {
743 segType = ((Integer)s.elementAt(i)).intValue();
745 case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
746 result[index++] = segType;
747 p = (Point2D.Double)h.elementAt(j++);
748 result[index++]=(float)p.getX()+x; result[index++]=(float)p.getY()+y;
750 case PathIterator.SEG_QUADTO:
751 result[index++] = segType;
752 p = (Point2D.Double)h.elementAt(j++);
753 result[index++]=(float)p.getX()+x; result[index++]=(float)p.getY()+y;
754 p = (Point2D.Double)h.elementAt(j++);
755 result[index++]=(float)p.getX()+x; result[index++]=(float)p.getY()+y;
757 case PathIterator.SEG_CUBICTO:
758 result[index++] = segType;
759 p = (Point2D.Double)h.elementAt(j++);
760 result[index++]=(float)p.getX()+x; result[index++]=(float)p.getY()+y;
761 p = (Point2D.Double)h.elementAt(j++);
762 result[index++]=(float)p.getX()+x; result[index++]=(float)p.getY()+y;
763 p = (Point2D.Double)h.elementAt(j++);
764 result[index++]=(float)p.getX()+x; result[index++]=(float)p.getY()+y;
766 case PathIterator.SEG_CLOSE:
767 result[index++] = segType;
772 float[] result2 =
new float[index];
773 System.arraycopy(result, 0, result2, 0, result2.length);
792 boolean parsePath(PathIterator pIter,
double[] params, Vector segments, Vector rois, Vector handles) {
794 boolean result =
true;
795 if(pIter==null)
return false;
796 double pw = 1.0, ph = 1.0;
802 Vector xCoords =
new Vector();
803 Vector yCoords =
new Vector();
804 if(segments==null) segments =
new Vector();
805 if(handles==null) handles =
new Vector();
807 if(params == null) params =
new double[1];
810 int roiType =
Roi.RECTANGLE;
812 boolean closed =
false;
813 boolean linesOnly =
true;
814 boolean curvesOnly =
true;
818 double sX = Double.NaN;
819 double sY = Double.NaN;
820 double x0 = Double.NaN;
821 double y0 = Double.NaN;
822 double usX = Double.NaN;
823 double usY = Double.NaN;
824 double ux0 = Double.NaN;
825 double uy0 = Double.NaN;
826 double pathLength = 0.0;
828 for(;!pIter.isDone();) {
829 coords =
new double[6];
830 ucoords =
new double[6];
831 segType = pIter.currentSegment(coords);
832 segments.add(
new Integer(segType));
834 System.arraycopy(coords,0,ucoords,0,coords.length);
835 scaleCoords(coords,pw,ph);
837 case PathIterator.SEG_MOVETO:
839 closed = ((int)ux0==(
int)usX && (int)uy0==(
int)usY);
840 if(closed && (
int)ux0!=(
int)usX && (
int)uy0!=(
int)usY) {
841 xCoords.add(
new Integer(((Integer)xCoords.elementAt(0)).intValue()));
842 yCoords.add(
new Integer(((Integer)yCoords.elementAt(0)).intValue()));
845 roiType = guessType(count, linesOnly, curvesOnly, closed);
846 Roi r = createRoi(xCoords, yCoords, roiType);
850 xCoords =
new Vector();
851 yCoords =
new Vector();
863 handles.add(
new Point2D.Double(ucoords[0],ucoords[1]));
864 xCoords.add(
new Integer((
int)ucoords[0]));
865 yCoords.add(
new Integer((
int)ucoords[1]));
868 case PathIterator.SEG_LINETO:
869 linesOnly = linesOnly &
true;
870 curvesOnly = curvesOnly &
false;
871 pathLength += Math.sqrt(Math.pow((y0-coords[1]),2.0)+Math.pow((x0-coords[0]),2.0));
876 handles.add(
new Point2D.Double(ucoords[0],ucoords[1]));
877 xCoords.add(
new Integer((
int)ucoords[0]));
878 yCoords.add(
new Integer((
int)ucoords[1]));
879 closed = ((int)ux0==(
int)usX && (int)uy0==(
int)usY);
881 case PathIterator.SEG_QUADTO:
882 linesOnly = linesOnly & false;
883 curvesOnly = curvesOnly & true;
884 curve = new QuadCurve2D.Double(x0,y0,coords[0],coords[2],coords[2],coords[3]);
885 pathLength += qBezLength((QuadCurve2D.Double)curve);
890 handles.add(new Point2D.Double(ucoords[0],ucoords[1]));
891 handles.add(new Point2D.Double(ucoords[2],ucoords[3]));
892 xCoords.add(new Integer((
int)ucoords[2]));
893 yCoords.add(new Integer((
int)ucoords[3]));
894 closed = ((
int)ux0==(
int)usX && (
int)uy0==(
int)usY);
896 case PathIterator.SEG_CUBICTO:
897 linesOnly = linesOnly & false;
898 curvesOnly = curvesOnly & true;
899 curve = new CubicCurve2D.Double(x0,y0,coords[0],coords[1],coords[2],coords[3],coords[4],coords[5]);
900 pathLength += cBezLength((CubicCurve2D.Double)curve);
905 handles.add(new Point2D.Double(ucoords[0],ucoords[1]));
906 handles.add(new Point2D.Double(ucoords[2],ucoords[3]));
907 handles.add(new Point2D.Double(ucoords[4],ucoords[5]));
908 xCoords.add(new Integer((
int)ucoords[4]));
909 yCoords.add(new Integer((
int)ucoords[5]));
910 closed = ((
int)ux0==(
int)usX && (
int)uy0==(
int)usY);
912 case PathIterator.SEG_CLOSE:
913 if((
int)ux0 != (
int)usX && (
int)uy0 != (
int)usY) pathLength += Math.sqrt(Math.pow((x0-sX),2.0) + Math.pow((y0-sY),2.0));
921 if(closed && (
int)x0!=(int)sX && (
int)y0!=(int)sY) {
922 xCoords.add(
new Integer(((Integer)xCoords.elementAt(0)).intValue()));
923 yCoords.add(
new Integer(((Integer)yCoords.elementAt(0)).intValue()));
926 roiType = guessType(count, linesOnly, curvesOnly, closed);
927 Roi r = createRoi(xCoords, yCoords, roiType);
933 params[0] = pathLength;
945 AffineTransform aTx = (((Graphics2D)g).getDeviceConfiguration()).getDefaultTransform();
946 g.setColor(ROIColor);
947 mag = ic.getMagnification();
948 Rectangle r = ic.getSrcRect();
949 aTx.setTransform(mag,0.0,0.0,mag,-r.x*mag,-r.y*mag);
953 ((Graphics2D)g).draw(aTx.createTransformedShape(shape));
975 if (updateFullWindow) { updateFullWindow =
false; imp.
draw(); }
984 PathIterator pIter = getFlatteningPathIterator(shape,flatness);
986 double x0 = Double.NaN;
987 double y0 = Double.NaN;
988 double sX = Double.NaN;
989 double sY = Double.NaN;
990 for(;!pIter.isDone();)
992 coords =
new double[6];
993 int segType = pIter.currentSegment(coords);
996 case PathIterator.SEG_MOVETO:
1001 ip.
moveTo(x+(
int)coords[0], y+(
int)coords[1]);
1003 case PathIterator.SEG_LINETO:
1006 ip.
lineTo(x+(
int)coords[0], y+(
int)coords[1]);
1008 case PathIterator.SEG_CLOSE:
1009 if(x0 != sX && y0 != sY) ip.
lineTo(x+(
int)sX, y+(
int)sY);
1020 if(shape==null)
return null;
1021 if (cachedMask!=null)
return cachedMask;
1022 BufferedImage bi =
new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
1023 Graphics2D g2d = bi.createGraphics();
1024 g2d.setColor(Color.white);
1029 Raster raster = bi.getRaster();
1030 DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
1031 byte[] mask = buffer.getData();
1032 cachedMask =
new ByteProcessor(width, height, mask, null);
1062 Shape getShape(){
return shape; }
1071 boolean setShape(Shape rhs) {
1072 boolean result =
true;
1073 if (rhs==null) {
IJ.
write(
"rhs null!");
return false;}
1074 if(shape.equals(rhs)) {IJ.write(
"shouldn't set it to itself");
return false;}
1076 type =
Roi.COMPOSITE;
1077 Rectangle rect = shape.getBounds();
1081 height = rect.height;
1090 private int min(
int[] array) {
1092 for (
int i=1; i<array.length; i++) val = Math.min(val,array[i]);
1097 private int max(
int[] array) {
1099 for (
int i=1; i<array.length; i++) val = Math.max(val,array[i]);