preparing release pom-trakem2-2.0.0, VectorString-2.0.0, TrakEM2_-1.0h
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Profile.java
blobb99069b5786b91913625f0875a64e0bfddf1aa82
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.display;
25 import java.awt.AlphaComposite;
26 import java.awt.Color;
27 import java.awt.Composite;
28 import java.awt.Graphics2D;
29 import java.awt.Polygon;
30 import java.awt.Rectangle;
31 import java.awt.event.KeyEvent;
32 import java.awt.event.MouseEvent;
33 import java.awt.geom.AffineTransform;
34 import java.awt.geom.Area;
35 import java.awt.geom.NoninvertibleTransformException;
36 import java.awt.geom.Point2D;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Map;
44 import org.scijava.vecmath.Point3f;
46 import ij.measure.Calibration;
47 import ij.measure.ResultsTable;
48 import ini.trakem2.Project;
49 import ini.trakem2.persistence.XMLOptions;
50 import ini.trakem2.tree.ProjectThing;
51 import ini.trakem2.utils.IJError;
52 import ini.trakem2.utils.M;
53 import ini.trakem2.utils.ProjectToolbar;
54 import ini.trakem2.utils.Utils;
55 import ini.trakem2.vector.SkinMaker;
56 import ini.trakem2.vector.VectorString2D;
59 /** A class to be a user-outlined profile over an image, which is painted with a particular color and also holds an associated text label.
60 * TODO - label not implemented yet.
61 * - can't paint segments in different colors yet
62 * - the whole curve is updated when only a particular set of points needs readjustment
63 * - also, points are smooth, and options should be given to make them non-smooth.
65 public class Profile extends Displayable implements VectorData {
67 /**The number of points.*/
68 protected int n_points;
69 /**The array of clicked points.*/
70 protected double[][] p = new double[2][5];
71 /**The array of left control points, one for each clicked point.*/
72 protected double[][] p_l = new double[2][5];
73 /**The array of right control points, one for each clicked point.*/
74 protected double[][] p_r = new double[2][5];
75 /**The array of interpolated points generated from p, p_l and p_r.*/
76 protected double[][] p_i = new double[2][0];
77 /**Paint/behave as open or closed curve.*/
78 protected boolean closed = false;
80 /** A new user-requested Profile.*/
81 public Profile(final Project project, final String title, final double x, final double y) {
82 super(project, title, x, y);
83 n_points = 0;
84 addToDatabase();
87 /**Construct a Bezier Profile object from a set of points mixed in this pattern: PCCPCCPCCPCCP , so, [PCC]n where P = backbone point and C = control point. This results from a BezierApproximation on a path of points as drawed with the mouse. Keep in mind the control points will NOT have the same tangents, but this may be either left like that or corrected with some smoothing algorithm.*/
88 public Profile(final Project project, final String title, final double x, final double y, final Point2D.Double[] points) {
89 super(project, title, x, y);
90 //setup arrays
91 final int size = (points.length / 3) + 1;
92 p = new double[2][size];
93 p_l =new double[2][size];
94 p_r =new double[2][size];
95 //first point:
96 p[0][0] = points[0].x;
97 p[1][0] = points[0].y;
98 p_l[0][0] = p[0][0];
99 p_l[1][0] = p[1][0];
100 p_r[0][0] = points[1].x;
101 p_r[1][0] = points[1].y;
102 n_points++;
103 //all middle points:
104 for (int j=1, i=3; i<points.length-3; i+=3, j++) {
105 p[0][j] = points[i].x;
106 p[1][j] = points[i].y;
107 p_l[0][j] = points[i-1].x;
108 p_l[1][j] = points[i-1].y;
109 if (null == points[i+1]) {
110 Utils.log("BezierProfile: points[" + i + " + 1] is null !");
112 p_r[0][j] = points[i+1].x;
113 p_r[1][j] = points[i+1].y;
114 n_points++;
116 //last point:
117 final int last = points.length-1;
118 p[0][n_points] = points[last].x;
119 p[1][n_points] = points[last].y;
120 p_l[0][n_points] = points[last-1].x;
121 p_l[1][n_points] = points[last-1].y;
122 p_r[0][n_points] = p[0][n_points];
123 p_r[1][n_points] = p[1][n_points];
124 n_points++;
126 calculateBoundingBox();
128 //add to database
129 addToDatabase();
130 updateInDatabase("points");
133 /**Construct a Bezier Profile from the database.*/
134 public Profile(final Project project, final long id, final String title, final float alpha, final boolean visible, final Color color, final double[][][] bezarr, final boolean closed, final boolean locked, final AffineTransform at) {
135 super(project, id, title, locked, at, 0, 0);
136 this.visible = visible;
137 this.alpha = alpha;
138 this.color = color;
139 this.closed = closed;
140 //make points from the polygon, in which they are stored as LPRLPR ... left control - backbone point - right control point (yes this looks very much like codons, but the important bit is the middle one, not the left one! HaHaHaHa!)
141 //// points are fields x,y in PGpoint.
142 p_l = bezarr[0];
143 p = bezarr[1];
144 p_r = bezarr[2];
145 n_points = p[0].length;
147 //calculate width and height
148 calculateBoundingBox(false);
151 /** Construct a Bezier Profile from the database, but the points will be loaded later, when actually needed, by calling setupForDisplay(). */
152 public Profile(final Project project, final long id, final String title, final float width, final float height, final float alpha, final boolean visible, final Color color, final boolean closed, final boolean locked, final AffineTransform at) {
153 super(project, id, title, locked, at, width, height);
154 this.visible = visible;
155 this.alpha = alpha;
156 this.color = color;
157 this.closed = closed;
158 this.n_points = -1; //used as a flag to signal "I have points, but unloaded"
161 /** Construct a Bezier Profile from an XML entry. */
162 public Profile(final Project project, final long id, final HashMap<String,String> ht, final HashMap<Displayable,String> ht_links) {
163 super(project, id, ht, ht_links);
164 // parse data
165 for (final Map.Entry<String,String> entry : ht.entrySet()) {
166 final String key = entry.getKey();
167 final String data = entry.getValue();
168 if (key.equals("d")) {
169 // parse the SVG points data
170 final ArrayList<String> al_p = new ArrayList<String>(),
171 al_p_r = new ArrayList<String>(),
172 al_p_l = new ArrayList<String>();// needs shifting, inserting one point at the beginning if not closed.
173 // sequence is: M p[0],p[1] C p_r[0],p_r[1] p_l[0],p_l[1] and repeat without the M, and finishes with the last p[0],p[1]. If closed, appended at the end is p_r[0],p_r[1] p_l[0],p_l[1]
174 // first point:
175 int i_start = data.indexOf('M');
176 int i_end = data.indexOf('C');
177 final String point = data.substring(i_start+1, i_end).trim();
178 al_p.add(point);
179 boolean go = true;
180 while (go) {
181 i_start = i_end;
182 i_end = data.indexOf('C', i_end+1);
183 if (-1 == i_end) {
184 i_end = data.length() -1;
185 go = false;
187 String txt = data.substring(i_start+1, i_end).trim();
188 // eliminate double spaces
189 while (-1 != txt.indexOf(" ")) {
190 txt = txt.replaceAll(" ", " ");
192 // reduce ", " and " ," to ","
193 txt = txt.replaceAll(" ,", ",");
194 txt = txt.replaceAll(", ", ",");
195 // cut by spaces
196 final String[] points = txt.split(" ");
197 // debug:
198 //Utils.log("Profile init: txt=__" + txt + "__ points.length=" + points.length);
199 if (3 == points.length) {
200 al_p_r.add(points[0]);
201 al_p_l.add(points[1]);
202 al_p.add(points[2]);
203 } else {
204 // error
205 Utils.log("Profile constructor from XML: error at parsing points.");
207 // example: C 34.5,45.6 45.7,23.0 34.8, 78.0 C ..
209 // NO, usability problems // this.closed = (-1 != data.lastIndexOf('z')); // 'z' must be lowercase to comply with SVG style
210 if (this.closed) {
211 // prepend last left control point and delete from the end
212 al_p_l.add(0, al_p_l.remove(al_p_l.size() -1));
213 // remove last point (duplicated in this SVG format)
214 al_p.remove(al_p.size() -1); // TODO check that it's really closed by comparing the points, not just by the z, and if not closed, remove the control points.
215 } else {
216 // prepend a left control point equal to the first point
217 al_p_l.add(0, al_p.get(0)); // no need to clone, String is final
218 // and append a right control point equal to the last point
219 al_p_r.add(al_p.get(al_p.size() -1));
221 // sanity check:
222 if (!(al_p.size() == al_p_l.size() && al_p_l.size() == al_p_r.size())) {
223 Utils.log2("Profile XML parsing: Disagreement in the number of points:\n\tp.length=" + al_p.size() + "\n\tp_l.length=" + al_p_l.size() + "\n\tp_r.length=" + al_p_r.size());
225 // Now parse the points
226 this.n_points = al_p.size();
227 this.p = new double[2][n_points];
228 this.p_l = new double[2][n_points];
229 this.p_r = new double[2][n_points];
230 for (int i=0; i<n_points; i++) {
231 String[] sp = al_p.get(i).split(",");
232 p[0][i] = Double.parseDouble(sp[0]);
233 p[1][i] = Double.parseDouble(sp[1]);
234 sp = al_p_l.get(i).split(",");
235 p_l[0][i] = Double.parseDouble(sp[0]);
236 p_l[1][i] = Double.parseDouble(sp[1]);
237 sp = al_p_r.get(i).split(",");
238 p_r[0][i] = Double.parseDouble(sp[0]);
239 p_r[1][i] = Double.parseDouble(sp[1]);
241 this.p_i = new double[2][0]; // empty
242 generateInterpolatedPoints(0.05);
247 /** A constructor for cloning purposes. */
248 private Profile(final Project project, final String title, final double x, final double y, final float width, final float height, final float alpha, final Color color, final int n_points, final double[][] p, final double[][] p_r, final double[][] p_l, final double[][] p_i, final boolean closed) {
249 super(project, title, x, y);
250 this.width = width;
251 this.height = height;
252 this.alpha = alpha;
253 this.color = color;
254 this.n_points = n_points;
255 this.p = p;
256 this.p_r = p_r;
257 this.p_l = p_l;
258 this.p_i = p_i;
259 this.closed = closed;
260 addToDatabase();
261 updateInDatabase("all");
264 /**Increase the size of the arrays by 5 points.*/
265 protected void enlargeArrays() {
266 //catch length
267 final int length = p[0].length;
268 //make copies
269 final double[][] p_copy = new double[2][length + 5];
270 final double[][] p_l_copy = new double[2][length + 5];
271 final double[][] p_r_copy = new double[2][length + 5];
272 //copy values
273 System.arraycopy(p[0], 0, p_copy[0], 0, length);
274 System.arraycopy(p[1], 0, p_copy[1], 0, length);
275 System.arraycopy(p_l[0], 0, p_l_copy[0], 0, length);
276 System.arraycopy(p_l[1], 0, p_l_copy[1], 0, length);
277 System.arraycopy(p_r[0], 0, p_r_copy[0], 0, length);
278 System.arraycopy(p_r[1], 0, p_r_copy[1], 0, length);
279 //assign them
280 this.p = p_copy;
281 this.p_l = p_l_copy;
282 this.p_r = p_r_copy;
285 /** Returns the number of backbone points. */
286 public int getPointCount() {
287 return n_points;
290 public boolean isClosed() {
291 return this.closed;
294 /**Find a point in an array 'a', with a precision dependent on the magnification. */
295 protected int findPoint(final double[][] a, final double x_p, final double y_p, final double magnification) {
296 int index = -1;
297 // make parameters local
298 double d = (10.0D / magnification);
299 if (d < 2) d = 2;
300 for (int i=0; i<n_points; i++) {
301 if ((Math.abs(x_p - a[0][i]) + Math.abs(y_p - a[1][i])) <= d) {
302 index = i;
305 return index;
308 /**Remove a point from the bezier backbone and its two associated control points.*/
309 protected void removePoint(final int index) {
310 // check preconditions:
311 if (index < 0) {
312 return;
313 } else if (n_points - 1 == index) {
314 //last point out
315 n_points--;
316 } else {
317 //one point out (but not the last)
318 n_points--;
319 // shift all points after 'index' one position to the left:
320 for (int i=index; i<n_points; i++) {
321 p[0][i] = p[0][i+1]; //the +1 doesn't fail ever because the n_points has been adjusted above, but the arrays are still the same size. The case of deleting the last point is taken care above.
322 p[1][i] = p[1][i+1];
323 p_l[0][i] = p_l[0][i+1];
324 p_l[1][i] = p_l[1][i+1];
325 p_r[0][i] = p_r[0][i+1];
326 p_r[1][i] = p_r[1][i+1];
329 // open the curve if necessary
330 if (closed && n_points < 2) {
331 closed = false;
332 updateInDatabase("closed");
334 //update in database
335 updateInDatabase("points");
338 /**Calculate distance from one point to another.*/
339 protected double distance(final double x1, final double y1, final double x2, final double y2) {
340 return Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
343 /**Move backbone point.*/
344 protected void dragPoint(final int index, final int dx, final int dy) {
345 p[0][index] += dx;
346 p[1][index] += dy;
347 p_l[0][index] += dx;
348 p_l[1][index] += dy;
349 p_r[0][index] += dx;
350 p_r[1][index] += dy;
353 /**Set the control points to the same value as the backbone point which they control.*/
354 protected void resetControlPoints(final int index) {
355 p_l[0][index] = p[0][index];
356 p_l[1][index] = p[1][index];
357 p_r[0][index] = p[0][index];
358 p_r[1][index] = p[1][index];
361 /**Drag a control point and adjust the other, dependent one, in a symmetric way or not.*/
362 protected void dragControlPoint(final int index, final double x_d, final double y_d, final double[][] p_dragged, final double[][] p_adjusted, final boolean symmetric) {
363 //measure hypothenusa: from p to p control
364 double hypothenusa;
365 if (symmetric) {
366 //make both points be dragged in parallel, the same distance
367 hypothenusa = distance(p[0][index], p[1][index], p_dragged[0][index], p_dragged[1][index]);
368 } else {
369 //make each point be dragged with its own distance
370 hypothenusa = distance(p[0][index], p[1][index], p_adjusted[0][index], p_adjusted[1][index]);
372 //measure angle: use the point being dragged
373 final double angle = Math.atan2(p_dragged[0][index] - p[0][index], p_dragged[1][index] - p[1][index]) + Math.PI;
374 //apply
375 p_dragged[0][index] = x_d;
376 p_dragged[1][index] = y_d;
377 p_adjusted[0][index] = p[0][index] + hypothenusa * Math.sin(angle); // it's sin and not cos because of stupid Math.atan2 way of delivering angles
378 p_adjusted[1][index] = p[1][index] + hypothenusa * Math.cos(angle);
381 /**Add a point either at the end or between two existing points, with accuracy depending on magnification. Does not update the database. x_p,y_p are in the local space. */
382 protected int addPoint(final double x_p, final double y_p, final double magnification, final double bezier_finess) {
383 //lookup closest interpolated point and then get the closest clicked point to it
384 int index = findClosestPoint(x_p, y_p, magnification, bezier_finess); // x_p, y_p are already local.
385 if (closed && -1 == index) {
386 return -1;
388 //check array size
389 if (p[0].length == n_points) {
390 enlargeArrays();
392 //decide:
393 if (0 == n_points || 1 == n_points || -1 == index || index + 1 == n_points) {
394 //append at the end
395 p[0][n_points] = p_l[0][n_points] = p_r[0][n_points] = x_p;
396 p[1][n_points] = p_l[1][n_points] = p_r[1][n_points] = y_p;
397 index = n_points;
398 } else {
399 //insert at index:
400 index++; //so it is added after the closest point;
401 // 1 - copy second half of array
402 final int sh_length = n_points -index;
403 final double[][] p_copy = new double[2][sh_length];
404 final double[][] p_l_copy = new double[2][sh_length];
405 final double[][] p_r_copy = new double[2][sh_length];
406 System.arraycopy(p[0], index, p_copy[0], 0, sh_length);
407 System.arraycopy(p[1], index, p_copy[1], 0, sh_length);
408 System.arraycopy(p_l[0], index, p_l_copy[0], 0, sh_length);
409 System.arraycopy(p_l[1], index, p_l_copy[1], 0, sh_length);
410 System.arraycopy(p_r[0], index, p_r_copy[0], 0, sh_length);
411 System.arraycopy(p_r[1], index, p_r_copy[1], 0, sh_length);
412 // 2 - insert value into 'p' (the two control arrays get the same value)
413 p[0][index] = p_l[0][index] = p_r[0][index] = x_p;
414 p[1][index] = p_l[1][index] = p_r[1][index] = y_p;
415 // 3 - copy second half into the array
416 System.arraycopy(p_copy[0], 0, p[0], index+1, sh_length);
417 System.arraycopy(p_copy[1], 0, p[1], index+1, sh_length);
418 System.arraycopy(p_l_copy[0], 0, p_l[0], index+1, sh_length);
419 System.arraycopy(p_l_copy[1], 0, p_l[1], index+1, sh_length);
420 System.arraycopy(p_r_copy[0], 0, p_r[0], index+1, sh_length);
421 System.arraycopy(p_r_copy[1], 0, p_r[1], index+1, sh_length);
423 //add one up
424 this.n_points++;
426 // set the x,y and readjust points
427 calculateBoundingBox();
429 return index;
432 /**Find the closest point to an interpolated point with precision depending upon magnification. The point x_p,y_p is in local coordinates. */
433 protected int findClosestPoint(final double x_p, final double y_p, final double magnification, final double bezier_finess) {
434 if (0 == p_i[0].length) return -1; // when none added yet
435 int index = -1;
436 double distance_sq = Double.MAX_VALUE;
437 double distance_sq_i;
438 double max = 12.0D / magnification;
439 max = max * max; //squaring it
440 for (int i=0; i<p_i[0].length; i++) {
441 //see which point is closer (there's no need to calculate the distance by multiplying squares and so on).
442 distance_sq_i = (p_i[0][i] - x_p)*(p_i[0][i] - x_p) + (p_i[1][i] - y_p)*(p_i[1][i] - y_p);
443 if (distance_sq_i < max && distance_sq_i < distance_sq) {
444 index = i;
445 distance_sq = distance_sq_i;
448 if (-1 != index) {
449 int index_found = (int)((double)index * bezier_finess);
450 if (index < (index_found / bezier_finess)) {
451 index_found--;
453 index = index_found;
455 return index;
458 /**Toggle curve closed/open.*/
459 public void toggleClosed() {
460 if (closed) {
461 closed = false;
462 } else {
463 closed = true;
465 //update database
466 updateInDatabase("closed");
469 protected void generateInterpolatedPoints(final double bezier_finess) {
470 if (0 >= n_points) {
471 return;
474 int n = n_points;
475 if (closed && n > 1) {
476 //do the loop for one more
477 n++;
478 //enlarge arrays if needed
479 if (p[0].length == n_points) {
480 enlargeArrays();
482 //add first point to the end (doesn't need to be deleted because n_points hasn't changed)
483 // n_points works as an index here.
484 p[0][n_points] = p[0][0];
485 p[1][n_points] = p[1][0];
486 p_l[0][n_points] = p_l[0][0];
487 p_l[1][n_points] = p_l[1][0];
488 p_r[0][n_points] = p_r[0][0];
489 p_r[1][n_points] = p_r[1][0];
492 // case there's only one point
493 if (1 == n_points) {
494 p_i = new double[2][1];
495 p_i[0][0] = p[0][0];
496 p_i[1][0] = p[1][0];
497 return;
499 // case there's more: interpolate!
500 p_i = new double[2][(int)(n * (1.0D/bezier_finess))];
501 double t, f0, f1, f2, f3;
502 int next = 0;
503 for (int i=0; i<n-1; i++) {
504 for (t=0.0D; t<1.0D; t += bezier_finess) {
505 f0 = (1-t)*(1-t)*(1-t);
506 f1 = 3*t*(1-t)*(1-t);
507 f2 = 3*t*t*(1-t);
508 f3 = t*t*t;
509 p_i[0][next] = f0*p[0][i] + f1*p_r[0][i] + f2*p_l[0][i+1] + f3*p[0][i+1];
510 p_i[1][next] = f0*p[1][i] + f1*p_r[1][i] + f2*p_l[1][i+1] + f3*p[1][i+1];
511 next++;
512 //enlarge if needed (when bezier_finess is not 0.05, it's difficult to predict because of int loss of precision.
513 if (p_i[0].length == next) {
514 final double[][] p_i_copy = new double[2][p_i[0].length + 5];
515 System.arraycopy(p_i[0], 0, p_i_copy[0], 0, p_i[0].length);
516 System.arraycopy(p_i[1], 0, p_i_copy[1], 0, p_i[1].length);
517 p_i = p_i_copy;
521 if (p_i[0].length != next) { // 'next' works as a length here
522 //resize back
523 final double[][] p_i_copy = new double[2][next];
524 System.arraycopy(p_i[0], 0, p_i_copy[0], 0, next);
525 System.arraycopy(p_i[1], 0, p_i_copy[1], 0, next);
526 p_i = p_i_copy;
530 @Override
531 public void paint(final Graphics2D g, final Rectangle srcRect, final double magnification, final boolean active, final int channels, final Layer active_layer, final List<Layer> layers) {
532 if (0 == n_points) return;
533 if (-1 == n_points) {
534 // load points from the database
535 setupForDisplay();
536 if (-1 == n_points) {
537 Utils.log2("Profile.paint: Some error ocurred, can't load points from database.");
538 return;
541 //arrange transparency
542 Composite original_composite = null;
543 if (alpha != 1.0f) {
544 original_composite = g.getComposite();
545 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
548 // local pointers, since they may be transformed
549 double[][] p = this.p;
550 double[][] p_r = this.p_r;
551 double[][] p_l = this.p_l;
552 double[][] p_i = this.p_i;
553 if (!this.at.isIdentity()) {
554 final Object[] ob = getTransformedData();
555 p = (double[][])ob[0];
556 p_l = (double[][])ob[1];
557 p_r = (double[][])ob[2];
558 p_i = (double[][])ob[3];
560 if (active) {
561 //draw/fill points
562 final int oval_radius = (int)Math.ceil(4 / magnification);
563 final int oval_corr = (int)Math.ceil(3 / magnification);
564 for (int j=0; j<n_points; j++) {
565 DisplayCanvas.drawHandle(g, (int)p[0][j], (int)p[1][j], magnification);
566 g.setColor(this.color);
567 //fill small ovals at control points
568 g.fillOval((int)p_l[0][j] -oval_corr, (int)p_l[1][j] -oval_corr, oval_radius, oval_radius);
569 g.fillOval((int)p_r[0][j] -oval_corr, (int)p_r[1][j] -oval_corr, oval_radius, oval_radius);
570 //draw lines between backbone and control points
571 g.drawLine((int)p[0][j], (int)p[1][j], (int)p_l[0][j], (int)p_l[1][j]);
572 g.drawLine((int)p[0][j], (int)p[1][j], (int)p_r[0][j], (int)p_r[1][j]);
576 //set color
577 g.setColor(this.color);
579 //draw lines between any two consecutive interpolated points
580 for (int i=0; i<p_i[0].length-1; i++) {
581 g.drawLine((int)p_i[0][i], (int)p_i[1][i], (int)p_i[0][i+1], (int)p_i[1][i+1]);
583 //draw last segment between last and first points, only if closed:
584 if (closed) {
585 g.drawLine((int)p_i[0][p_i[0].length-1], (int)p_i[1][p_i[0].length-1], (int)p_i[0][0], (int)p_i[1][0]);
588 //Transparency: fix alpha composite back to original.
589 if (null != original_composite) {
590 g.setComposite(original_composite);
594 /**Helper vars for mouse events. It's safe to have them static since only one Profile will be edited at a time.*/
595 static private int index = -1;
596 static private int index_l = -1;
597 static private int index_r = -1;
598 static private boolean is_new_point = false;
600 /**Execute the mousePressed MouseEvent on this Profile.*/
601 @Override
602 public void mousePressed(final MouseEvent me, final Layer layer, int x_p, int y_p, final double mag) {
603 // transform the x_p, y_p to the local coordinates
604 if (!this.at.isIdentity()) {
605 final Point2D.Double po = inverseTransformPoint(x_p, y_p);
606 x_p = (int)po.x;
607 y_p = (int)po.y;
610 final int tool = ProjectToolbar.getToolId();
612 // reset helper vars
613 is_new_point = false;
614 index = index_r = index_l = -1;
616 if (ProjectToolbar.PEN == tool) {
618 //collect vars
619 if (Utils.isControlDown(me) && me.isShiftDown()) {
620 index = findNearestPoint(p, n_points, x_p, y_p);
621 } else {
622 index = findPoint(p, x_p, y_p, mag);
625 if (-1 != index) {
626 if (Utils.isControlDown(me) && me.isShiftDown()) {
627 //delete point
628 removePoint(index);
629 index = index_r = index_l = -1;
630 generateInterpolatedPoints(0.05);
631 repaint(false);
632 return;
633 } else if (me.isAltDown()) {
634 resetControlPoints(index);
635 return;
636 } else if (me.isShiftDown()) {
637 if (0 == index && n_points > 1 && !closed) {
638 //close curve, reset left control point of the first point and set it up for dragging
639 closed = true;
640 updateInDatabase("closed");
641 p_l[0][0] = p[0][0];
642 p_l[1][0] = p[1][0];
643 index = -1;
644 index_r = -1;
645 index_l = 0; //the first one
646 repaint(false);
647 return;
652 // find if click is on a left control point
653 index_l = findPoint(p_l, x_p, y_p, mag);
654 index_r = -1;
655 // if not, then try on the set of right control points
656 if (-1 == index_l) {
657 index_r = findPoint(p_r, x_p, y_p, mag);
660 //if no conditions are met, attempt to add point (won't get added if the curve is closed and the click is too far from the interpolated points).
661 if (-1 == index && -1 == index_l && -1 == index_r && !me.isShiftDown() && !me.isAltDown()) {
662 //add a new point and assign its index to the left control point, so it can be dragged right away. This is copying the way Karbon does for drawing Bezier curves, which is the contrary to Adobe's way, but which I find more useful.
663 index_l = addPoint(x_p, y_p, mag, 0.05);
664 if (-1 != index_l) is_new_point = true;
665 else if (1 == n_points) {
666 //for the very first point, drag the right control point, not the left.
667 index_r = index_l;
668 index_l = -1;
670 repaint(false);
671 return;
676 /**Execute the mouseDragged MouseEvent on this Profile.*/
677 @Override
678 public void mouseDragged(final MouseEvent me, final Layer layer, int x_p, int y_p, int x_d, int y_d, int x_d_old, int y_d_old) {
679 // transform to the local coordinates
680 if (!this.at.isIdentity()) {
681 final Point2D.Double p = inverseTransformPoint(x_p, y_p);
682 x_p = (int)p.x;
683 y_p = (int)p.y;
684 final Point2D.Double pd = inverseTransformPoint(x_d, y_d);
685 x_d = (int)pd.x;
686 y_d = (int)pd.y;
687 final Point2D.Double pdo = inverseTransformPoint(x_d_old, y_d_old);
688 x_d_old = (int)pdo.x;
689 y_d_old = (int)pdo.y;
692 final int tool = ProjectToolbar.getToolId();
694 if (ProjectToolbar.PEN == tool) {
696 //if a point in the backbone is found, then:
697 if (-1 != index) {
698 if (!me.isAltDown()) {
699 //drag point
700 dragPoint(index, x_d - x_d_old, y_d - y_d_old);
701 } else {
702 //drag both control points symmetrically
703 dragControlPoint(index, x_d, y_d, p_l, p_r, true);
705 generateInterpolatedPoints(0.05);
706 repaint(false);
707 return;
710 //if a control point is found, then drag it, adjusting the other control point non-symmetrically
711 if (-1 != index_r) {
712 dragControlPoint(index_r, x_d, y_d, p_r, p_l, is_new_point);
713 generateInterpolatedPoints(0.05);
714 repaint(false);
715 return;
717 if (-1 != index_l) {
718 dragControlPoint(index_l, x_d, y_d, p_l, p_r, is_new_point);
719 generateInterpolatedPoints(0.05);
720 repaint(false);
721 return;
724 // no points selected. Drag the whole curve on alt down (without affecting linked curves)
725 if (me.isAltDown()) {
726 final int dx = x_d - x_d_old;
727 final int dy = y_d - y_d_old;
728 this.at.translate(dx, dy);
729 repaint(false);
730 return;
735 /**Execute the mouseReleased MouseEvent on this Profile.*/
736 @Override
737 public void mouseReleased(final MouseEvent me, final Layer layer, final int x_p, final int y_p, final int x_d, final int y_d, final int x_r, final int y_r) {
738 final int tool = ProjectToolbar.getToolId();
739 if (ProjectToolbar.PEN == tool) {
740 //generate interpolated points
741 generateInterpolatedPoints(0.05);
742 repaint(); //needed at least for the removePoint, and also for repainting the DisplayablePanel and the DisplayNavigator // TODO this may be redundant with below
745 //update points in database if there was any change
746 if (-1 != index || -1 != index_r || -1 != index_l) {
747 updateInDatabase("points");
748 updateInDatabase("transform+dimensions"); //was: dimensions
749 Display.repaint(layer, this); // the DisplayablePanel
750 } else if (x_r != x_p || y_r != y_p) {
751 updateInDatabase("transform+dimensions");
752 Display.repaint(layer, this); // the DisplayablePanel
754 // reset helper vars
755 is_new_point = false;
756 index = index_r = index_l = -1;
759 protected void calculateBoundingBox() {
760 calculateBoundingBox(true);
763 /**Calculate the bounding box of the curve in the shape of a Rectangle defined by x,y,width,height. If adjust_position is true, then points are made local to the minimal x,y. */
764 protected void calculateBoundingBox(final boolean adjust_position) {
765 if (0 == n_points) {
766 this.width = this.height = 0;
767 updateBucket();
768 return;
770 //go over all points and control points and find the max and min
771 // (there's no need to use the interpolated points because the points and control points define the boxes in which the interpolated points are).
772 double min_x = Double.MAX_VALUE;
773 double min_y = Double.MAX_VALUE;
774 double max_x = 0.0D;
775 double max_y = 0.0D;
777 for (int i=0; i<n_points; i++) {
778 if (p[0][i] < min_x) min_x = p[0][i];
779 if (p_l[0][i] < min_x) min_x = p_l[0][i];
780 if (p_r[0][i] < min_x) min_x = p_r[0][i];
781 if (p[1][i] < min_y) min_y = p[1][i];
782 if (p_l[1][i] < min_y) min_y = p_l[1][i];
783 if (p_r[1][i] < min_y) min_y = p_r[1][i];
784 if (p[0][i] > max_x) max_x = p[0][i];
785 if (p_l[0][i] > max_x) max_x = p_l[0][i];
786 if (p_r[0][i] > max_x) max_x = p_r[0][i];
787 if (p[1][i] > max_y) max_y = p[1][i];
788 if (p_l[1][i] > max_y) max_y = p_l[1][i];
789 if (p_r[1][i] > max_y) max_y = p_r[1][i];
792 this.width = (float)(max_x - min_x);
793 this.height = (float)(max_y - min_y);
795 if (adjust_position) {
796 // now readjust points to make min_x,min_y be the x,y
797 for (int i=0; i<n_points; i++) {
798 p[0][i] -= min_x; p[1][i] -= min_y;
799 p_l[0][i] -= min_x; p_l[1][i] -= min_y;
800 p_r[0][i] -= min_x; p_r[1][i] -= min_y;
802 for (int i=0; i<p_i[0].length; i++) {
803 p_i[0][i] -= min_x; p_i[1][i] -= min_y;
805 this.at.translate(min_x, min_y); // not using super.translate(...) because a preConcatenation is not needed; here we deal with the data.
806 updateInDatabase("transform");
808 updateBucket();
809 updateInDatabase("dimensions");
813 @Override
814 public void repaint() {
815 repaint(true);
818 /**Repaints in the given ImageCanvas only the area corresponding to the bounding box of this Profile. */
819 public void repaint(final boolean repaint_navigator) {
820 //TODO: this could be further optimized to repaint the bounding box of the last modified segments, i.e. the previous and next set of interpolated points of any given backbone point. This would be trivial if each segment of the Bezier curve was an object.
821 final Rectangle box = getBoundingBox(null);
822 calculateBoundingBox();
823 box.add(getBoundingBox(null));
824 Display.repaint(layer, this, box, 5, repaint_navigator);
827 /**Check if the given point (usually from a MOUSE_PRESSED MouseEvent) is contained within the boundaries of this object. The point is expected as local coordinates. */
828 public boolean containsPoint(final int x_p, final int y_p) {
829 // as in getPerimeter():
830 final int n_i = p_i[0].length;
831 final int[] intx = new int[n_i];
832 final int[] inty = new int[n_i];
833 for (int i=0; i<n_i; i++) {
834 intx[i] = (int)p_i[0][i];
835 inty[i] = (int)p_i[1][i];
837 final Polygon polygon = new Polygon(intx, inty, n_i);
838 return polygon.contains(x_p, y_p);
841 /**Release all memory resources taken by this object.*/
842 @Override
843 public void destroy() {
844 super.destroy();
845 p = null;
846 p_l = null;
847 p_r = null;
848 p_i = null;
851 /**Make this object ready to be painted.*/
852 private void setupForDisplay() {
853 // load points
854 if (null == p || null == p_l || null == p_r) {
855 //load points from database
856 final double[][][] bezarr = project.getLoader().fetchBezierArrays(this.id);
857 if (null == bezarr) {
858 Utils.log("Profile.setupForDisplay: could not load the bezier points from the database for id=" + this.id);
859 this.p_i = new double[0][0];
860 return;
862 p_l = bezarr[0];
863 p = bezarr[1];
864 p_r = bezarr[2];
865 n_points = p[0].length;
866 // recreate interpolated points
867 generateInterpolatedPoints(0.05); //TODO the 0.05 bezier finess, read the value from the Project perhaps.
871 /**Cache this Profile if needed.*/ //Is there much sense in caching Profile objects? Unless you have thousands ... and that CAN be the case!
872 public void cache() {
873 //TODO
876 /**Release memory resources used by this object: namely the arrays of points, which can be reloaded with a call to setupForDisplay()*/
877 public void flush() {
878 p = null;
879 p_l = null;
880 p_r = null;
881 p_i = null;
882 n_points = -1; // flag that points exist (and need to be reloaded)
885 /** The perimeter of this profile, in integer precision. */
886 @Override
887 public Polygon getPerimeter() {
888 if (-1 == n_points) setupForDisplay();
889 if (null == p_i) return null; // has been flushed, incorrect access! This is a patch.
891 // transform
892 double[][] p_i = this.p_i;
893 if (!this.at.isIdentity()) p_i = transformPoints(this.p_i);
895 final int n_i = p_i[0].length;
896 final int[] intx = new int[n_i];
897 final int[] inty = new int[n_i];
898 for (int i=0; i<n_i; i++) {
899 intx[i] = (int)p_i[0][i];
900 inty[i] = (int)p_i[1][i];
902 return new Polygon(intx, inty, n_i);
905 /** Writes the data of this object as a Bezier object in the .shapes file represented by the 'data' StringBuffer. The z_scale is added to manually correct for sample squashing under the coverslip. */
906 public void toShapesFile(final StringBuffer data, final String group, final String color, final double z_scale) {
907 if (-1 == n_points) setupForDisplay(); // reload
908 // local pointers, since they may be transformed
909 double[][] p = this.p;
910 double[][] p_r = this.p_r;
911 double[][] p_l = this.p_l;
912 if (!this.at.isIdentity()) {
913 final Object[] ob = getTransformedData();
914 p = (double[][])ob[0];
915 p_l = (double[][])ob[1];
916 p_r = (double[][])ob[2];
918 final double z = layer.getZ();
919 final char l = '\n';
920 data.append("type=bezier").append(l)
921 .append("name=").append(project.getMeaningfulTitle(this)).append(l)
922 .append("group=").append(group).append(l)
923 .append("color=").append(color).append(l)
924 .append("supergroup=").append("null").append(l)
925 .append("supercolor=").append("null").append(l)
926 .append("in slice=").append(z * z_scale).append(l) // fake, this is now the absolute z coordinate
927 .append("curve_closed=").append(true).append(l) // must!
928 .append("density field=").append(false).append(l) // must!
930 for (int i=0; i<n_points; i++) {
931 data.append("p x=").append(p[0][i]).append(l)
932 .append("p y=").append(p[1][i]).append(l)
933 .append("p_r x=").append(p_r[0][i]).append(l)
934 .append("p_r y=").append(p_r[1][i]).append(l)
935 .append("p_l x=").append(p_l[0][i]).append(l)
936 .append("p_l y=").append(p_l[1][i]).append(l)
941 @Override
942 public void exportSVG(final StringBuffer data, final double z_scale, final String indent) {
943 final String in = indent + "\t";
944 if (-1 == n_points) setupForDisplay(); // reload
945 if (0 == n_points) return;
946 final String[] RGB = Utils.getHexRGBColor(color);
947 final double[] a = new double[6];
948 at.getMatrix(a);
949 data.append(indent).append("<path\n")
950 .append(in).append("type=\"profile\"\n")
951 .append(in).append("id=\"").append(id).append("\"\n")
952 .append(in).append("transform=\"matrix(").append(a[0]).append(',')
953 .append(a[1]).append(',')
954 .append(a[2]).append(',')
955 .append(a[3]).append(',')
956 .append(a[4]).append(',')
957 .append(a[5]).append(")\"\n")
958 .append(in).append("style=\"fill:none;stroke-opacity:").append(alpha).append(";stroke:#").append(RGB[0]).append(RGB[1]).append(RGB[2]).append(";stroke-width:1.0px;\"\n")
959 .append(in).append("d=\"M");//.append(p[0][0]).append(',').append(p[1][0]).append(" C ").append(p_r[0][0]).append(',').append(p_r[1][0]);
960 for (int i=0; i<n_points-1; i++) {
961 data.append(' ').append(p[0][i]).append(',').append(p[1][i])
962 .append(" C ").append(p_r[0][i]).append(',').append(p_r[1][i])
963 .append(' ').append(p_l[0][i+1]).append(',').append(p_l[1][i+1])
966 data.append(' ').append(p[0][n_points-1]).append(',').append(p[1][n_points-1]);
967 if (closed) {
968 data.append(" C ").append(p_r[0][n_points-1]).append(',').append(p_r[1][n_points-1])
969 .append(' ').append(p_l[0][0]).append(',').append(p_l[1][0])
970 .append(' ').append(p[0][0]).append(',').append(p[1][0])
971 .append(" z")
974 data.append("\"\n")
975 .append(in).append("z=\"").append(layer.getZ() * z_scale).append("\"\n")
976 .append(in).append("links=\"")
978 if (null != hs_linked && 0 != hs_linked.size()) {
979 int ii = 0;
980 final int len = hs_linked.size();
981 for (final Displayable d : hs_linked) {
982 data.append(d.getId());
983 if (ii != len-1) data.append(',');
984 ii++;
987 data.append("\"\n")
988 .append(indent).append("/>\n")
992 /** Returns a triple array, each containing a [2][n_points] array
993 * specifiying the x,y of each left control point, backbone point
994 * and right control point respectively. All of them are copies. */
995 public double[][][] getBezierArrays() {
996 //assumes the profile is a Bezier curve.
997 //put points and control points into PGpoint objects, as: LPRLPRLPR... (L = left control point, P = backbone point, R = right control point)
998 if (-1 == n_points) setupForDisplay(); // reload
999 final double[][][] b = new double[3][2][];
1000 b[0][0] = Utils.copy(p_l[0], n_points);
1001 b[0][1] = Utils.copy(p_l[1], n_points);
1002 b[1][0] = Utils.copy(p[0], n_points);
1003 b[1][1] = Utils.copy(p[1], n_points);
1004 b[2][0] = Utils.copy(p_r[0], n_points);
1005 b[2][1] = Utils.copy(p_r[1], n_points);
1006 return b;
1009 @Override
1010 public boolean isDeletable() {
1011 return 0 == n_points;
1014 /** Returns true if it's linked to at least one patch in the same Layer. Otherwise returns false. */
1015 @Override
1016 public boolean isLinked() {
1017 if (null == hs_linked || hs_linked.isEmpty()) return false;
1018 for (final Displayable d : hs_linked) {
1019 if (d instanceof Patch && d.layer.equals(this.layer)) return true;
1021 return false;
1024 /** Returns false if the target_layer contains a profile that is directly linked to this profile. */
1025 @Override
1026 public boolean canSendTo(final Layer target_layer) {
1027 if (null == hs_linked || hs_linked.isEmpty()) return false;
1028 for (final Displayable d : hs_linked) {
1029 if (d instanceof Profile && d.layer.equals(target_layer)) return false;
1031 return true;
1034 protected double[][] getFirstPoint()
1036 if (0 == n_points) return null;
1037 return new double[][]{{p_l[0][0],p_l[1][0]},{p[0][0],p[1][0]},{p_r[0][0],p_r[1][0]}};
1040 protected double[][] getLastPoint()
1042 return new double[][]{{p_l[0][n_points-1],p_l[1][n_points-1]},{p[0][n_points-1],p[1][n_points-1]},{p_r[0][n_points-1],p_r[1][n_points-1]}};
1045 public boolean hasPoints() {
1046 return 0 != n_points;
1049 protected void setPoints(final double[][] p_l, final double[][] p, final double[][] p_r) {
1050 this.p_l = p_l;
1051 this.p = p;
1052 this.p_r = p_r;
1053 this.n_points = p_l[0].length;
1054 this.generateInterpolatedPoints(0.05);
1057 public void setPoints(final double[][] p_l, final double[][] p, final double[][] p_r, final boolean update) {
1058 setPoints(p_l, p, p_r);
1059 calculateBoundingBox();
1060 if (update) {
1061 updateInDatabase("points");
1062 repaint(true);
1066 protected void addPointsAtBegin(final double[][] new_p_l, final double[][] new_p, final double[][] new_p_r) {
1067 final double[][] tmp_p_l = new double[2][p_l[0].length + new_p_l[0].length];
1068 final double[][] tmp_p = new double[2][p[0].length + new_p[0].length];
1069 final double[][] tmp_p_r = new double[2][p_r[0].length + new_p_r[0].length];
1070 int i = 0;
1071 for (; i < new_p_l[0].length; i++) {
1072 tmp_p_l[0][i] = new_p_l[0][i];
1073 tmp_p_l[1][i] = new_p_l[1][i];
1074 tmp_p[0][i] = new_p[0][i];
1075 tmp_p[1][i] = new_p[1][i];
1076 tmp_p_r[0][i] = new_p_r[0][i];
1077 tmp_p_r[1][i] = new_p_r[1][i];
1079 for (int j = 0; j < n_points; j++, i++) {
1080 tmp_p_l[0][i] = p_l[0][j];
1081 tmp_p_l[1][i] = p_l[1][j];
1082 tmp_p[0][i] = p[0][j];
1083 tmp_p[1][i] = p[1][j];
1084 tmp_p_r[0][i] = p_r[0][j];
1085 tmp_p_r[1][i] = p_r[1][j];
1087 this.n_points += new_p_l[0].length;
1089 p_l = tmp_p_l;
1090 p = tmp_p;
1091 p_r = tmp_p_r;
1092 this.generateInterpolatedPoints(0.05);
1095 protected void addPointsAtEnd(final double[][] new_p_l, final double[][] new_p, final double[][] new_p_r) {
1096 final double[][] tmp_p_l = new double[2][p_l[0].length + new_p_l[0].length];
1097 final double[][] tmp_p = new double[2][p[0].length + new_p[0].length];
1098 final double[][] tmp_p_r = new double[2][p_r[0].length + new_p_r[0].length];
1099 int i = 0;
1100 for (; i < n_points; i++) {
1101 tmp_p_l[0][i] = p_l[0][i];
1102 tmp_p_l[1][i] = p_l[1][i];
1103 tmp_p[0][i] = p[0][i];
1104 tmp_p[1][i] = p[1][i];
1105 tmp_p_r[0][i] = p_r[0][i];
1106 tmp_p_r[1][i] = p_r[1][i];
1109 for (int j = 0; j < new_p_l[0].length; i++,j++) {
1110 tmp_p_l[0][i] = new_p_l[0][j];
1111 tmp_p_l[1][i] = new_p_l[1][j];
1112 tmp_p[0][i] = new_p[0][j];
1113 tmp_p[1][i] = new_p[1][j];
1114 tmp_p_r[0][i] = new_p_r[0][j];
1115 tmp_p_r[1][i] = new_p_r[1][j];
1117 this.n_points += new_p_l[0].length;
1119 p_l = tmp_p_l;
1120 p = tmp_p;
1121 p_r = tmp_p_r;
1122 this.generateInterpolatedPoints(0.05);
1125 public int getNearestPointIndex(final double x_p, final double y_p) {
1126 int ret = -1;
1127 double minDist = Double.POSITIVE_INFINITY;
1128 for (int i = 0; i < this.n_points; i++) {
1129 final double dx = this.p[0][i]-x_p;
1130 final double dy = this.p[1][i]-y_p;
1131 final double dist = dx*dx+dy*dy;
1132 if(dist < minDist)
1134 minDist = dist;
1135 ret = i;
1138 return ret;
1141 public void insertBetween(int startIndex, int endIndex, final double[][] tmp_p_l, final double[][] tmp_p, final double[][] tmp_p_r){
1142 if(endIndex < startIndex)
1144 for (int i = 0; i < 2; i++) {
1145 for (int j = 0; j < tmp_p[0].length/2; j++) {
1146 final double tmppl = tmp_p_l[i][j];
1147 final double tmpp = tmp_p[i][j];
1148 final double tmppr = tmp_p_r[i][j];
1150 tmp_p_r[i][j] = tmp_p_l[i][tmp_p_l[0].length -1- j];
1151 tmp_p[i][j] = tmp_p[i][tmp_p[0].length -1- j];
1152 tmp_p_l[i][j] = tmp_p_r[i][tmp_p_r[0].length -1- j];
1154 tmp_p_r[i][tmp_p_l[0].length -1- j]=tmppl;
1155 tmp_p[i][tmp_p[0].length -1- j]=tmpp;
1156 tmp_p_l[i][tmp_p_r[0].length -1- j]=tmppr;
1160 final int tmp = startIndex;
1161 startIndex = endIndex;
1162 endIndex = tmp;
1166 double[][] beginning_p_l;
1167 double[][] beginning_p;
1168 double[][] beginning_p_r;
1170 double[][] ending_p_l;
1171 double[][] ending_p;
1172 double[][] ending_p_r;
1174 if(endIndex - startIndex < n_points + startIndex - endIndex || closed == false)
1176 beginning_p_l = new double [2][startIndex+1];
1177 beginning_p = new double [2][startIndex+1];
1178 beginning_p_r = new double [2][startIndex+1];
1180 ending_p_l = new double [2][n_points- endIndex];
1181 ending_p = new double [2][n_points- endIndex];
1182 ending_p_r = new double [2][n_points- endIndex];
1184 for(int i = 0 ; i <= startIndex ; i++)
1186 for (int j = 0; j < 2; j++) {
1187 beginning_p_l [j][i] = this.p_l[j][i];
1188 beginning_p [j][i] = this.p[j][i];
1189 beginning_p_r [j][i] = this.p_r[j][i];
1192 for(int i = endIndex ; i < this.n_points ; i++)
1194 for (int j = 0; j < 2; j++) {
1195 ending_p_l [j][i-endIndex] = this.p_l[j][i];
1196 ending_p [j][i-endIndex] = this.p[j][i];
1197 ending_p_r [j][i-endIndex] = this.p_r[j][i];
1200 System.out.println("1");
1202 else
1204 beginning_p_l = new double [2][endIndex-startIndex + 1];
1205 beginning_p = new double [2][ endIndex-startIndex + 1 ];
1206 beginning_p_r = new double [2][endIndex-startIndex + 1];
1208 ending_p_l = new double [2][0];
1209 ending_p = new double [2][0];
1210 ending_p_r = new double [2][0];
1212 for(int i = startIndex ; i <= endIndex ; i++)
1214 for (int j = 0; j < 2; j++) {
1215 beginning_p_r [j][endIndex - i] = this.p_l[j][i];
1216 beginning_p [j][endIndex - i] = this.p[j][i];
1217 beginning_p_l [j][endIndex - i] = this.p_r[j][i];
1221 System.out.println("2");
1227 final double[][] new_p_l = new double[2][beginning_p_l[0].length + ending_p_l[0].length + tmp_p_l[0].length];
1228 final double[][] new_p = new double[2][beginning_p[0].length + ending_p[0].length + tmp_p[0].length];
1229 final double[][] new_p_r = new double[2][beginning_p_r[0].length + ending_p_r[0].length + tmp_p_r[0].length];
1231 for (int i = 0; i < beginning_p[0].length; i++) {
1232 for (int j = 0; j < 2; j++) {
1233 new_p_l[j][i] = beginning_p_l[j][i];
1234 new_p[j][i] = beginning_p[j][i];
1235 new_p_r[j][i] = beginning_p_r[j][i];
1238 for (int i = 0; i < tmp_p[0].length; i++) {
1239 for (int j = 0; j < 2; j++) {
1240 new_p_l[j][i+beginning_p[0].length] = tmp_p_l[j][i];
1241 new_p[j][i+beginning_p[0].length] = tmp_p[j][i];
1242 new_p_r[j][i+beginning_p[0].length] = tmp_p_r[j][i];
1245 for (int i = 0; i < ending_p[0].length; i++) {
1246 for (int j = 0; j < 2; j++) {
1247 new_p_l[j][i+beginning_p[0].length+tmp_p[0].length] = ending_p_l[j][i];
1248 new_p[j][i+beginning_p[0].length+tmp_p[0].length] = ending_p[j][i];
1249 new_p_r[j][i+beginning_p[0].length+tmp_p[0].length] = ending_p_r[j][i];
1252 this.n_points = new_p[0].length;
1253 this.p_l = new_p_l;
1254 this.p = new_p;
1255 this.p_r = new_p_r;
1256 this.calculateBoundingBox();
1257 this.generateInterpolatedPoints(0.05);
1260 public void printPoints() {
1261 System.out.println("#####\nw,h: " + width + "," + height);
1262 for (int i=0; i<n_points; i++) {
1263 System.out.println("x,y: " + p[0][i] + " , " + p[1][i]);
1265 System.out.println("\n");
1269 /** x,y is the cursor position in offscreen coordinates. */ // COPIED from the Pipe
1270 @Override
1271 public void snapTo(final int cx, final int cy, final int x_p, final int y_p) { // WARNING: DisplayCanvas is locking at mouseDragged when the cursor is outside the DisplayCanvas Component, so this is useless or even harmful at the moment.
1272 if (-1 != index) {
1273 // #$#@$%#$%!!! TODO this doesn't work, although it *should*. The index_l and index_r work, and the mouseEntered coordinates are fine too. Plus it messes up the x,y position or something, for then on reload the pipe is streched or displaced (not clear).
1275 double dx = p_l[0][index] - p[0][index];
1276 double dy = p_l[1][index] - p[1][index];
1277 p_l[0][index] = cx + dx;
1278 p_l[1][index] = cy + dy;
1279 dx = p_r[0][index] - p[0][index];
1280 dy = p_r[1][index] - p[1][index];
1281 p_r[0][index] = cx + dx;
1282 p_r[1][index] = cy + dy;
1283 p[0][index] = cx;
1284 p[1][index] = cy;
1286 } else if (-1 != index_l) {
1287 p_l[0][index_l] = cx;
1288 p_l[1][index_l] = cy;
1289 } else if (-1 != index_r) {
1290 p_r[0][index_r] = cx;
1291 p_r[1][index_r] = cy;
1292 } else {
1293 // drag the whole pipe
1294 // CONCEPTUALLY WRONG, what happens when not dragging the pipe, on mouseEntered? Disaster!
1295 //drag(cx - x_p, cy - y_p);
1299 @Override
1300 public void exportXML(final StringBuilder sb_body, final String indent, final XMLOptions options) {
1301 sb_body.append(indent).append("<t2_profile\n");
1302 final String in = indent + "\t";
1303 super.exportXML(sb_body, in, options);
1304 if (-1 == n_points) setupForDisplay(); // reload
1305 final String[] RGB = Utils.getHexRGBColor(color);
1306 sb_body.append(in).append("style=\"fill:none;stroke-opacity:").append(alpha).append(";stroke:#").append(RGB[0]).append(RGB[1]).append(RGB[2]).append(";stroke-width:1.0px;\"\n");
1307 if (n_points > 0) {
1308 sb_body.append(in).append("d=\"M");
1309 for (int i=0; i<n_points-1; i++) {
1310 sb_body.append(' ').append(p[0][i]).append(',').append(p[1][i])
1311 .append(" C ").append(p_r[0][i]).append(',').append(p_r[1][i])
1312 .append(' ').append(p_l[0][i+1]).append(',').append(p_l[1][i+1])
1315 sb_body.append(' ').append(p[0][n_points-1]).append(',').append(p[1][n_points-1]);
1316 if (closed) {
1317 sb_body.append(" C ").append(p_r[0][n_points-1]).append(',').append(p_r[1][n_points-1])
1318 .append(' ').append(p_l[0][0]).append(',').append(p_l[1][0])
1319 .append(' ').append(p[0][0]).append(',').append(p[1][0])
1320 .append(" z")
1323 sb_body.append("\"\n");
1325 sb_body.append(indent).append(">\n");
1326 super.restXML(sb_body, in, options);
1327 sb_body.append(indent).append("</t2_profile>\n");
1330 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
1331 final String type = "t2_profile";
1332 if (hs.contains(type)) return;
1333 hs.add(type);
1334 sb_header.append(indent).append("<!ELEMENT t2_profile (").append(Displayable.commonDTDChildren()).append(")>\n");
1335 Displayable.exportDTD(type, sb_header, hs, indent);
1336 sb_header.append(indent).append(TAG_ATTR1).append(type).append(" d").append(TAG_ATTR2)
1340 /** Returns the interpolated points as a VectorString2D, calibrated.
1341 * Returns null if there aren't any points. */
1342 public VectorString2D getPerimeter2D() {
1343 return getPerimeter2D(layer.getParent().getCalibration());
1345 private VectorString2D getPerimeter2D(final Calibration cal) {
1346 if (-1 == n_points) setupForDisplay();
1347 if (0 == n_points) return null;
1348 if (0 == p_i[0].length) generateInterpolatedPoints(0.05);
1349 final double[][] pi = transformPoints(p_i);
1350 VectorString2D sv = null;
1351 try {
1352 sv = new VectorString2D(pi[0], pi[1], this.layer.getZ(), this.closed);
1353 } catch (final Exception e) {
1354 IJError.print(e);
1356 if (null != cal) sv.calibrate(cal);
1357 return sv;
1360 @Override
1361 public void keyPressed(final KeyEvent ke) {
1362 super.keyPressed(ke);
1363 if (ke.isConsumed()) return;
1364 final int key_code = ke.getKeyCode();
1365 Rectangle box = null;
1366 switch(key_code) {
1367 case KeyEvent.VK_X: // remove all points
1368 if (0 == ke.getModifiers() && (ProjectToolbar.getToolId() == ProjectToolbar.PEN || ProjectToolbar.getToolId() == ProjectToolbar.PENCIL)) {
1369 box = getBoundingBox(box);
1370 n_points = 0;
1371 this.p_i = new double[2][0];
1372 calculateBoundingBox(true);
1373 ke.consume();
1374 if (closed) toggleClosed();
1375 updateInDatabase("points");
1377 break;
1378 case KeyEvent.VK_C: // toggle close with shift+c
1379 if (0 == (ke.getModifiers() ^ java.awt.Event.SHIFT_MASK)) {
1380 //preconditions: at least 2 points!
1381 if (n_points > 1) {
1382 toggleClosed();
1383 generateInterpolatedPoints(0.05);
1384 ke.consume();
1387 break;
1389 if (ke.isConsumed()) {
1390 Display.repaint(this.layer, box, 5);
1394 @Override
1395 public void setColor(final Color c) {
1396 // propagate to all linked profiles within the same profile_list
1397 setColor(c, new HashSet<Profile>());
1400 /** Exploits the fact that Profile instances among the directly linked as returned by getLinked(Profile.class) will be members of the same profile_list. */
1401 private void setColor(final Color c, final HashSet<Profile> hs_done) {
1402 if (hs_done.contains(this)) return;
1403 hs_done.add(this);
1404 super.setColor(c);
1405 final HashSet<Displayable> hs = getLinked(Profile.class);
1406 if (null != hs) {
1407 for (final Iterator<Displayable> it = hs.iterator(); it.hasNext(); ) {
1408 final Profile p = (Profile)it.next();
1409 p.setColor(c, hs_done);
1414 /** Performs a deep copy of this object, unlocked and visible. */
1415 @Override
1416 public Displayable clone(final Project pr, final boolean copy_id) {
1417 final long nid = copy_id ? this.id : pr.getLoader().getNextId();
1418 final Profile copy = new Profile(pr, nid, null != title ? title.toString() : null, width, height, alpha, this.visible, new Color(color.getRed(), color.getGreen(), color.getBlue()), closed, this.locked, (AffineTransform)this.at.clone());
1419 // The data:
1420 if (-1 == n_points) setupForDisplay(); // load data
1421 copy.n_points = n_points;
1422 copy.p = new double[][]{(double[])this.p[0].clone(), (double[])this.p[1].clone()};
1423 copy.p_l = new double[][]{(double[])this.p_l[0].clone(), (double[])this.p_l[1].clone()};
1424 copy.p_r = new double[][]{(double[])this.p_r[0].clone(), (double[])this.p_r[1].clone()};
1425 copy.p_i = new double[][]{(double[])this.p_i[0].clone(), (double[])this.p_i[1].clone()};
1426 // add
1427 copy.addToDatabase();
1429 return copy;
1432 private Object[] getTransformedData() {
1433 final double[][] p = transformPoints(this.p);
1434 final double[][] p_l = transformPoints(this.p_l);
1435 final double[][] p_r = transformPoints(this.p_r);
1436 final double[][] p_i = transformPoints(this.p_i);
1437 return new Object[]{p, p_l, p_r, p_i};
1440 /** Takes a profile_list, scans for its Profile children, makes sublists of continuous profiles (if they happen to be branched), and then creates triangles for them using weighted vector strings. */
1441 static public List<Point3f> generateTriangles(final ProjectThing pt, final double scale) {
1442 if (!pt.getType().equals("profile_list")) {
1443 Utils.log2("Profile: ignoring unhandable ProjectThing type.");
1444 return null;
1446 final ArrayList<ProjectThing> al = pt.getChildren(); // should be sorted by Z already
1447 if (al.size() < 2) {
1448 Utils.log("profile_list " + pt + " has less than two profiles: can't render in 3D.");
1449 return null;
1451 // collect all Profile
1452 final HashSet<Profile> hs = new HashSet<Profile>();
1453 for (final ProjectThing child : al) {
1454 final Object ob = child.getObject();
1455 if (ob instanceof Profile) {
1456 hs.add((Profile)ob);
1457 } else {
1458 Utils.log2("Render: skipping non Profile class child");
1461 // Create sublists of profiles, following the chain of links.
1462 final Profile[] p = new Profile[hs.size()];
1463 hs.toArray(p);
1464 // find if at least one is visible
1465 boolean hidden = true;
1466 for (int i=0; i<p.length; i++) {
1467 if (p[i].visible) {
1468 hidden = false;
1469 break;
1471 if (null == p[i] || 0 == p[i].n_points) {
1472 Utils.log("Cannot generate triangle mesh: empty profile " + p[i] + (null != p[i] ? " at layer " + p[i].getLayer() : ""));
1473 return null;
1476 if (hidden) return null;
1477 // collect starts and ends
1478 final HashSet<Profile> hs_bases = new HashSet<Profile>();
1479 final HashSet<Profile> hs_done = new HashSet<Profile>();
1480 final List<Point3f> triangles = new ArrayList<Point3f>();
1481 do {
1482 Profile base = null;
1483 // choose among existing bases
1484 if (hs_bases.size() > 0) {
1485 base = hs_bases.iterator().next();
1486 } else {
1487 // find a new base, simply by taking the lowest Z or remaining profiles
1488 double min_z = Double.MAX_VALUE;
1489 for (int i=0; i<p.length; i++) {
1490 if (hs_done.contains(p[i])) continue;
1491 final double z = p[i].getLayer().getZ();
1492 if (z < min_z) {
1493 min_z = z;
1494 base = p[i];
1497 // add base
1498 if (null != base) hs_bases.add(base);
1500 if (null == base) {
1501 Utils.log2("No more bases.");
1502 break;
1504 // crawl list to get a sequence of profiles in increasing or decreasing Z order, but not mixed z trends
1505 final ArrayList<Profile> al_profiles = new ArrayList<Profile>();
1506 //Utils.log2("Calling accumulate for base " + base);
1507 al_profiles.add(base);
1508 final Profile last = accumulate(hs_done, al_profiles, base, 0);
1509 // if the trend was not empty, add it
1510 if (last != base) {
1511 // count as done
1512 hs_done.addAll(al_profiles);
1513 // add new possible base (which may have only 2 links if it was from a broken Z trend)
1514 hs_bases.add(last);
1515 // create 3D object from base to base
1516 final Profile[] profiles = new Profile[al_profiles.size()];
1517 al_profiles.toArray(profiles);
1518 final List<Point3f> tri = makeTriangles(profiles, scale);
1519 if (null != tri) triangles.addAll(tri);
1520 } else {
1521 // remove base
1522 hs_bases.remove(base);
1524 } while (0 != hs_bases.size());
1526 return triangles;
1529 /** Recursive; returns the last added profile. */
1530 static private Profile accumulate(final HashSet<Profile> hs_done, final ArrayList<Profile> al, final Profile step, int z_trend) {
1531 final HashSet<Displayable> hs_linked = step.getLinked(Profile.class);
1532 if (al.size() > 1 && hs_linked.size() > 2) {
1533 // base found
1534 return step;
1536 final double step_z = step.getLayer().getZ();
1537 Profile next_step = null;
1538 boolean started = false;
1539 for (final Iterator<Displayable> it = hs_linked.iterator(); it.hasNext(); ) {
1540 final Object ob = it.next();
1541 // loop only one cycle, to move only in one direction
1542 if (al.contains(ob) || started || hs_done.contains(ob)) continue;
1543 started = true;
1544 next_step = (Profile)ob;
1545 final double next_z = next_step.getLayer().getZ();
1546 if (0 == z_trend) {
1547 // define trend
1548 if (next_z > step_z) {
1549 z_trend = 1;
1550 } else {
1551 z_trend = -1;
1553 // add!
1554 al.add(next_step);
1555 } else {
1556 // if the z trend is broken, finish
1557 if ( (next_z > step_z && 1 == z_trend)
1558 || (next_z < step_z && -1 == z_trend) ) {
1559 // z trend continues
1560 al.add(next_step);
1561 } else {
1562 // z trend broken
1563 next_step = null;
1567 Profile last = step;
1568 if (null != next_step) {
1569 hs_done.add(next_step);
1570 last = accumulate(hs_done, al, next_step, z_trend);
1572 return last;
1575 /** Make a mesh as a calibrated list of 3D triangles.*/
1576 static private List<Point3f> makeTriangles(final Profile[] p, final double scale) {
1577 try {
1578 final VectorString2D[] sv = new VectorString2D[p.length];
1579 boolean closed = true; // dummy initialization
1580 final Calibration cal = p[0].getLayerSet().getCalibrationCopy();
1581 cal.pixelWidth *= scale;
1582 cal.pixelHeight *= scale;
1583 for (int i=0; i<p.length; i++) {
1584 if (0 == p[i].n_points) continue;
1585 if (0 == i) closed = p[i].closed;
1586 else if (p[i].closed != closed) {
1587 Utils.log2("All profiles should be either open or closed, not mixed.");
1588 return null;
1590 sv[i] = p[i].getPerimeter2D(cal);
1592 return SkinMaker.generateTriangles(sv, -1, -1, closed);
1593 } catch (final Exception e) {
1594 IJError.print(e);
1596 return null;
1599 /** Does nothing. */
1600 @Override
1601 public boolean softRemove() {
1602 return true;
1605 @Override
1606 protected boolean remove2(final boolean check) {
1607 return project.getProjectTree().remove(check, project.findProjectThing(this), null); // will call remove(check) here
1610 /** Calibrated for pixel width only (that is, it assumes pixel aspect ratio 1:1), in units as specified at getLayerSet().getCalibration().getUnit() */
1611 public double computeLength() {
1612 if (-1 == n_points || 0 == this.p_i[0].length) setupForDisplay();
1613 if (this.p_i[0].length < 2) return 0;
1614 final double[][] p_i = transformPoints(this.p_i);
1615 double len = 0;
1616 for (int i=1; i<p_i[0].length; i++) {
1617 len += Math.sqrt(Math.pow(p_i[0][i] - p_i[0][i-1], 2) + Math.pow(p_i[1][i] - p_i[1][i-1], 2));
1619 if (closed) {
1620 final int last = p[0].length -1;
1621 len += Math.sqrt(Math.pow(p_i[0][last] - p_i[0][0], 2) + Math.pow(p_i[1][last] - p_i[1][0], 2));
1623 // to calibrate for pixelWidth and pixelHeight, I'd have to multiply each x,y values above separately
1624 return len * getLayerSet().getCalibration().pixelWidth;
1627 /** Calibrated, in units as specified at getLayerSet().getCalibration().getUnit() -- returns zero if this profile is not closed. */
1628 public double computeArea() {
1629 if (-1 == n_points) setupForDisplay();
1630 if (n_points < 2) return 0;
1631 if (!closed) return 0;
1632 if (0 == p_i[0].length) generateInterpolatedPoints(0.05);
1633 final Calibration cal = getLayerSet().getCalibration();
1634 return M.measureArea(new Area(getPerimeter()), getProject().getLoader()) * cal.pixelWidth * cal.pixelHeight;
1637 /** Measures the calibrated length, the lateral surface as the length times the layer thickness, and the volume (if closed) as the area times the layer thickness. */
1638 @Override
1639 public ResultsTable measure(ResultsTable rt) {
1640 if (null == rt) rt = Utils.createResultsTable("Profile results", new String[]{"id", "length", "side surface: length x thickness", "volume: area x thickness", "name-id"});
1641 if (-1 == n_points) setupForDisplay();
1642 if (n_points < 2) return null;
1643 if (0 == p_i[0].length) generateInterpolatedPoints(0.05);
1644 final Calibration cal = getLayerSet().getCalibration();
1645 // computeLength returns a calibrated length, so only calibrate the layer thickness:
1646 final double len = computeLength();
1647 final double surface_flat = len * layer.getThickness() * cal.pixelWidth;
1648 rt.incrementCounter();
1649 rt.addLabel("units", cal.getUnit());
1650 rt.addValue(0, id);
1651 rt.addValue(1, len);
1652 rt.addValue(2, surface_flat);
1653 final double volume = closed ? computeArea() * layer.getThickness() * cal.pixelWidth : 0;
1654 rt.addValue(3, volume);
1655 rt.addValue(4, getNameId());
1656 return rt;
1659 /** Assumes Z-coord sorted list of profiles, as stored in a "profile_list" ProjectThing type. . */
1660 static public ResultsTable measure(final Profile[] profiles, ResultsTable rt, final long profile_list_id) {
1661 Utils.log2("profiles.length" + profiles.length);
1662 if (null == profiles || 0 == profiles.length) return null;
1663 if (1 == profiles.length) {
1664 // don't measure if there is only one
1665 return rt;
1667 for (final Profile p : profiles) {
1668 if (null == p || 0 == p.n_points) {
1669 Utils.log("Cannot measure: empty profile " + p + (null != p ? " at layer " + p.getLayer() : ""));
1670 return rt;
1673 if (null == rt) rt = Utils.createResultsTable("Profile list results", new String[]{"id", "interpolated surface", "surface: sum of length x thickness", "volume", "name-id"});
1674 final Calibration cal = profiles[0].getLayerSet().getCalibration();
1675 // else, interpolate skin and measure each triangle
1676 final List<Point3f> tri = makeTriangles(profiles, 1.0); // already calibrated
1677 final int n_tri = tri.size();
1678 if (0 != n_tri % 3) {
1679 Utils.log("Profile.measure error: triangle verts list not a multiple of 3 for profile list id " + profile_list_id);
1680 return rt;
1682 // Surface: calibrated sum of the area of all triangles in the mesh.
1683 double surface = 0;
1684 for (int i=2; i<n_tri; i+=3) {
1685 surface += M.measureArea(tri.get(i-2), tri.get(i-1), tri.get(i));
1687 // add capping ends
1688 final double area_first = profiles[0].computeArea();
1689 final double area_last = profiles[profiles.length-1].computeArea();
1690 if (profiles[0].closed) surface += area_first;
1691 if (profiles[profiles.length-1].closed) surface += area_last;
1693 // Surface flat: sum of the perimeter lengths times the layer thickness
1694 double surface_flat = 0;
1695 for (int i=0; i<profiles.length; i++) {
1696 if (0 == profiles[i].p_i[0].length) profiles[i].generateInterpolatedPoints(0.05);
1697 surface_flat += profiles[i].computeLength() * profiles[i].layer.getThickness() * cal.pixelWidth;
1700 // Volume: area times layer thickness
1701 double volume = area_first * profiles[0].layer.getThickness();
1702 for (int i=1; i<profiles.length-1; i++) {
1703 volume += profiles[i].computeArea() * profiles[i].layer.getThickness();
1705 volume += area_last * profiles[profiles.length-1].layer.getThickness();
1707 // calibrate volume: the Z is still in pixels
1708 volume *= cal.pixelWidth;
1710 rt.incrementCounter();
1711 rt.addLabel("units", cal.getUnit());
1712 rt.addValue(0, profile_list_id);
1713 rt.addValue(1, surface);
1714 rt.addValue(2, surface_flat);
1715 rt.addValue(3, volume);
1716 double nameid = 0;
1717 try {
1718 nameid = Double.parseDouble(profiles[0].project.findProjectThing(profiles[0]).getParent().getTitle());
1719 } catch (final NumberFormatException nfe) {}
1720 rt.addValue(4, nameid);
1721 return rt;
1724 @Override
1725 final Class<?> getInternalDataPackageClass() {
1726 return DPProfile.class;
1729 @Override
1730 synchronized Object getDataPackage() {
1731 return new DPProfile(this);
1734 static private final class DPProfile extends Displayable.DataPackage {
1735 final double[][] p, p_l, p_r, p_i;
1736 final boolean closed;
1738 DPProfile(final Profile profile) {
1739 super(profile);
1740 // store copies of all arrays
1741 this.p = new double[][]{Utils.copy(profile.p[0], profile.n_points), Utils.copy(profile.p[1], profile.n_points)};
1742 this.p_r = new double[][]{Utils.copy(profile.p_r[0], profile.n_points), Utils.copy(profile.p_r[1], profile.n_points)};
1743 this.p_l = new double[][]{Utils.copy(profile.p_l[0], profile.n_points), Utils.copy(profile.p_l[1], profile.n_points)};
1744 this.p_i = new double[][]{Utils.copy(profile.p_i[0], profile.p_i[0].length), Utils.copy(profile.p_i[1], profile.p_i[0].length)};
1745 this.closed = profile.closed;
1747 @Override
1748 final boolean to2(final Displayable d) {
1749 super.to1(d);
1750 final Profile profile = (Profile)d;
1751 final int len = p[0].length; // == n_points, since it was cropped on copy
1752 profile.p = new double[][]{Utils.copy(p[0], len), Utils.copy(p[1], len)};
1753 profile.n_points = p[0].length;
1754 profile.p_r = new double[][]{Utils.copy(p_r[0], len), Utils.copy(p_r[1], len)};
1755 profile.p_l = new double[][]{Utils.copy(p_l[0], len), Utils.copy(p_l[1], len)};
1756 profile.p_i = new double[][]{Utils.copy(p_i[0], p_i[0].length), Utils.copy(p_i[1], p_i[1].length)};
1757 profile.closed = closed;
1758 return true;
1762 // It's such a pitty that this code is almost identical to that of the Pipe, and it can't be abstracted effectively any further.
1763 @Override
1764 synchronized public boolean apply(final Layer la, final Area roi, final mpicbg.models.CoordinateTransform ict) throws Exception {
1765 if (this.layer != la) return true;
1766 double[] fp = null;
1767 mpicbg.models.CoordinateTransform chain = null;
1768 Area localroi = null;
1769 AffineTransform inverse = null;
1770 for (int i=0; i<n_points; i++) {
1771 if (null == localroi) {
1772 inverse = this.at.createInverse();
1773 localroi = roi.createTransformedArea(inverse);
1775 if (localroi.contains(p[0][i], p[1][i])) {
1776 if (null == chain) {
1777 chain = M.wrap(this.at, ict, inverse);
1778 fp = new double[2];
1780 // The point and its two associated control points:
1781 M.apply(chain, p, i, fp);
1782 M.apply(chain, p_l, i, fp);
1783 M.apply(chain, p_r, i, fp);
1786 if (null != chain) {
1787 generateInterpolatedPoints(0.05);
1788 calculateBoundingBox(true);
1790 return true;
1792 @Override
1793 public boolean apply(final VectorDataTransform vdt) throws Exception {
1794 if (vdt.layer != this.layer) return false;
1795 final double[] fp = new double[2];
1796 final VectorDataTransform vlocal = vdt.makeLocalTo(this);
1797 for (int i=0; i<n_points; i++) {
1798 for (final VectorDataTransform.ROITransform rt : vlocal.transforms) {
1799 if (rt.roi.contains(p[0][i], p[1][i])) {
1800 // The point and its two associated control points:
1801 M.apply(rt.ct, p, i, fp);
1802 M.apply(rt.ct, p_l, i, fp);
1803 M.apply(rt.ct, p_r, i, fp);
1804 break;
1808 generateInterpolatedPoints(0.05);
1809 calculateBoundingBox(true);
1810 return true;
1813 @Override
1814 synchronized public boolean isRoughlyInside(final Layer layer, final Rectangle r) {
1815 if (this.layer != layer) return false;
1816 try {
1817 final Rectangle box = this.at.createInverse().createTransformedShape(r).getBounds();
1818 for (int i=0; i<n_points; i++) {
1819 if (box.contains(p[0][i], p[1][i])) return true;
1821 } catch (final NoninvertibleTransformException e) {
1822 IJError.print(e);
1824 return false;