3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005, 2006 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.
23 package ini
.trakem2
.display
;
26 import ij
.gui
.GenericDialog
;
29 import ini
.trakem2
.ControlWindow
;
30 import ini
.trakem2
.Project
;
31 import ini
.trakem2
.persistence
.DBObject
;
32 import ini
.trakem2
.tree
.LayerThing
;
33 import ini
.trakem2
.utils
.IJError
;
34 import ini
.trakem2
.utils
.Utils
;
36 import java
.util
.ArrayList
;
37 import java
.util
.List
;
38 import java
.util
.Collection
;
39 import java
.util
.HashMap
;
40 import java
.util
.HashSet
;
42 import java
.util
.Iterator
;
43 import java
.util
.TreeMap
;
44 import java
.util
.Collection
;
46 import java
.awt
.Color
;
47 import java
.awt
.Rectangle
;
48 import java
.awt
.geom
.AffineTransform
;
49 import java
.awt
.geom
.Area
;
50 import java
.awt
.Image
;
52 public final class Layer
extends DBObject
implements Bucketable
{
54 private final ArrayList
<Displayable
> al_displayables
= new ArrayList
<Displayable
>();
55 /** For fast search. */
57 private HashMap
<Displayable
,ArrayList
<Bucket
>> db_map
= null;
60 private double thickness
;
62 private LayerSet parent
;
64 public Layer(Project project
, double z
, double thickness
, LayerSet parent
) {
67 this.thickness
= thickness
;
72 /** Reconstruct from database*/
73 public Layer(Project project
, long id
, double z
, double thickness
) {
76 this.thickness
= thickness
;
80 /** Reconstruct from XML file. */
81 public Layer(Project project
, long id
, HashMap ht_attributes
) {
85 for (Iterator it
= ht_attributes
.entrySet().iterator(); it
.hasNext(); ) {
86 Map
.Entry entry
= (Map
.Entry
)it
.next();
87 String key
= (String
)entry
.getKey();
88 String data
= (String
)entry
.getValue();
89 if (key
.equals("z")) {
90 this.z
= Double
.parseDouble(data
);
91 } else if (key
.equals("thickness")) {
92 this.thickness
= Double
.parseDouble(data
);
94 // all the above could have been done with reflection since the fields have the same name
98 /** Creates a new Layer asking for z and thickness, and adds it to the parent and returns it. Returns null if the dialog was canceled.*/
99 static public Layer
create(Project project
, LayerSet parent
) {
100 if (null == parent
) return null;
101 GenericDialog gd
= ControlWindow
.makeGenericDialog("New Layer");
102 gd
.addMessage("In pixels:"); // TODO set elsewhere the units!
103 gd
.addNumericField("z coordinate: ", 0.0D
, 3);
104 gd
.addNumericField("thickness: ", 1.0D
, 3);
106 if (gd
.wasCanceled()) return null;
108 double z
= gd
.getNextNumber();
109 double thickness
= gd
.getNextNumber();
110 if (Double
.isNaN(z
) || Double
.isNaN(thickness
)) return null;
111 Layer layer
= new Layer(project
, z
, thickness
, parent
);
114 } catch (Exception e
) {}
118 static public Layer
[] createMany(Project project
, LayerSet parent
) {
119 if (null == parent
) return null;
120 GenericDialog gd
= ControlWindow
.makeGenericDialog("Many new layers");
121 gd
.addNumericField("First Z coord: ", 0, 3);
122 gd
.addNumericField("thickness: ", 1.0, 3);
123 gd
.addNumericField("Number of layers: ", 1, 0);
124 gd
.addCheckbox("Skip existing layers", true);
126 if (gd
.wasCanceled()) return null;
127 // start iteration to add layers
128 boolean skip
= gd
.getNextBoolean();
129 double z
= gd
.getNextNumber();
130 double thickness
= gd
.getNextNumber();
131 int n_layers
= (int)gd
.getNextNumber();
133 Utils
.showMessage("Can't create layers with negative thickness");
137 Utils
.showMessage("Invalid number of layers");
140 Layer
[] layer
= new Layer
[n_layers
];
141 for (int i
=0; i
<n_layers
; i
++) {
143 layer
[i
] = parent
.getLayer(z
);
144 if (null == layer
[i
]) {
145 layer
[i
] = new Layer(project
, z
, thickness
, parent
);
146 parent
.addSilently(layer
[i
]);
147 } else layer
[i
] = null; // don't create
149 layer
[i
] = new Layer(project
, z
, thickness
, parent
);
150 parent
.addSilently(layer
[i
]);
154 // update the scroller of currently open Displays
155 Display
.updateLayerScroller(parent
);
159 /** Returns a title such as 018__4-K4_2__z1.67 , which is [layer_set index]__[title]__[z coord] . The ordinal number starts at 1 and finishes at parent's length, inclusive. */
160 public String
getPrintableTitle() {
161 final LayerThing lt
= project
.findLayerThing(this);
162 if (null == lt
) return toString();
163 String title
= lt
.getTitle();
164 if (null == title
) title
= "";
165 else title
= title
.replace(' ', '_');
166 final StringBuffer sb
= new StringBuffer().append(parent
.indexOf(this) + 1);
167 final int s_size
= Integer
.toString(parent
.size()).length();
168 while (sb
.length() < s_size
) {
171 sb
.append('_').append('_').append(title
).append('_').append('_').append('z').append(Utils
.cutNumber(this.z
, 3, true));
172 return sb
.toString();
175 public String
toString() {
176 if (null == parent
) return new StringBuffer("z=").append(Utils
.cutNumber(z
, 4)).toString();
177 //return "z=" + Utils.cutNumber(z / parent.getCalibration().pixelDepth * z !!!?? I don't have the actual depth to correct with.
178 return "z=" + Utils
.cutNumber(z
, 4);
181 /** Add a displayable and update all Display instances showing this Layer. */
182 public void add(Displayable displ
) { add(displ
, true); }
184 public void add(Displayable displ
, boolean update_displays
) {
185 add(displ
, update_displays
, true);
188 public void add(final Displayable displ
, final boolean update_displays
, final boolean update_db
) {
189 if (null == displ
|| -1 != al_displayables
.indexOf(displ
)) return;
192 final Displayable
[] d
= new Displayable
[al_displayables
.size()];
193 al_displayables
.toArray(d
);
196 if (displ
instanceof Patch
) {
197 // find last Patch (which start at 0)
198 for (i
=0; i
<d
.length
; i
++) {
199 if (d
[i
] instanceof Patch
) { j
= i
;}
205 al_displayables
.add(displ
); // at the end
206 stack_index
= d
.length
;
208 al_displayables
.add(j
, displ
);
213 al_displayables
.add(0, displ
); // at the very beggining
216 } else if (displ
instanceof Profile
) {
217 // find first LayerSet or if none, first DLabel, add before it
218 for (i
=d
.length
-1; i
>-1; i
--) {
219 if (! (d
[i
] instanceof DLabel
|| d
[i
] instanceof LayerSet
)) { j
= i
; break; }
223 if (j
>= d
.length
) { al_displayables
.add(displ
); stack_index
= d
.length
; }
224 else { al_displayables
.add(j
, displ
); stack_index
= j
; }
226 // no labels or LayerSets
227 al_displayables
.add(displ
); // at the end
228 stack_index
= d
.length
;
230 } else if (displ
instanceof LayerSet
) {
231 // find first DLabel, add before it
232 for (i
=d
.length
-1; i
>-1; i
--) {
233 if (! (d
[i
] instanceof DLabel
)) { j
= i
; break; }
236 j
++; // add it after the non-label one, displacing the label one position
237 if (j
>= d
.length
) { al_displayables
.add(displ
); stack_index
= d
.length
; } // at the end
238 else { al_displayables
.add(j
, displ
); stack_index
= j
; }
241 al_displayables
.add(displ
); // at the end
242 stack_index
= d
.length
;
246 al_displayables
.add(displ
); // at the end
247 stack_index
= d
.length
;
251 updateInDatabase("stack_index"); // of the displayables ...
252 displ
.setLayer(this);
254 displ
.setLayer(this, false);
257 // insert into bucket
259 if (d
.length
== stack_index
) {
261 root
.put(stack_index
, displ
, displ
.getBoundingBox(null));
263 // add as last first, then update
264 root
.put(d
.length
, displ
, displ
.getBoundingBox(null));
265 // find and update the range of affected Displayable objects
266 root
.update(this, displ
, stack_index
, d
.length
); // first to last indices affected
270 if (update_displays
) {
271 Display
.add(this, displ
);
275 public HashMap
<Displayable
, ArrayList
<Bucket
>> getBucketMap() {
279 /** Used for reconstruction purposes. Assumes the displ are given in the proper order! */
280 public void addSilently(final DBObject displ
) { // why DBObject and not Displayable ?? TODO
281 if (null == displ
|| -1 != al_displayables
.indexOf(displ
)) return;
283 ((Displayable
)displ
).setLayer(this, false);
284 al_displayables
.add((Displayable
)displ
);
285 } catch (Exception e
) {
286 Utils
.log("Layer.addSilently: Not a Displayable/LayerSet, not adding DBObject id=" + displ
.getId());
291 public boolean remove(final Displayable displ
) {
292 if (null == displ
|| null == al_displayables
|| -1 == al_displayables
.indexOf(displ
)) {
293 Utils
.log2("Layer can't remove Displayable " + displ
.getId());
296 // remove from Bucket before modifying stack index
297 if (null != root
) Bucket
.remove(displ
, db_map
);
298 // now remove proper, so stack_index hasn't changed yet
299 al_displayables
.remove(displ
);
300 Display
.remove(this, displ
);
304 /** Used for reconstruction purposes. */
305 public void setParentSilently(LayerSet layer_set
) {
306 if (layer_set
== this.parent
) return;
307 this.parent
= layer_set
;
308 //Utils.log("Layer " +id + ": I have as new parent the LayerSet " + layer_set.getId());
311 public void setParent(LayerSet layer_set
) { // can be null
312 if (layer_set
== this.parent
) return;
313 this.parent
= layer_set
;
314 updateInDatabase("layer_set_id");
317 public LayerSet
getParent() {
321 public double getZ() { return z
; }
322 public double getThickness() { return thickness
; }
324 /** Remove this layer and all its contents from the project. */
325 public boolean remove(boolean check
) {
327 if (check
&& !Utils
.check("Really delete " + this.toString() + " and all its children?")) return false;
328 // destroy the Display objects that show this layer
329 Display
.remove(this);
330 // proceed to remove all the children
331 Displayable
[] displ
= new Displayable
[al_displayables
.size()]; // to avoid concurrent modifications
332 al_displayables
.toArray(displ
);
333 for (int i
=0; i
<displ
.length
; i
++) {
334 if (!displ
[i
].remove(false)) { // will call back Layer.remove(Displayable)
335 Utils
.showMessage("Could not delete " + displ
[i
]);
339 al_displayables
.clear();
340 // remove from the parent
341 /*can't ever be null//if (null != parent) */
343 Display
.updateLayerScroller(parent
);
344 removeFromDatabase();
345 } catch (Exception e
) { IJError
.print(e
); return false; }
349 public void setZ(double z
) {
350 if (Double
.isNaN(z
) || z
== this.z
) return;
352 if (null != parent
) {
353 parent
.reposition(this);
354 // fix ordering in the trees (must be done after repositioning in the parent)
355 LayerThing lt
= project
.findLayerThing(this);
357 LayerThing p
= (LayerThing
)lt
.getParent();
359 p
.removeChild(lt
); // does not affect the database
360 p
.addChild(lt
); // idem
364 updateInDatabase("z");
367 public void setThickness(final double thickness
) {
368 if (Double
.isNaN(thickness
) || thickness
== this.thickness
) return;
369 this.thickness
= thickness
;
370 updateInDatabase("thickness");
373 public boolean contains(final int x
, final int y
, int inset
) {
374 if (inset
< 0) inset
= -inset
;
375 return x
>= inset
&& y
>= inset
&& x
<= parent
.getLayerWidth() - inset
&& y
<= parent
.getLayerHeight() - inset
;
378 public boolean contains(final Displayable displ
) {
379 return -1 != al_displayables
.indexOf(displ
);
382 /** Returns true if any of the Displayable objects are of the given class. */
383 public boolean contains(final Class c
) {
384 for (Object ob
: al_displayables
) {
385 if (ob
.getClass() == c
) return true;
390 /** Count instances of the given Class. */
391 public int count(final Class c
) {
393 for (Object ob
: al_displayables
) {
394 if (ob
.getClass() == c
) n
++;
399 public boolean isEmpty() {
400 return 0 == al_displayables
.size() && parent
.isEmptyAt(this); // check for ZDisplayable painting here as well
403 /** Returns a copy of the list of Displayable objects.*/
404 public ArrayList
<Displayable
> getDisplayables() {
405 return (ArrayList
<Displayable
>)al_displayables
.clone();
408 /** Returns the real list of displayables, not a copy. If you modify this list, Thor may ground you with His lightning. */
409 public final ArrayList
<Displayable
> getDisplayableList() {
410 return al_displayables
;
413 public int getNDisplayables() {
414 return al_displayables
.size();
417 /** Returns a list of Displayable of class c only.*/
418 public ArrayList
<Displayable
> getDisplayables(final Class c
) {
419 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
420 if (null == c
) return al
;
421 if (Displayable
.class == c
) {
422 al
.addAll(al_displayables
);
425 for (Object ob
: al_displayables
) {
426 if (ob
.getClass() == c
) al
.add((Displayable
)ob
); // cast only the few added, not all as it would in looping with Displayabe d : al_displayables
431 /** Returns a list of all Displayable of class c that intersect the given rectangle. */
432 public ArrayList
<Displayable
> getDisplayables(final Class c
, final Rectangle roi
) {
433 return getDisplayables(c
, new Area(roi
), true);
436 /** Returns a list of all Displayable of class c that intersect the given area. */
437 public ArrayList
<Displayable
> getDisplayables(final Class c
, final Area aroi
, final boolean visible_only
) {
438 final ArrayList
<Displayable
> al
= getDisplayables(c
);
439 for (Iterator
<Displayable
> it
= al
.iterator(); it
.hasNext(); ) {
440 Displayable d
= it
.next();
441 if (visible_only
&& !d
.isVisible()) { it
.remove(); continue; }
442 Area area
= new Area(d
.getPerimeter());
443 area
.intersect(aroi
);
444 Rectangle b
= area
.getBounds();
445 if (0 == b
.width
|| 0 == b
.height
) it
.remove();
450 public Displayable
get(final long id
) {
451 for (Displayable d
: al_displayables
) {
452 if (d
.getId() == id
) return d
;
457 public double getLayerWidth() {
458 return parent
.getLayerWidth();
460 public double getLayerHeight() {
461 return parent
.getLayerHeight();
464 public Collection
<Displayable
> find(final int x
, final int y
) {
465 return find(x
, y
, false);
468 /** Find the Displayable objects that contain the point. */
469 public Collection
<Displayable
> find(final int x
, final int y
, final boolean visible_only
) {
470 if (null != root
) return root
.find(x
, y
, this, visible_only
);
471 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
472 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
473 Displayable d
= (Displayable
)al_displayables
.get(i
);
474 if (visible_only
&& !d
.isVisible()) continue;
475 if (d
.contains(x
, y
)) {
482 public Collection
<Displayable
> find(final Class c
, final int x
, final int y
) {
483 return find(c
, x
, y
, false);
486 /** Find the Displayable objects of Class c that contain the point. */
487 public Collection
<Displayable
> find(final Class c
, final int x
, final int y
, final boolean visible_only
) {
488 if (Displayable
.class == c
) return find(x
, y
); // search among all
489 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
490 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
491 Displayable d
= (Displayable
)al_displayables
.get(i
);
492 if (visible_only
&& !d
.isVisible()) continue;
493 if (d
.getClass() == c
&& d
.contains(x
, y
)) {
500 public Collection
<Displayable
> find(final Rectangle r
) {
501 return find(r
, false);
504 /** Find the Displayable objects whose bounding box intersects with the given rectangle. */
505 public Collection
<Displayable
> find(final Rectangle r
, final boolean visible_only
) {
506 if (null != root
&& root
.isBetter(r
, this)) return root
.find(r
, this, visible_only
);
507 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
508 for (Displayable d
: al_displayables
) {
509 if (visible_only
&& !d
.isVisible()) continue;
510 if (d
.getBoundingBox().intersects(r
)) {
517 /** Find the Displayable objects of class 'target' whose perimeter (not just the bounding box) intersect the given Displayable (which is itself not included if present in this very Layer). */
518 public Collection
<Displayable
> getIntersecting(final Displayable d
, final Class target
) {
520 final Area area
= new Area(d
.getPerimeter());
521 if (root
.isBetter(area
.getBounds(), this)) return root
.find(area
, this, false);
523 final ArrayList
<Displayable
> al
= new ArrayList();
524 for (int i
= al_displayables
.size() -1; i
>-1; i
--) {
525 Object ob
= al_displayables
.get(i
);
526 if (ob
.getClass() != target
) continue;
527 Displayable da
= (Displayable
)ob
;
528 if (d
.intersects(da
)) {
532 // remove the calling one
533 if (al
.contains(d
)) al
.remove(d
);
537 /** Returns -1 if not found. */
538 public final int indexOf(final Displayable d
) {
539 return al_displayables
.indexOf(d
);
542 /** Within its own class only.
543 * 'up' is at the last element of the ArrayList (since when painting, the first one gets painted first, and thus gets buried the most while the last paints last, on top). */
544 public void moveUp(final Displayable d
) {
545 final int i
= al_displayables
.indexOf(d
);
546 if (null == d
|| -1 == i
|| al_displayables
.size() -1 == i
) return;
547 if (al_displayables
.get(i
+1).getClass() == d
.getClass()) {
549 al_displayables
.remove(d
);
550 al_displayables
.add(i
+1, d
);
552 updateInDatabase("stack_index");
553 Display
.updatePanelIndex(d
.getLayer(), d
);
554 if (null != root
) root
.update(this, d
, i
, i
+1);
557 /** Within its own class only. */
558 public void moveDown(final Displayable d
) {
559 final int i
= al_displayables
.indexOf(d
);
560 if (null == d
|| -1 == i
|| 0 == i
) return;
561 if (al_displayables
.get(i
-1).getClass() == d
.getClass()) {
563 Displayable o
= al_displayables
.remove(i
-1);
564 al_displayables
.add(i
, o
);
566 updateInDatabase("stack_index");
567 Display
.updatePanelIndex(d
.getLayer(), d
);
568 if (null != root
) root
.update(this, d
, i
-1, i
);
571 /** Within its own class only. */
572 public void moveTop(final Displayable d
) { // yes I could have made several lists and make my life easier. Whatever
573 final int i
= al_displayables
.indexOf(d
);
574 final int size
= al_displayables
.size();
575 if (null == d
|| -1 == i
|| size
-1 == i
) return;
576 final Class c
= d
.getClass();
577 boolean done
= false;
579 for (; j
<size
; j
++) {
580 if (al_displayables
.get(j
).getClass() == c
) continue;
582 al_displayables
.remove(d
);
583 al_displayables
.add(--j
, d
); // j-1
588 // solves case of no other class present
591 al_displayables
.remove(d
);
592 al_displayables
.add(d
);
595 updateInDatabase("stack_index");
596 Display
.updatePanelIndex(d
.getLayer(), d
);
597 if (null != root
) root
.update(this, d
, i
, j
);
600 /** Within its own class only. */
601 public void moveBottom(final Displayable d
) {
602 final int i
= al_displayables
.indexOf(d
);
603 if (null == d
|| -1 == i
|| 0 == i
) return;
604 Class c
= d
.getClass();
605 boolean done
= false;
607 for (; j
> -1; j
--) {
608 if (al_displayables
.get(j
).getClass() == c
) continue;
610 al_displayables
.remove(d
);
611 al_displayables
.add(++j
, d
); // j+1
616 // solve case of no other class present
618 al_displayables
.remove(d
);
619 al_displayables
.add(0, d
);
622 updateInDatabase("stack_index");
623 Display
.updatePanelIndex(d
.getLayer(), d
);
624 if (null != root
) root
.update(this, d
, j
, i
);
627 /** Within its own class only. */
628 public boolean isTop(final Displayable d
) {
629 final int i
= al_displayables
.indexOf(d
);
630 final int size
= al_displayables
.size();
631 if (size
-1 == i
) return true;
632 if (al_displayables
.get(i
+1).getClass() == d
.getClass()) return false;
634 } // these two methods will throw an Exception if the Displayable is not found (-1 == i) (the null.getClass() *should* throw it)
635 /** Within its own class only. */
636 public boolean isBottom(final Displayable d
) {
637 final int i
= al_displayables
.indexOf(d
);
638 if (0 == i
) return true;
639 if (al_displayables
.get(i
-1).getClass() == d
.getClass()) return false;
643 /** Get the index of the given Displayable relative to the rest of its class. Beware that the order of the al_displayables is bottom at zero, top at last, but the relative index returned here is inverted: top at zero, bottom at last -to match the tabs' vertical orientation in a Display.*/
644 public int relativeIndexOf(final Displayable d
) {
645 int k
= al_displayables
.indexOf(d
);
646 if (-1 == k
) return -1;
647 Class c
= d
.getClass();
648 int size
= al_displayables
.size();
650 for (; i
<size
; i
++) {
651 if (al_displayables
.get(i
).getClass() == c
) continue;
659 Utils
.log2("relativeIndexOf: return 0");
663 /** Note: Not recursive into embedded LayerSet objects. Returns the hash set of objects whose visibility has changed. */
664 public HashSet
<Displayable
> setVisible(String type
, boolean visible
, boolean repaint
) {
665 type
= type
.toLowerCase();
666 if (type
.equals("image")) type
= "patch";
667 final HashSet
<Displayable
> hs
= new HashSet
<Displayable
>();
668 for (Displayable d
: al_displayables
) {
669 if (visible
!= d
.isVisible() && d
.getClass().getName().toLowerCase().endsWith(type
)) {
670 d
.setVisible(visible
, false); // don't repaint
671 Display
.updateVisibilityCheckbox(this, d
, null);
676 Display
.repaint(this);
680 public void setAllVisible(boolean repaint
) {
681 for (Displayable d
: al_displayables
) {
682 if (!d
.isVisible()) d
.setVisible(true, repaint
);
686 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
687 public HashSet
<Displayable
> hideExcept(ArrayList
<Class
> type
, boolean repaint
) {
688 final HashSet
<Displayable
> hs
= new HashSet
<Displayable
>();
689 for (Displayable d
: al_displayables
) {
690 if (!type
.contains(d
.getClass()) && d
.isVisible()) {
691 d
.setVisible(false, repaint
);
698 public void exportXML(StringBuffer sb_body
, String indent
, Object any
) {
699 String in
= indent
+ "\t";
701 sb_body
.append(indent
).append("<t2_layer oid=\"").append(id
).append("\"\n")
702 .append(in
).append(" thickness=\"").append(thickness
).append("\"\n")
703 .append(in
).append(" z=\"").append(z
).append("\"\n")
705 String title
= project
.findLayerThing(this).getTitle();
706 if (null == title
) title
= "";
707 sb_body
.append(in
).append(" title=\"").append(title
).append("\"\n"); // TODO 'title' should be a property of the Layer, not the LayerThing. Also, the LayerThing should not exist: LayerSet and Layer should be directly presentable in a tree. They are not Things as in "objects of the sample", but rather, structural necessities such as Patch.
708 sb_body
.append(indent
).append(">\n");
709 // 2 - export children
710 if (null != al_displayables
) {
711 for (Iterator it
= al_displayables
.iterator(); it
.hasNext(); ) {
712 Displayable d
= (Displayable
)it
.next();
713 d
.exportXML(sb_body
, in
, any
);
717 sb_body
.append(indent
).append("</t2_layer>\n");
720 /** Includes all Displayable objects in the list of possible children. */
721 static public void exportDTD(StringBuffer sb_header
, HashSet hs
, String indent
) {
722 String type
= "t2_layer";
723 if (hs
.contains(type
)) return;
724 sb_header
.append(indent
).append("<!ELEMENT t2_layer (t2_patch,t2_label,t2_layer_set,t2_profile)>\n")
725 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" oid").append(Displayable
.TAG_ATTR2
)
726 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" thickness").append(Displayable
.TAG_ATTR2
)
727 .append(indent
).append(Displayable
.TAG_ATTR1
).append(type
).append(" z").append(Displayable
.TAG_ATTR2
)
731 public String
getTitle() {
732 LayerThing lt
= project
.findLayerThing(this);
733 if (null == lt
|| null == lt
.getTitle() || 0 == lt
.getTitle().trim().length()) return this.toString();
734 return lt
.getTitle();
737 public void destroy() {
738 for (Iterator it
= al_displayables
.iterator(); it
.hasNext(); ) {
739 Displayable d
= (Displayable
)it
.next();
744 /** Returns null if no Displayable objects of class c exist. */
745 public Rectangle
getMinimalBoundingBox(final Class c
) {
746 Rectangle box
= null;
747 Rectangle tmp
= new Rectangle();
748 for (Iterator it
= getDisplayables(c
).iterator(); it
.hasNext(); ) {
749 tmp
= ((Displayable
)it
.next()).getBoundingBox(tmp
);
751 box
= (Rectangle
)tmp
.clone();
759 /** Preconcatenate the given AffineTransform to all Displayable objects of class c, without respecting their links. */
760 public void apply(final Class c
, final AffineTransform at
) {
761 boolean all
= Displayable
.class == c
;
762 for (Iterator it
= al_displayables
.iterator(); it
.hasNext(); ) {
763 final Displayable d
= (Displayable
)it
.next();
764 if (all
|| d
.getClass() == c
) {
765 d
.preTransform(at
, false);
771 /** Make a copy of this layer into the given LayerSet, enclosing only Displayable objects within the roi, and translating them for that roi x,y. */
772 public Layer
clone(final Project pr
, LayerSet ls
, final Rectangle roi
, final boolean copy_id
) {
773 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
774 final Layer copy
= new Layer(pr
, nid
, z
, thickness
);
776 for (Iterator it
= find(roi
).iterator(); it
.hasNext(); ) {
777 Displayable dc
= ((Displayable
)it
.next()).clone(pr
, copy_id
);
778 copy
.addSilently(dc
);
780 final AffineTransform transform
= new AffineTransform();
781 transform
.translate(-roi
.x
, -roi
.y
);
782 copy
.apply(Displayable
.class, transform
);
786 static public final int IMAGEPROCESSOR
= 0;
787 static public final int PIXELARRAY
= 1;
788 static public final int IMAGE
= 2;
789 static public final int IMAGEPLUS
= 3;
791 /** Returns the region defined by the rectangle as an image in the type and format specified.
792 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
793 * The format is either Layer.IMAGEPROCESSOR, Layer.IMAGEPLUS, Layer.PIXELARRAY or Layer.IMAGE.
795 public Object
grab(final Rectangle r
, final double scale
, final Class c
, final int c_alphas
, final int format
, final int type
) {
796 // check that it will fit in memory
797 if (!project
.getLoader().releaseToFit(r
.width
, r
.height
, type
, 1.1f
)) {
798 Utils
.log("Layer.grab: Cannot fit a flat image of " + (long)(r
.width
*r
.height
*(ImagePlus
.GRAY8
==type?
1:4)*1.1) + " bytes in memory.");
801 if (IMAGE
== format
) {
802 return project
.getLoader().getFlatAWTImage(this, r
, scale
, c_alphas
, type
, c
, null, true, Color
.black
);
804 final ImagePlus imp
= project
.getLoader().getFlatImage(this, r
, scale
, c_alphas
, type
, c
, null, true);
809 return imp
.getProcessor();
811 return imp
.getProcessor().getPixels();
817 public DBObject
findById(final long id
) {
818 if (this.id
== id
) return this;
819 for (Displayable d
: al_displayables
) {
820 if (d
.getId() == id
) return d
;
825 // private to the package
826 void linkPatchesR() {
827 for (Displayable d
: al_displayables
) {
828 if (d
.getClass() == LayerSet
.class) ((LayerSet
)d
).linkPatchesR();
829 d
.linkPatches(); // Patch.class does nothing
833 /** Recursive into nested LayerSet objects.*/
834 public void updateLayerTree() {
835 project
.getLayerTree().addLayer(parent
, this);
836 for (Displayable d
: getDisplayables(LayerSet
.class)) {
837 ((LayerSet
)d
).updateLayerTree();
841 /** Don't use this for fast pixel grabbing; this is intended for the dropper tool and status bar reporting by mouse motion. */
842 public int[] getPixel(final int x
, final int y
, final double mag
) {
843 // find Patch under cursor
844 final Collection
<Displayable
> under
= find(Patch
.class, x
, y
);
845 if (null == under
|| under
.isEmpty()) return new int[3]; // zeros
846 final Patch pa
= (Patch
)under
.iterator().next();// get(0) // the top one, since they are ordered like html divs
847 // TODO: edit here when adding layer mipmaps
848 return pa
.getPixel(x
, y
, mag
);
851 public void recreateBuckets() {
852 this.root
= new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket
.getBucketSide(this));
853 this.db_map
= new HashMap
<Displayable
,ArrayList
<Bucket
>>();
854 this.root
.populate(this, db_map
);
858 /** Update buckets of a position change for the given Displayable. */
859 public void updateBucket(final Displayable d
) {
860 if (null != root
) root
.updatePosition(d
, db_map
);
863 public void checkBuckets() {
864 if (use_buckets
&& (null == root
|| null == db_map
)) recreateBuckets();
867 private boolean use_buckets
= true;
869 public void setBucketsEnabled(boolean b
) {
870 this.use_buckets
= b
;
871 if (!use_buckets
) this.root
= null;
874 static class DoEditLayer
implements DoStep
{
875 final double z
, thickness
;
877 DoEditLayer(final Layer layer
) {
880 this.thickness
= layer
.thickness
;
882 public Displayable
getD() { return null; }
883 public boolean isEmpty() { return false; }
884 public boolean isIdenticalTo(final Object ob
) {
885 if (!(ob
instanceof Layer
)) return false;
886 final Layer layer
= (Layer
) ob
;
887 return this.la
.id
== layer
.id
&& this.z
== layer
.z
&& this.thickness
== layer
.thickness
;
889 public boolean apply(int action
) {
891 la
.thickness
= this.thickness
;
892 la
.getProject().getLayerTree().updateUILater();
893 Display
.update(la
.getParent());
898 static class DoEditLayers
implements DoStep
{
899 final ArrayList
<DoEditLayer
> all
= new ArrayList
<DoEditLayer
>();
900 DoEditLayers(final List
<Layer
> all
) {
901 for (final Layer la
: all
) {
902 this.all
.add(new DoEditLayer(la
));
905 public Displayable
getD() { return null; }
906 public boolean isEmpty() { return all
.isEmpty(); }
907 public boolean isIdenticalTo(final Object ob
) {
908 if (!(ob
instanceof DoEditLayers
)) return false;
909 final DoEditLayers other
= (DoEditLayers
) ob
;
910 if (all
.size() != other
.all
.size()) return false;
912 final Iterator
<DoEditLayer
> it1
= all
.iterator();
913 final Iterator
<DoEditLayer
> it2
= other
.all
.iterator();
914 for (; it1
.hasNext() && it2
.hasNext(); ) {
915 if (!it1
.next().isIdenticalTo(it2
.next())) return false;
919 public boolean apply(int action
) {
920 boolean failed
= false;
921 for (final DoEditLayer one
: all
) {
922 if (!one
.apply(action
)) {
930 /** Add an object that is Layer bound, such as Profile, Patch, DLabel and LayerSet. */
931 static class DoContentChange
implements DoStep
{
933 final ArrayList
<Displayable
> al
;
934 DoContentChange(final Layer la
) {
936 this.al
= la
.getDisplayables(); // a copy
938 public Displayable
getD() { return null; }
939 public boolean isEmpty() { return false; }
940 /** Check that the Displayable objects of this layer are the same and in the same quantity and order. */
941 public boolean isIdenticalTo(Object ob
) {
942 if (!(ob
instanceof DoContentChange
)) return false;
943 final DoContentChange dad
= (DoContentChange
) ob
;
944 if (la
!= dad
.la
|| al
.size() != dad
.al
.size()) return false;
946 final Iterator
<Displayable
> it1
= al
.iterator();
947 final Iterator
<Displayable
> it2
= dad
.al
.iterator();
948 for (; it1
.hasNext() && it2
.hasNext(); ) {
949 if (it1
.next() != it2
.next()) return false;
953 public boolean apply(final int action
) {
954 // find the subset in la.al_displayables that is not in this.al
955 final HashSet
<Displayable
> sub1
= new HashSet
<Displayable
>(la
.al_displayables
);
956 sub1
.removeAll(this.al
);
957 // find the subset in this.al that is not in la.al_displayables
958 final HashSet
<Displayable
> sub2
= new HashSet
<Displayable
>(this.al
);
959 sub2
.removeAll(la
.al_displayables
);
961 HashSet
<Displayable
> subA
=null, subB
=null;
963 if (action
== DoStep
.UNDO
) {
966 } else if (action
== DoStep
.REDO
) {
970 if (null != subA
&& null != subB
) {
971 // Mark Patch for mipmap file removal
972 for (final Displayable d
: subA
) {
973 if (d
.getClass() == Patch
.class) {
974 d
.getProject().getLoader().queueForMipmapRemoval((Patch
)d
, true);
978 for (final Displayable d
: subB
) {
979 if (d
.getClass() == Patch
.class) {
980 d
.getProject().getLoader().queueForMipmapRemoval((Patch
)d
, false);
985 la
.al_displayables
.clear();
986 la
.al_displayables
.addAll(this.al
);
987 la
.recreateBuckets();
988 Display
.updateVisibleTabs();
989 Display
.clearSelection();