File: ShapeRoi.java

package info (click to toggle)
imagej 1.54g-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,520 kB
  • sloc: java: 132,209; sh: 286; xml: 255; makefile: 6
file content (1212 lines) | stat: -rw-r--r-- 53,123 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
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.filter.Analyzer;
import ij.util.Tools;
import ij.util.FloatArray;

/**A subclass of <code>ij.gui.Roi</code> (2D Regions Of Interest) implemented in terms of java.awt.Shape.
 * A ShapeRoi is constructed from a <code>ij.gui.Roi</code> 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
 * <code>java.awt.geom</code> as a backend. <br>
 * This code is in the public domain.
 * @author Cezar M.Tigaret <c.tigaret@ucl.ac.uk>
 */
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.
	*   The FLATNESS is an indication of the maximum deviation between the flattened and the original shape. */
	static final double FLATNESS = 0.1;

	/** Flatness used for filling the shape when creating a mask. Lower values result in higher accuracy
	 *  (determining which pixels near the border are filled), but lower speed when filling shapes with
	 *  curved borders. */
	static final double FILL_FLATNESS = 0.01;
	
	/**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 <strong><code>true</code></strong>.
	 */
	private static final int MAXPOLY = 10; // I hate arbitrary values !!!!

    private static final int OR=0, AND=1, XOR=2, NOT=3;

	/**The <code>java.awt.Shape</code> 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 <strong></code>true</code></strong> 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
	 * <code>MAXPOLY</code> and composed exclusively of line segments.
	 * If <strong><code>true</code></strong> 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 <strong><code>true</code></strong> 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; //not really used any more
	private static Stroke defaultStroke = new BasicStroke();


	/** 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 <strong><code>true</code></strong> 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} unless the average side length
	 * is large (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;
		shape = roiToShape((Roi)r.clone());
	}

	/** Constructs a ShapeRoi from an array of variable length path segments. Each
		segment consists of the segment type followed by 0-6 coordintes (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;
		else 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();
		else
			return makeShapeFromArray(getShapeAsArray(rhs, 0, 0));
	}

	/**********************************************************************************/
	/***                  Logical operations on shaped rois                        ****/
	/**********************************************************************************/

	/**Unary union operator.
	 * The caller is set to its union with the argument.
	 * @return the union of <strong><code>this</code></strong> and <code>sr</code>
	 */
	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 <strong><code>this</code></strong> and <code>sr</code>
	 */
	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 <code>this</code> and <code>sr</code>
	 * @see ij.gui.Roi#xor(Roi[])
	 * @see ij.gui.Overlay#xor(int[])
	 */
	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 <strong><code>this</code></strong> subtracted from <code>sr</code>
	 */
	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()));
		try {
			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;
			}
		} catch(Exception e) {}
		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;
		cachedMask = null;
		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:<br>
		<table><col><col><col><col><col><col><col>
			<thead>
				<tr><th scope=col> Roi class </th><th scope=col> Roi type </th><th scope=col> Shape </th><</tr>
			</thead>
			<tbody>
				<tr><td> ij.gui.Roi </td>       <td> Roi.RECTANGLE </td><td> java.awt.geom.Rectangle2D.Double </td></tr>
				<tr><td> ij.gui.OvalRoi </td>   <td> Roi.OVAL </td>     <td> java.awt.Polygon of the corresponding traced roi </td></tr>
				<tr><td> ij.gui.Line </td>      <td> Roi.LINE </td>     <td> java.awt.geom.Line2D.Double </td></tr>
				<tr><td> ij.gui.PolygonRoi </td><td> Roi.POLYGON </td>  <td> java.awt.Polygon or (if subpixel resolution) closed java.awt.geom.GeneralPath </td></tr>
				<tr><td> ij.gui.PolygonRoi </td><td> Roi.FREEROI </td>  <td> java.awt.Polygon or (if subpixel resolution) closed java.awt.geom.GeneralPath </td></tr>
				<tr><td> ij.gui.PolygonRoi </td><td> Roi.TRACED_ROI</td><td> java.awt.Polygon or (if subpixel resolution) closed java.awt.geom.GeneralPath </td></tr>
				<tr><td> ij.gui.PolygonRoi </td><td> Roi.POLYLINE </td> <td> open java.awt.geom.GeneralPath  </td></tr>
				<tr><td> ij.gui.PolygonRoi </td><td> Roi.FREELINE </td> <td> open java.awt.geom.GeneralPath  </td></tr>
				<tr><td> ij.gui.PolygonRoi </td><td> Roi.ANGLE </td>    <td> open java.awt.geom.GeneralPath  </td></tr>
				<tr><td> ij.gui.ShapeRoi </td>  <td> Roi.COMPOSITE </td><td> shape of argument  </td></tr>
				<tr><td> ij.gui.ShapeRoi </td>  <td> ShapeRoi.NO_TYPE</td><td> null </td></tr>
			</tbody>
		</table>
	 *
	 * @return A java.awt.geom.* object that inherits from java.awt.Shape interface.
	 *
	 */
	private Shape roiToShape(Roi roi) {
		if (roi.isLine())
			roi = Roi.convertLineToArea(roi);
		Shape shape = null;
		Rectangle r = roi.getBounds();
		boolean closeShape = true;
		int roiType = roi.getType();
		switch(roiType) {
			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:
				int arcSize = roi.getCornerDiameter();
				if (arcSize>0)
					shape = new RoundRectangle2D.Double(0, 0, r.width, r.height, arcSize, arcSize);
				else
					shape = new Rectangle2D.Double(0.0, 0.0, (double)r.width, (double)r.height);
				break;
			case Roi.POLYLINE: case Roi.FREELINE: case Roi.ANGLE:
				closeShape = false;
			case Roi.POLYGON: case Roi.FREEROI: case Roi.TRACED_ROI: case Roi.OVAL:
				if (roiType == Roi.OVAL) {
					//shape = new Ellipse2D.Double(-0.001, -0.001, r.width+0.002, r.height+0.002); //inaccurate (though better with increased diameter) 
					shape = ((OvalRoi)roi).getPolygon(false);
				} else if (closeShape && !roi.subPixelResolution()) {
					int nPoints =((PolygonRoi)roi).getNCoordinates();
					int[] xCoords = ((PolygonRoi)roi).getXCoordinates();
					int[] yCoords = ((PolygonRoi)roi).getYCoordinates();
					shape = new Polygon(xCoords, yCoords, nPoints);
				} else {
					FloatPolygon floatPoly = roi.getFloatPolygon();
					if (floatPoly.npoints <=1) break;
					shape = new GeneralPath(closeShape ? GeneralPath.WIND_EVEN_ODD : GeneralPath.WIND_NON_ZERO, floatPoly.npoints);
					((GeneralPath)shape).moveTo(floatPoly.xpoints[0] - r.x, floatPoly.ypoints[0] - r.y);
					for (int i=1; i<floatPoly.npoints; i++)
						((GeneralPath)shape).lineTo(floatPoly.xpoints[i] - r.x, floatPoly.ypoints[i] - r.y);
					if (closeShape)
						((GeneralPath)shape).closePath();
				}
				break;
			case Roi.POINT:
				ImageProcessor mask = roi.getMask();
				byte[] maskPixels = (byte[])mask.getPixels();
				int maskWidth = mask.getWidth();
				Area area = new Area();
				for (int y=0; y<mask.getHeight(); y++) {
					int yOffset = y*maskWidth;
					for (int x=0; x<maskWidth; x++) {
						if (maskPixels[x+yOffset]!=0)
							area.add(new Area(new Rectangle(x, y, 1, 1)));
					}
				}
				shape = area;
				break;
			case Roi.COMPOSITE: shape = ShapeRoi.cloneShape(((ShapeRoi)roi).getShape());
				break;
			default:
				throw new IllegalArgumentException("Roi type not supported");
		}

		if (shape!=null) {
			setLocation(r.x, r.y);
            Rectangle2D shapeBounds = shape.getBounds2D();
            Rectangle2D.Double sBounds = null;
            if (shapeBounds instanceof Rectangle2D.Double)
				sBounds = (Rectangle2D.Double)shapeBounds;
			else {
				sBounds = new Rectangle2D.Double();
				sBounds.setRect(shapeBounds);  //convert to Rectangle2D.Double
			}
			this.width  = (int)(Math.max(sBounds.x, 0) + sBounds.width + 0.5);
			this.height = (int)(Math.max(sBounds.y, 0) + sBounds.height+ 0.5);
			if (bounds != null) {
				bounds.width = width;
				bounds.height = height;
			}
			this.startX = x;
			this.startY = y;
			//IJ.log("RoiToShape: "+x+" "+y+" "+width+" "+height+" "+bounds);
		}
		return shape;
	}

	/** Constructs a GeneralPath from a float array of segment type+coordinates for each segment. 
	 *  The resulting GeneralPath has winding rule WIND_EVEN_ODD, which is appropriate for closed shapes */
	static GeneralPath makeShapeFromArray(float[] array) {
		if(array==null) return null;
		GeneralPath s = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
		int index=0;
		float[] seg = new float[7];
		while (true) {
			//if(index<array.length)IJ.log(index+" type="+array[index]);
			index = getSegment(array, seg, index);
			if (index<0) break;
			int type = (int)seg[0];
			switch(type) {
				case PathIterator.SEG_MOVETO:
					((GeneralPath)s).moveTo(seg[1], seg[2]);
					break;
				case PathIterator.SEG_LINETO:
					((GeneralPath)s).lineTo(seg[1], seg[2]);
					break;
				case PathIterator.SEG_QUADTO:
					((GeneralPath)s).quadTo(seg[1], seg[2],seg[3], seg[4]);
					break;
				case PathIterator.SEG_CUBICTO:
					((GeneralPath)s).curveTo(seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]);
					break;
				case PathIterator.SEG_CLOSE:
					((GeneralPath)s).closePath();
					break;
				default: break;
			}
		}
		return s;
	}

	/** Reads the data of a segment from an array describing the shape at as in a PathIterator. Reading starts at 'index'.
	 *  The position in the array for the next segment is returned, -1 if the array ends.
	 *  The segment type and coordinates of the segment are stored into 'seg' (which must have a length of at least 7) */
	private static int getSegment(float[] array, float[] seg, int index) {
		int len = array.length;
		if (index>=len) return -1;
		seg[0] = array[index++];
		int type = (int)seg[0];
		int nCoords = nCoords(type);
		if (index+nCoords > len) return -1;
		for (int i=1; i<=nCoords; i++)
			seg[i] = array[index++];
		return index;
	}

	/** Returns the number of coordinates for the given PathIterator segment type */
	private static int nCoords(int segmentType) {
		switch (segmentType) {
				case PathIterator.SEG_MOVETO:
				case PathIterator.SEG_LINETO:
					return 2;
				case PathIterator.SEG_QUADTO:
					return 4;
				case PathIterator.SEG_CUBICTO:
					return 6;
				case PathIterator.SEG_CLOSE:
					return 0;
				default:
					throw new RuntimeException("Invalid Segment Type: "+segmentType);
		}
	}

	/** Saves an Roi so it can be retrieved later using getRois(). Not compatible with the other functions of this class.
	 * @deprecated  Use ShapeRoi(Roi) creator and merge with <code>or(ShapeRoi)</code>. */
	void saveRoi(Roi roi) {
		if (savedRois==null)
			savedRois = new Vector();
		savedRois.addElement(roi);
	}

	/**Converts a Shape into Roi object(s).
	 * <br>This method parses the shape into (possibly more than one) Roi objects 
	 * and returns them in an array.
	 * <br>A simple, &quot;regular&quot; path results in a single Roi following these simple rules:
		<table><col><col><col>
			<thead><tr><th scope=col> Shape type </th><th scope=col> Roi class </th><th scope=col> Roi type </th></tr></thead>
			<tbody>
				<tr><td> java.awt.geom.Rectangle2D.Double </td><td> ij.gui.Roi </td><td> Roi.RECTANGLE </td></tr>
				<tr><td> java.awt.geom.Ellipse2D.Double </td><td> ij.gui.OvalRoi</td><td> Roi.OVAL </td></tr>
				<tr><td> java.awt.geom.Line2D.Double </td><td> ij.gui.Line </td><td> Roi.LINE </td></tr>
				<tr><td> java.awt.Polygon </td>	<td> ij.gui.PolygonRoi </td><td> Roi.POLYGON </td></tr>
			</tbody>
		</table>
	 * <br><br>Each subpath of a <code>java.awt.geom.GeneralPath</code> is converted following these rules:
		<table frame="border"><col><col><col><col><col><col>
			<thead>
				<tr><th rowspan="2" scope=col> Segment<br> types </th><th rowspan="2" scope=col> Number of<br> segments </th>
					<th rowspan="2" scope=col> Closed<br> path </th><th rowspan="2" scope=col> Value of<br> forceAngle </th>
					<th rowspan="2" scope=col> Value of<br> forceTrace </th><th rowspan="2" scope=col> Roi type </th></tr>
			</thead>
			<tbody>
				<tr><td> lines only: </td><td align="center"> 0 </td><td>  </td><td>  </td><td>  </td><td> ShapeRoi.NO_TYPE </td></tr>
				<tr><td>  </td><td align="center"> 1 </td><td>  </td><td>  </td><td>  </td>	<td> ShapeRoi.NO_TYPE </td></tr>
				<tr><td>  </td><td align="center"> 2 </td><td align="center"> Y </td><td>  </td><td>  </td><td> ShapeRoi.NO_TYPE </td></tr>
				<tr><td>  </td><td>  </td><td align="center"> N </td><td>  </td><td>  </td><td> Roi.LINE </td></tr>
				<tr><td>  </td><td align="center"> 3 </td><td align="center"> Y </td><td align="center"> N </td><td>  </td><td> Roi.POLYGON </td></tr>
				<tr><td>  </td><td>  </td><td align="center"> N </td><td align="center"> Y </td><td>  </td><td> Roi.ANGLE </td></tr>
				<tr><td>  </td><td>  </td><td align="center"> N </td><td align="center"> N </td><td>  </td><td> Roi.POLYLINE </td></tr>
				<tr><td>  </td><td align="center"> 4 </td><td align="center"> Y </td><td>  </td<td>  </td><td> Roi.RECTANGLE </td></tr>
				<tr><td>  </td><td>  </td><td align="center"> N </td><td>  </td><td>  </td><td> Roi.POLYLINE </td></tr>
				<tr><td>  </td><td align="center"> &lt;= MAXPOLY </td>	<td align="center"> Y </td><td>  </td><td>  </td><td> Roi.POLYGON </td></tr>
				<tr><td>  </td><td>  </td><td align="center"> N </td><td>  </td><td>  </td><td> Roi.POLYLINE </td></tr>
				<tr><td>  </td><td align="center"> &gt; MAXPOLY </td><td align="center"> Y </td><td>  </td>	<td align="center"> Y </td><td> Roi.TRACED_ROI </td></tr>
				<tr><td>  </td><td>  </td><td>  </td><td>  </td><td align="center"> N </td><td> Roi.FREEROI </td></tr>
				<tr><td>  </td><td>  </td><td align="center"> N </td><td>  </td><td>  </td><td> Roi.FREELINE </td></tr>
				<tr><td> anything<br>else: </td><td align="center"> &lt;= 2 </td><td>  </td><td>  </td><td>  </td><td> ShapeRoi.NO_TYPE </td></tr>
				<tr><td>  </td><td align="center"> &gt; 2 </td><td>  </td><td>  </td><td>  </td><td> ShapeRoi.SHAPE_ROI </td></tr>
			</tbody>
		</table>
	 * @return an array of ij.gui.Roi objects.
	 */
	public Roi[] getRois () {
		if (shape==null)
			return new Roi[0];
		if (savedRois!=null)
			return (Roi[])savedRois.toArray(new Roi[savedRois.size()]);
		ArrayList rois = new ArrayList();
		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.add(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.add(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.add(r);
		} else if (shape instanceof Polygon) {
			Roi r = new PolygonRoi(((Polygon)shape).xpoints, ((Polygon)shape).ypoints, ((Polygon)shape).npoints, Roi.POLYGON);
			rois.add(r);
		} else {
			PathIterator pIter;
			if (flatten)
				pIter = getFlatteningPathIterator(shape,flatness);
			else
				pIter = shape.getPathIterator(new AffineTransform());
			parsePath(pIter, ALL_ROIS, rois);
		}
		return (Roi[])rois.toArray(new Roi[rois.size()]);
	}


	/**Attempts to convert this ShapeRoi into a single non-composite Roi.
	 * @return an ij.gui.Roi object or null if it cannot be simplified to become a non-composite roi.
	 */
	public Roi shapeToRoi() {
		if (shape==null || !(shape instanceof GeneralPath))
			return null;
		PathIterator pIter = shape.getPathIterator(new AffineTransform());
		ArrayList rois = new ArrayList();
		parsePath(pIter, ONE_ROI, rois);
		if (rois.size() == 1)
			return (Roi)rois.get(0);
		else
			return null;
	}

	/**Attempts to convert this ShapeRoi into a single non-composite Roi.
	 * For showing as a Roi, one should apply copyAttributes
	 * @return an ij.gui.Roi object, which is either the non-composite roi,
	 * or this ShapeRoi (if such a conversion is not possible) or null if
	 * this is an empty roi.
	 */
	public Roi trySimplify() {
		Roi roi = shapeToRoi();
		return (roi==null) ? this : roi;
	}
	
	/**Implements the rules of conversion from <code>java.awt.geom.GeneralPath</code> to <code>ij.gui.Roi</code>.
	 * @param nSegments The number of segments that compose the path (= number of vertices for a polygon)
	 * @param polygonLength length of polygon in pixels, or NaN if curved segments
	 * @param horizontalVerticalIntOnly Indicates whether the GeneralPath is composed of only vertical and horizontal lines with integer coordinates
	 * @param forceTrace Indicates that closed shapes with <code>horizontalVerticalIntOnly=true</code> should become TRACED_ROIs
	 * @param closed Indicates a closed GeneralPath
	 * @see #shapeToRois()
	 * @return a type flag like Roi.RECTANGLE or NO_TYPE if the type cannot be determined
	 */
	private int guessType(int nSegments, double polygonLength, boolean horizontalVerticalIntOnly, boolean forceTrace, boolean closed) {
		int roiType = Roi.RECTANGLE;
		if (Double.isNaN(polygonLength)) {
			roiType = Roi.COMPOSITE;
		} else {
			// For more segments, they should be longer to qualify for a polygon with handles:
			// The threshold for the average segment length is 4.0 for 4 segments, 16.0 for 64 segments, 32.0 for 256 segments
			boolean longEdges = polygonLength/(nSegments*Math.sqrt(nSegments)) >= 2;
			if (nSegments < 2)
				roiType = NO_TYPE;
			else if (nSegments == 2)
				roiType = closed ? NO_TYPE : Roi.LINE;
			else if (nSegments == 3 && !closed && forceAngle)
				roiType = Roi.ANGLE;
			else if (nSegments == 4 && closed && horizontalVerticalIntOnly && longEdges && !forceTrace && !this.forceTrace)
				roiType = Roi.RECTANGLE;
			else if (closed && horizontalVerticalIntOnly && (!longEdges || forceTrace || this.forceTrace))
				roiType = Roi.TRACED_ROI;
			else if (nSegments <= MAXPOLY || longEdges)
				roiType = closed ? Roi.POLYGON : Roi.POLYLINE;
			else
				roiType = closed ? Roi.FREEROI : Roi.FREELINE;
		}
		//IJ.log("guessType n= "+nSegments+" len="+polygonLength+" longE="+(polygonLength/(nSegments*Math.sqrt(nSegments)) >= 2)+" hvert="+horizontalVerticalIntOnly+" clos="+closed+" -> "+roiType);
		return roiType;
	}

	/**Creates a 'classical' (non-Shape) Roi object based on the arguments.
	 * @see #shapeToRois()
	 * @param xPoints the x coordinates
	 * @param yPoints the y coordinates
	 * @param type the type flag
	 * @return a ij.gui.Roi object or null
	 */
	private Roi createRoi(float[] xPoints, float[] yPoints, int roiType) {
		if (roiType == NO_TYPE || roiType == Roi.COMPOSITE) return null;
		Roi roi = null;
		if (xPoints == null || yPoints == null || xPoints.length != yPoints.length || xPoints.length==0) return null;

		Tools.addToArray(xPoints, (float)getXBase());
		Tools.addToArray(yPoints, (float)getYBase());

		switch(roiType) {
			case Roi.LINE: roi = new ij.gui.Line(xPoints[0],yPoints[0],xPoints[1],yPoints[1]); break;
			case Roi.RECTANGLE:
				double[] xMinMax = Tools.getMinMax(xPoints);
				double[] yMinMax = Tools.getMinMax(yPoints);
				roi = new Roi((int)xMinMax[0], (int)yMinMax[0],
						(int)xMinMax[1] - (int)xMinMax[0], (int)yMinMax[1] - (int)yMinMax[0]);
				break;
			case TRACED_ROI:
				roi = new PolygonRoi(toIntR(xPoints), toIntR(yPoints), xPoints.length, roiType);
				break;
			default:
				roi = new PolygonRoi(xPoints, yPoints, xPoints.length, roiType);
				break;
		}
		return roi;
	}

	/**********************************************************************************/
	/***                                   Geometry                                ****/
	/**********************************************************************************/

	/** Checks whether the center of the specified pixel inside of this ROI's shape boundaries.
	 *  Note the ImageJ convention of 0.5 pixel shift between outline and pixel center,
	 *  i.e., pixel (0,0) is enclosed by the rectangle spanned between (0,0) and (1,1).
	 *  The value slightly below 0.5 is for rounding according to the ImageJ convention
	 *  (which is opposite to that of the java.awt.Shape class):
	 *  In ImageJ, points exactly at the left (right) border are considered outside (inside);
	 *  points exactly on horizontal borders, are considered outside (inside) at the border
	 *  with the lower (higher) y.
	 */
	public boolean contains(int x, int y) {
		if (shape==null) return false;
		return shape.contains(x-this.x+0.494, y-this.y+0.49994);
	}

	/** Returns whether coordinate (x,y) is contained in the Roi.
	 *  Note that the coordinate (0,0) is the top-left corner of pixel (0,0).
	 *  Use contains(int, int) to determine whether a given pixel is contained in the Roi. */
	public boolean containsPoint(double x, double y) {
		if (!super.containsPoint(x, y))
			return false;
		return shape.contains(x-this.x+1e-3, y-this.y+1e-6); //adding a bit to reduce the likelyhood of numerical errors at integers
	}

	/** Returns the perimeter of this ShapeRoi. */
	public double getLength() {
		if (width==0 && height==0)
			return 0.0;
		return parsePath(shape.getPathIterator(new AffineTransform()), GET_LENGTH, null);
	}

	/** Returns a path iterator for this ROI's shape containing no curved (only straight) segments */
	PathIterator getFlatteningPathIterator(Shape s, double fl) {
		return s.getPathIterator(new AffineTransform(),fl);
	}

	/**Length of the control polygon of the cubic B&eacute;zier curve argument, in double precision.*/
	double cplength(CubicCurve2D.Double c) {
		return 	Math.sqrt(sqr(c.ctrlx1-c.x1) + sqr(c.ctrly1-c.y1)) +
				Math.sqrt(sqr(c.ctrlx2-c.ctrlx1) + sqr(c.ctrly2-c.ctrly1)) +
				Math.sqrt(sqr(c.x2-c.ctrlx2) + sqr(c.y2-c.ctrly2));
	}

	/**Length of the control polygon of the quadratic B&eacute;zier curve argument, in double precision.*/
	double qplength(QuadCurve2D.Double c) {
		return  Math.sqrt(sqr(c.ctrlx-c.x1) + sqr(c.ctrly-c.y1)) +
				Math.sqrt(sqr(c.x2-c.ctrlx) + sqr(c.y2-c.ctrly));
	}

	/**Length of the chord between the end points of the cubic B&eacute;zier curve argument, in double precision.*/
	double cclength(CubicCurve2D.Double c) {
		return Math.sqrt(sqr(c.x2-c.x1) + sqr(c.y2-c.y1));
	}

	/**Length of the chord between the end points of the quadratic B&eacute;zier curve argument, in double precision.*/
	double qclength(QuadCurve2D.Double c) {
		return Math.sqrt(sqr(c.x2-c.x1) + sqr(c.y2-c.y1));
	}

	/**Calculates the length of a cubic B&eacute;zier curve specified in double precision.
	 * The algorithm is based on the theory presented in paper <br>
	 * &quot;Jens Gravesen. Adaptive subdivision and the length and energy of B&eacute;zier curves. Computational Geometry <strong>8:</strong><em>13-31</em> (1997)&quot;
	 * implemented using <code>java.awt.geom.CubicCurve2D.Double</code>.
	 * Please visit {@link <a href="http://www.graphicsgems.org/gems.html#gemsiv">Graphics Gems IV</a>} 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&eacute;zier curve specified in double precision.
	 * The algorithm is based on the theory presented in paper <br>
	 * &quot;Jens Gravesen. Adaptive subdivision and the length and energy of B&eacute;zier curves. Computational Geometry <strong>8:</strong><em>13-31</em> (1997)&quot;
	 * implemented using <code>java.awt.geom.CubicCurve2D.Double</code>.
	 * Please visit {@link <a href="http://www.graphicsgems.org/gems.html#gemsiv">Graphics Gems IV</a>} 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&eacute;zier curve in half.
  * @param c A cubic B&eacute;zier curve to be divided
  * @return an array with the left and right cubic B&eacute;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&eacute;zier curve in half.
  * @param c A quadratic B&eacute;zier curve to be divided
  * @return an array with the left and right quadratic B&eacute;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 <strong>fixed</strong> structure:<br>
	 * <code>x0,y0,x1,y1,....,xn,yn</code> and with even length of <code>2*(n+1)</code>.
	 * @param pw The x-scale of the image.
	 * @param ph The y-scale of the image.
	 * @param n  number of values in <code>c</code> that should be modified (must be less or equal to the size of <code>c</code>
	 *
	 */
	void scaleCoords(double[] c, int n, double pw, double ph) {
		for(int i=0; i<n;) {
			c[i++]*=pw;
			c[i++]*=ph;
		}
	}

	/** Applies an offset xBase, yBase to the n coordinates, which are supposed to be in x, y, x, y ... sequence */
	static void addOffset(float[] c, int n, float xBase, float yBase) {
		for(int i=0; i<n;) {
			c[i++] += xBase;
			c[i++] += yBase;
		}
	}

	/** Retrieves the end points and control points of the path as a float array. The array 
	 *  contains a sequence of variable length segments that use from from one to seven array elements.
	 *  The first element of a segment is the type as defined in the PathIterator interface. SEG_MOVETO 
	 *  and SEG_LINETO segments also include two coordinates (one end point), SEG_QUADTO segments include four 
	 *  coordinates and SEG_CUBICTO segments include six coordinates (three points).
	 *  Coordinates are with respect to the image bounds, not the Roi bounds. */
	public float[] getShapeAsArray() {
		return getShapeAsArray(shape, (float)getXBase(), (float)getYBase());
	}

	/** Converts a java.awt.Shape to an array of segment types and coordinates.
	 *  'xBase' and 'yBase' are added to the x and y coordinates, respectively. */
	static float[] getShapeAsArray(Shape shape, float xBase, float yBase) {
		if (shape==null) return null;
		PathIterator pIt = shape.getPathIterator(new AffineTransform());
		FloatArray shapeArray = new FloatArray();
		float[] coords = new float[6];
		while (!pIt.isDone()) {
			int segType = pIt.currentSegment(coords);
			shapeArray.add(segType);
			int nCoords = nCoords(segType);
			if (nCoords > 0) {
				addOffset(coords, nCoords, xBase, yBase);
				shapeArray.add(coords, nCoords);
			}
			pIt.next();
		}
		return shapeArray.toArray();
	}

	final static int ALL_ROIS=0, ONE_ROI=1, GET_LENGTH=2; //task types
	final static int NO_SEGMENT_ANY_MORE = -1; //pseudo segment type when closed
	/**Parses the geometry of this ROI's shape by means of the shape's PathIterator;
	 * Depending on the <code>task</code> and <code>rois</code> argument it will:
	 * <br>- create a single non-Shape Roi and add it to <code>rois</code> in case
	 *       there is only one subpath, otherwise add this Roi unchanged to <code>rois</code>
	 *       (task = ONE_ROI and rois non-null)
	 * <br>- add each subpath as a Roi to rois; curved subpaths will be flattened, i.e. converted to a
	 *       polygon approximation (task != ONE_ROI and rois non-null)
	 * <br>- measure the combined length of all subpaths/Rois and return it (task = GET_LENGTH, rois may be null)
	 * @param pIter the PathIterator to be parsed.
	 * @param params an array with one element that will hold the calculated total length of the rois if its initial value is 0.
	 *        If params holds the value SHAPE_TO_ROI, it will be tried to convert this ShapeRoi to a non-composite Roi. If this
	 *        is not possible and this ShapeRoi is not empty, a reference to this ShapeRoi will be returned.
	 * @param rois an ArrayList that will hold ij.gui.Roi objects constructed from subpaths of this path;
	 *        may be null only when <code>task = GET_LENGTH</code>
	 * (see @link #shapeToRois()} for details;
	 * @return Total length if task = GET_LENGTH.*/
	double parsePath(PathIterator pIter, int task, ArrayList rois) {
		if (pIter==null || pIter.isDone())
			return 0.0;
		double pw = 1.0, ph = 1.0;
		if (imp!=null) {
			Calibration cal = imp.getCalibration();
			pw = cal.pixelWidth;
			ph = cal.pixelHeight;
		}
		float xbase = (float)getXBase();
		float ybase = (float)getYBase();

		FloatArray xPoints = new FloatArray();    //vertex coordinates of current subpath
		FloatArray yPoints = new FloatArray();
		FloatArray shapeArray = new FloatArray(); //values for creating a GeneralPath for the current subpath
		boolean getLength = task == GET_LENGTH;
		int nSubPaths = 0;           // the number of subpaths
		boolean horVertOnly = true; // subpath has only horizontal or vertical lines
		boolean closed = false;
		//boolean success = false;
		float[]  fcoords = new float[6];  // unscaled float coordinates of the path segment
		double[] coords  = new double[6]; // scaled (calibrated) coordinates of the path segment
		double startCalX = 0.0;   // start x of subpath (scaled)
		double startCalY = 0.0;   // start y of subpath (scaled)
		double lastCalX = 0.0;    // x of previous point in the subpath (scaled)
		double lastCalY = 0.0;    // y of previous point in the subpath (scaled)
		double pathLength = 0.0;  // calibrated pathLength/perimeter of current curve
		double totalLength = 0.0; // sum of all calibrated path lengths/perimeters
		double uncalLength = 0.0; // uncalibrated length of polygon, NaN in case of curves
		boolean done = false;
		while (true) {
			int segType = done ? NO_SEGMENT_ANY_MORE : pIter.currentSegment(fcoords);  //read segment (if there is one more)
			int nCoords = 0;               //will be number of coordinates supplied with the segment
			if (!done) {
				nCoords = nCoords(segType);
				if (getLength) {           //make scaled coodinates to calculate the length
					pIter.currentSegment(coords);
					scaleCoords(coords, nCoords, pw, ph);
				}
				pIter.next();
				done = pIter.isDone();
			}

			//IJ.log("segType="+segType+" nCoord="+nCoords+" done="+done+" nPoi="+nPoints+" len="+pathLength);
			if (segType == NO_SEGMENT_ANY_MORE || (segType == PathIterator.SEG_MOVETO && xPoints.size()>0)) {
				// subpath finished: analyze it & create roi if appropriate
				closed = closed || (xPoints.size()>0 && xPoints.get(0) == xPoints.getLast() && yPoints.get(0) == yPoints.getLast());
				float[] xpf = xPoints.toArray();
				float[] ypf = yPoints.toArray();
				if (Double.isNaN(uncalLength) || !allInteger(xpf) || !allInteger(ypf))
					horVertOnly = false;         //allow conversion to rectangle or traced roi only for integer coordinates
				boolean forceTrace = getLength && (!done || nSubPaths>0);  //when calculating the length for >1 subpath, assume traced rois if it can be such
				int roiType = guessType(xPoints.size(), uncalLength, horVertOnly, forceTrace, closed);
				Roi roi = null;
				if (roiType == COMPOSITE && rois != null) { //for ShapeRois with curves, we have the length from the path already, make roi only if needed
					Shape shape = makeShapeFromArray(shapeArray.toArray());  //the curved subpath (image pixel coordinates)
					FloatPolygon fp = getFloatPolygon(shape, FLATNESS, /*separateSubpaths=*/ false, /*addPointForClose=*/ false, /*absoluteCoord=*/ false);
					roi = new PolygonRoi(fp, FREEROI);
				} else if (roiType != NO_TYPE) { //NO_TYPE looks like an empty roi; only return non-empty rois
					roi = createRoi(xpf, ypf, roiType);
				}
				if (rois != null && roi != null)
					rois.add(roi);
				if (task == ONE_ROI) {
					if (rois.size() > 1) {       //we can't make a single roi from this; so we can only keep the roi as it is
						rois.clear();
						rois.add(this);
						return 0.0;
					}
				}
				if (getLength && roi != null && !Double.isNaN(uncalLength)) {
					roi.setImage(imp);           //calibration
					pathLength = roi.getLength();//we don't use the path length of the Shape; e.g. for traced rois ImageJ has a better algorithm
					roi.setImage(null);
				}
				totalLength += pathLength;
			}
			if (segType == NO_SEGMENT_ANY_MORE)  // b r e a k   t h e   l o o p
				return getLength ? totalLength : 0;

			closed = false;
			switch(segType) {
				case PathIterator.SEG_MOVETO:    //we start a new subpath
					xPoints.clear();
					yPoints.clear();
					shapeArray.clear();
					nSubPaths++;
					pathLength = 0;
					startCalX = coords[0];  
					startCalY = coords[1];
					closed = false;
					horVertOnly = true;
					break;
				case PathIterator.SEG_LINETO:
					pathLength += Math.sqrt(sqr(lastCalY-coords[1])+sqr(lastCalX-coords[0]));
					break;
				case PathIterator.SEG_QUADTO:
					if (getLength) {
						QuadCurve2D.Double curve = new QuadCurve2D.Double(lastCalX,lastCalY,coords[0],coords[2],coords[2],coords[3]);
						pathLength += qBezLength(curve);
					}
					uncalLength = Double.NaN;    // not a polygon
					break;
				case PathIterator.SEG_CUBICTO:
					if (getLength) {
						CubicCurve2D.Double curve = new CubicCurve2D.Double(lastCalX,lastCalY,coords[0],coords[1],coords[2],coords[3],coords[4],coords[5]);
						pathLength += cBezLength(curve);
					}
					uncalLength = Double.NaN;    // not a polygon
					break;
				case PathIterator.SEG_CLOSE:
					pathLength += Math.sqrt(sqr(lastCalX-startCalX) + sqr(lastCalY-startCalY));
					fcoords[0] = xPoints.get(0); //destination coordinates; with these we can handle it as SEG_LINETO
					fcoords[1] = yPoints.get(0);
					closed = true;
					break;
				default:
					break;
			}
			if (xPoints.size()>0 && (segType == PathIterator.SEG_LINETO || segType == PathIterator.SEG_CLOSE)) {
				float dx = fcoords[0] - xPoints.getLast();
				float dy = fcoords[1] - yPoints.getLast();
				uncalLength += Math.sqrt(sqr(dx) + sqr(dy));
				if (dx != 0f && dy != 0f) horVertOnly = false;
			}

			if (nCoords > 0) {
				xPoints.add(fcoords[nCoords - 2]); // the last coordinates are the end point of the segment
				yPoints.add(fcoords[nCoords - 1]);
				lastCalX = coords[nCoords - 2];
				lastCalY = coords[nCoords - 1];
			}
			shapeArray.add(segType);
			addOffset(fcoords, nCoords, xbase, ybase); // shift the shape to image origin
			shapeArray.add(fcoords, nCoords(segType));
		}
	}

	/** Non-destructively draws the shape of this object on the associated ImagePlus. */
	public void draw(Graphics g) {
		Color color =  strokeColor!=null? strokeColor:ROIColor;
		boolean isActiveOverlayRoi = !overlay && isActiveOverlayRoi();
		//IJ.log("draw: "+overlay+"  "+isActiveOverlayRoi);
		if (isActiveOverlayRoi) {
			if (color==Color.cyan)
				color = Color.magenta;
			else
				color = Color.cyan;
		}
		if (fillColor!=null) color = fillColor;
		g.setColor(color);
		AffineTransform aTx = (((Graphics2D)g).getDeviceConfiguration()).getDefaultTransform();
		Graphics2D g2d = (Graphics2D)g;
		if (stroke!=null && !isActiveOverlayRoi)
			g2d.setStroke((ic!=null&&ic.getCustomRoi())||isCursor()?stroke:getScaledStroke());
		mag = getMagnification();
		int basex=0, basey=0;
		if (ic!=null) {
			Rectangle r = ic.getSrcRect();
			basex=r.x; basey=r.y;
		}
		aTx.setTransform(mag, 0.0, 0.0, mag, -basex*mag, -basey*mag);
		aTx.translate(getXBase(), getYBase());
		if (fillColor!=null) {
			if (isActiveOverlayRoi) {
				g2d.setColor(Color.cyan);
				g2d.draw(aTx.createTransformedShape(shape));
			} else {
				g2d.fill(aTx.createTransformedShape(shape));
				if (strokeColor!=null) {
					g.setColor(strokeColor);
					g2d.draw(aTx.createTransformedShape(shape));
				}
			}
		} else
			g2d.draw(aTx.createTransformedShape(shape));
		if (stroke!=null) g2d.setStroke(defaultStroke);
		if (Toolbar.getToolId()==Toolbar.OVAL)
			drawRoiBrush(g);
		if (state!=NORMAL && imp!=null && imp.getRoi()!=null)
			showStatus();
		if (updateFullWindow) 
			{updateFullWindow = false; imp.draw();}
	}

	public void drawRoiBrush(Graphics g) {
		g.setColor(ROIColor);
		int size = Toolbar.getBrushSize();
		if (size==0 || ic==null)
			return;
		int flags = ic.getModifiers();
		if ((flags&16)==0) return; // exit if mouse button up
		int osize = size;
		size = (int)(size*mag);
		Point p = ic.getCursorLoc();
		int sx = ic.screenX(p.x);
		int sy = ic.screenY(p.y);
		int offset = (int)Math.round(ic.getMagnification()/2.0);
		if ((osize&1)==0)
			offset=0; // not needed when brush width even
		g.drawOval(sx-size/2+offset, sy-size/2+offset, size, size);
	}
	
	/**Draws the shape of this object onto the specified ImageProcessor.
	 * <br> 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 = shape.getPathIterator(new AffineTransform(), 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).
	 *  Takes into account the usual ImageJ convention of 0.5 pxl shift between the outline and pixel
	 *  coordinates; e.g., pixel (0,0) is surrounded by the rectangle spanned between (0,0) and (1,1).
	 *  Note that apart from the 0.5 pixel shift, ImageJ has different convention for the border points
	 *  than the java.awt.Shape class:
	 *  In ImageJ, points exactly at the left (right) border are considered outside (inside);
	 *  points exactly on horizontal borders, are considered outside (inside) at the border
	 *  with the lower (higher) y.
	 *  */
	public ImageProcessor getMask() {
		if (shape==null)
			return null;
		ImageProcessor mask = cachedMask;
		if (mask!=null && mask.getPixels()!=null && mask.getWidth()==width && mask.getHeight()==height)
			return mask;
		/* The following code using Graphics2D.fill would in principle work, but is very inaccurate
		 * at least with Oracle Java 8 or OpenJDK Java 10.
		 * For near-vertical polgon edges of 1000 pixels length, the deviation can be >0.8 pixels in x.
		 * Thus, approximating the shape by a polygon and using the PolygonFiller is more accurate
		 * (and roughly equally fast). --ms Jan 2018 */
		/*BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
		Graphics2D g2d = bi.createGraphics();
		g2d.setColor(Color.white);
		g2d.transform(AffineTransform.getTranslateInstance(-0.48, -0.49994)); //very inaccurate, only reasonable with "-0.48"
		g2d.fill(shape);
		Raster raster = bi.getRaster();
		DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();		
		byte[] mask = buffer.getData();
		cachedMask = new ByteProcessor(width, height, mask, null);
		cachedMask.setThreshold(255,255,ImageProcessor.NO_LUT_UPDATE);*/
		FloatPolygon fpoly = getFloatPolygon(FILL_FLATNESS, true, false, false);
		PolygonFiller pf = new PolygonFiller(fpoly.xpoints, fpoly.ypoints, fpoly.npoints, (float)(getXBase()-x), (float)(getYBase()-y));
		mask = pf.getMask(width, height);
		cachedMask = mask;
        return mask;
	}

	/**Returns a reference to the Shape object encapsulated by this ShapeRoi. */
	public Shape getShape() {
		return shape;
	}

	/**Sets the <code>java.awt.Shape</code> object encapsulated by <strong><code>this</code></strong>
	 * to the argument.
	 * <br>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 <code>setShape(ShapeRoi.cloneShape(shape))</code>.
	 * @return <strong><code>false</code></strong> 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;
	}

	/**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<array.length; i++) val = Math.min(val,array[i]);
		return val;
	}

	/**Returns the element with the largest value in the array argument.*/
	private int max(int[] array) {
		int val = array[0];
		for (int i=1; i<array.length; i++) val = Math.max(val,array[i]);
		return val;
	}
	
	static ShapeRoi getCircularRoi(int x, int y, int width) {
		return new ShapeRoi(new OvalRoi(x - width / 2, y - width / 2, width, width));
	}

	/** Always returns -1 since ShapeRois do not have handles. */
	public int isHandle(int sx, int sy) {
		   return -1;
	}

	/** Used by the getSelectionCoordinates macro function */
	public FloatPolygon getSelectionCoordinates() {
		return getFloatPolygon(FLATNESS, true, false, true);
	}

	/** Returns a FloatPolygon with all vertices of the flattened shape, i.e., the shape approximated by
	 *  straight line segments. This method is for listing the coordinates and creating the convex hull.
	 *  @param flatness Roughly the maximum allowable distance between the shape and the approximate polygon
	 *  @param separateSubpaths whether individual subpaths should be separated by NaN coordinates
	 *  @param addPointForClose whether the starting point of a closed subpath should be repeated at its end.
	 *   Note that with <code>addPointForClose = false</code>, there is no distinction between open and closed subpaths.
	 *  @param absoluteCoord specifies whether the coordinates should be with respect to image bounds, not Roi bounds. */
	public FloatPolygon getFloatPolygon(double flatness, boolean separateSubpaths, boolean addPointForClose, boolean absoluteCoord) {
		return getFloatPolygon(shape, flatness, separateSubpaths, addPointForClose, absoluteCoord);
	}

	public FloatPolygon getFloatPolygon(Shape shape, double flatness, boolean separateSubpaths, boolean addPointForClose, boolean absoluteCoord) {
		if (shape == null) return null;
		PathIterator pIter = getFlatteningPathIterator(shape, flatness);
		FloatArray xp = new FloatArray();
		FloatArray yp = new FloatArray();
		float[] coords = new float[6];
		int subPathStart = 0;
		while (!pIter.isDone()) {
			int segType = pIter.currentSegment(coords);
			switch(segType) {
				case PathIterator.SEG_MOVETO:
					if (separateSubpaths && xp.size()>0 && !Float.isNaN(xp.get(xp.size()-1))) {
						xp.add(Float.NaN);
						yp.add(Float.NaN);
					}
					subPathStart = xp.size();
				case PathIterator.SEG_LINETO:
					xp.add(coords[0]);
					yp.add(coords[1]);
					break;
				case PathIterator.SEG_CLOSE:
					boolean isClosed = xp.getLast() == xp.get(subPathStart) && yp.getLast() == yp.get(subPathStart);
					if (addPointForClose && !isClosed) {
						xp.add(xp.get(subPathStart));
						yp.add(yp.get(subPathStart));
					} else if (isClosed) {
						xp.removeLast(1);  //remove duplicate point if we should not add point to close the shape
						yp.removeLast(1);
					}
					if (separateSubpaths && xp.size()>0 && !Float.isNaN(xp.get(xp.size()-1))) {
						xp.add(Float.NaN);
						yp.add(Float.NaN);
					}
					break;
				default:
					throw new RuntimeException("Invalid Segment Type: "+segType);
			}
			pIter.next();
		}
		float[] xpf = xp.toArray();
		float[] ypf = yp.toArray();
		if (absoluteCoord) {
			Tools.addToArray(xpf, (float)getXBase());
			Tools.addToArray(ypf, (float)getYBase());
		}
		int n = xpf.length;
		if (n>0 && Float.isNaN(xpf[n-1])) n--; //omit NaN at the end
		return new FloatPolygon(xpf, ypf, n);
	}

	public FloatPolygon getFloatConvexHull() {
		FloatPolygon fp = getFloatPolygon(FLATNESS, /*separateSubpaths=*/ false, /*addPointForClose=*/ false, /*absoluteCoord=*/ true);
		return fp == null ? null : fp.getConvexHull();
	}
	
	public Polygon getPolygon() {
		FloatPolygon fp = getFloatPolygon();
		return new Polygon(toIntR(fp.xpoints), toIntR(fp.ypoints), fp.npoints);
	}

	/** Returns all vertex points of the shape as approximated by polygons,
	 *  in image pixel coordinates */
	public FloatPolygon getFloatPolygon() {
		return getFloatPolygon(FLATNESS, /*separateSubpaths=*/ false, /*addPointForClose=*/ false, /*absoluteCoord=*/ true);
	}

	/** Returns all vertex points of the shape as approximated by polygons,
	 *  where options may include "close" to add points to close each subpath, and
	 *  "separate" to insert NaN values between subpaths (= individual polygons) */
	public FloatPolygon getFloatPolygon(String options) {
		options = options.toLowerCase();
		boolean separateSubpaths = options.indexOf("separate") >= 0;
		boolean addPointForClose = options.indexOf("close") >= 0;
		return getFloatPolygon(FLATNESS, separateSubpaths, addPointForClose, /*absoluteCoord=*/ true);
	}

	/** Retuns the number of vertices, of this shape as approximated by straight lines.
	 *  Note that points might be counted twice where the shape gets closed. */
	public int size() {
		return getPolygon().npoints;
	}

	boolean allInteger(float[] a) {
		for (int i=0; i<a.length; i++)
			if (a[i] != (int)a[i]) return false;
		return true;
	}
}