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.
23 package ini
.trakem2
.display
;
28 import ij
.WindowManager
;
29 import ij
.gui
.ImageCanvas
;
31 import ij
.gui
.Toolbar
;
32 import ij
.measure
.Calibration
;
33 import ij
.process
.ByteProcessor
;
34 import ij
.process
.ColorProcessor
;
35 import ini
.trakem2
.Project
;
36 import ini
.trakem2
.display
.graphics
.GraphicsSource
;
37 import ini
.trakem2
.display
.inspect
.InspectPatchTrianglesMode
;
38 import ini
.trakem2
.imaging
.Segmentation
;
39 import ini
.trakem2
.persistence
.Loader
;
40 import ini
.trakem2
.utils
.Bureaucrat
;
41 import ini
.trakem2
.utils
.IJError
;
42 import ini
.trakem2
.utils
.Lock
;
43 import ini
.trakem2
.utils
.ProjectToolbar
;
44 import ini
.trakem2
.utils
.Search
;
45 import ini
.trakem2
.utils
.Utils
;
46 import ini
.trakem2
.utils
.Worker
;
48 import java
.awt
.AWTException
;
49 import java
.awt
.AlphaComposite
;
50 import java
.awt
.BasicStroke
;
51 import java
.awt
.Color
;
52 import java
.awt
.Component
;
53 import java
.awt
.Composite
;
54 import java
.awt
.Cursor
;
55 import java
.awt
.Event
;
56 import java
.awt
.Graphics
;
57 import java
.awt
.Graphics2D
;
58 import java
.awt
.GraphicsConfiguration
;
59 import java
.awt
.Image
;
60 import java
.awt
.Point
;
61 import java
.awt
.Rectangle
;
62 import java
.awt
.RenderingHints
;
63 import java
.awt
.Robot
;
64 import java
.awt
.Stroke
;
65 import java
.awt
.Toolkit
;
66 import java
.awt
.Transparency
;
67 import java
.awt
.event
.InputEvent
;
68 import java
.awt
.event
.KeyEvent
;
69 import java
.awt
.event
.KeyListener
;
70 import java
.awt
.event
.MouseEvent
;
71 import java
.awt
.event
.MouseWheelEvent
;
72 import java
.awt
.event
.MouseWheelListener
;
73 import java
.awt
.geom
.AffineTransform
;
74 import java
.awt
.geom
.Area
;
75 import java
.awt
.geom
.Ellipse2D
;
76 import java
.awt
.image
.BufferedImage
;
77 import java
.awt
.image
.PixelGrabber
;
78 import java
.awt
.image
.VolatileImage
;
79 import java
.util
.ArrayList
;
80 import java
.util
.Collection
;
81 import java
.util
.Collections
;
82 import java
.util
.HashMap
;
83 import java
.util
.HashSet
;
84 import java
.util
.Hashtable
;
85 import java
.util
.Iterator
;
86 import java
.util
.List
;
87 import java
.util
.ListIterator
;
90 import java
.util
.Vector
;
91 import java
.util
.concurrent
.Executors
;
92 import java
.util
.concurrent
.ScheduledExecutorService
;
93 import java
.util
.concurrent
.ScheduledFuture
;
94 import java
.util
.concurrent
.TimeUnit
;
96 import javax
.vecmath
.Point2f
;
97 import javax
.vecmath
.Vector2f
;
98 import javax
.vecmath
.Vector3d
;
100 public final class DisplayCanvas
extends ImageCanvas
implements KeyListener
/*, FocusListener*/, MouseWheelListener
{
102 private static final long serialVersionUID
= 1L;
104 private Display display
;
106 private boolean update_graphics
= false;
107 private BufferedImage offscreen
= null;
108 private final HashSet
<BufferedImage
> to_flush
= new HashSet
<BufferedImage
>();
109 private ArrayList
<Displayable
> al_top
= new ArrayList
<Displayable
>();
111 private final Lock lock_paint
= new Lock();
113 private Rectangle box
= null; // the bounding box of the active
115 private FakeImageWindow fake_win
;
117 private FreeHandProfile freehandProfile
= null;
118 private Robot r
;// used for setting the mouse pointer
120 private final Object offscreen_lock
= new Object();
122 private Cursor noCursor
;
124 private boolean snapping
= false;
125 private boolean dragging
= false;
126 private boolean input_disabled
= false;
127 private boolean input_disabled2
= false;
129 /** Store a copy of whatever data as each Class may define it, one such data object per class.
130 * Private to the package. */
131 static private Hashtable
<Class
<?
>,Object
> copy_buffer
= new Hashtable
<Class
<?
>,Object
>();
133 static void setCopyBuffer(final Class
<?
> c
, final Object ob
) { copy_buffer
.put(c
, ob
); }
134 static Object
getCopyBuffer(final Class
<?
> c
) { return copy_buffer
.get(c
); }
136 static private boolean openglEnabled
= false;
137 static private boolean quartzEnabled
= false;
138 static private boolean ddscaleEnabled
= false;
140 // Private to the display package:
141 static final RenderingHints rhints
;
143 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent; */
145 final Map
<RenderingHints
.Key
, Object
> hints
= new HashMap
<RenderingHints
.Key
, Object
>();
147 String openglProperty
= System
.getProperty("sun.java2d.opengl");
148 openglEnabled
= openglProperty
!= null && Boolean
.parseBoolean(openglProperty
);
149 } catch (Exception ex
) { }
151 String quartzProperty
= System
.getProperty("apple.awt.graphics.UseQuartz");
152 quartzEnabled
= Boolean
.parseBoolean(quartzProperty
);
153 } catch (Exception ex
) { }
155 String ddscaleProperty
= System
.getProperty("sun.java2d.ddscale");
156 String d3dProperty
= System
.getProperty("sun.java2d.d3d");
157 ddscaleEnabled
= Boolean
.parseBoolean(ddscaleProperty
) && Boolean
.parseBoolean(d3dProperty
);
158 } catch (Exception ex
) { }
161 // Bilinear interpolation can be accelerated by the OpenGL pipeline
162 hints
.put(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_BILINEAR
);
163 hints
.put(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
165 } else if (quartzEnabled
) {
166 //hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
167 hints
.put(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
);
168 hints
.put(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_SPEED
);
169 hints
.put(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_OFF
);
171 } else if (ddscaleEnabled
) {
172 hints
.put(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_BILINEAR
);
175 rhints
= new RenderingHints(hints
);
178 private VolatileImage volatileImage
;
179 private Object volatile_lock
= new Object();
180 //private javax.swing.Timer resourceTimer = new javax.swing.Timer(10000, resourceReaper);
181 //private boolean frameRendered = false;
182 private boolean invalid_volatile
= false;
184 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent.
185 * MUST be called within a "synchronized (volatile_lock) { ... }" block. */
186 private void renderVolatileImage(final GraphicsConfiguration gc
, final BufferedImage offscreen
,
187 final ArrayList
<Displayable
> top
, final Displayable active
,
188 final Layer active_layer
, final List
<Layer
> layers
,
189 final int c_alphas
, final AffineTransform at
, Rectangle clipRect
) {
191 // Recreate volatileImage ONLY if necessary: when null, when incompatible, or when dimensions have changed
192 // Otherwise, just paint on top of it
193 final int w
= getWidth(), h
= getHeight();
194 if (0 == w
|| 0 == h
) return;
195 if (null == volatileImage
|| volatileImage
.getWidth() != w
196 || volatileImage
.getHeight() != h
|| volatileImage
.validate(gc
) == VolatileImage
.IMAGE_INCOMPATIBLE
) {
197 if (null != volatileImage
) volatileImage
.flush();
198 volatileImage
= gc
.createCompatibleVolatileImage(w
, h
);
199 volatileImage
.setAccelerationPriority(1.0f
);
200 invalid_volatile
= false;
201 clipRect
= null; // paint all
204 // Now paint the BufferedImage into the accelerated image
206 final Graphics2D g
= volatileImage
.createGraphics();
209 if (null != clipRect
) g
.setClip(clipRect
);
211 // 1 - Erase any background
212 g
.setColor(Color
.black
);
213 if (null == clipRect
) g
.fillRect(0, 0, w
, h
);
214 else g
.fillRect(clipRect
.x
, clipRect
.y
, clipRect
.width
, clipRect
.height
);
216 // 2 - Paint offscreen image
217 if (null != offscreen
) g
.drawImage(offscreen
, 0, 0, null);
219 // 3 - Paint the active Displayable and all cached on top
221 //Object antialias = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
222 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
); // to smooth edges of the images
223 //Object text_antialias = g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
224 g
.setRenderingHint(RenderingHints
.KEY_TEXT_ANTIALIASING
, RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
);
225 //Object render_quality = g.getRenderingHint(RenderingHints.KEY_RENDERING);
226 g
.setRenderingHint(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
228 if (null != active_layer
) {
230 g
.setStroke(this.stroke
); // AFTER setting the transform
231 // Active has to be painted wherever it is, within al_top, if it's not an image
232 //if (null != active && active.getClass() != Patch.class && !active.isOutOfRepaintingClip(magnification, srcRect, clipRect)) active.paint(g, magnification, true, c_alphas, active_layer);
233 final boolean must_paint_active
= null != active
&& active
.isVisible() && !ImageData
.class.isAssignableFrom(active
.getClass());
234 boolean active_painted
= !must_paint_active
;
237 final Rectangle tmp
= null != clipRect ?
new Rectangle() : null;
238 final Rectangle clip
= null != clipRect ?
new Rectangle((int)(clipRect
.x
* magnification
) - srcRect
.x
, (int)(clipRect
.y
* magnification
) - srcRect
.y
, (int)(clipRect
.width
* magnification
), (int)(clipRect
.height
* magnification
)) : null;
239 for (final Displayable d
: top
) {
240 if (null != clipRect
&& !d
.getBoundingBox(tmp
).intersects(clip
)) continue;
241 d
.paint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
242 if (active_painted
) continue;
243 else active_painted
= d
== active
;
246 if (must_paint_active
&& !active_painted
) {
247 // Active may not have been part of top array if it was added new and the offscreen image was not updated,
248 // which is the case for any non-image object
249 // Or, when selecting an object if there were none selected yet.
250 active
.paint(g
, srcRect
, magnification
, true, c_alphas
, active_layer
, layers
);
254 display
.getMode().getGraphicsSource().paintOnTop(g
, display
, srcRect
, magnification
);
256 if (null != active_layer
.getOverlay2())
257 active_layer
.getOverlay2().paint(g
, srcRect
, magnification
);
258 if (null != active_layer
.getParent().getOverlay2())
259 active_layer
.getParent().getOverlay2().paint(g
, srcRect
, magnification
);
261 if (null != display
.gridoverlay
) display
.gridoverlay
.paint(g
);
263 /* // debug: paint the ZDisplayable's bucket in this layer
264 if (null != active_layer.getParent().lbucks) {
265 active_layer.getParent().lbucks.get(active_layer).root.paint(g, srcRect, magnification, Color.red);
270 } while (volatileImage
.contentsLost());
273 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent;
274 * Paints (and re-renders, if necessary) the volatile image onto the given Graphics object, which
275 * is that of the DisplayCanvas as provided to the paint(Graphics g) method.
277 * Expects clipRect in screen coordinates
279 private void render(final Graphics g
, final Displayable active
, final Layer active_layer
,
280 final List
<Layer
> layers
, final int c_alphas
, final AffineTransform at
, Rectangle clipRect
) {
281 final Graphics2D g2d
= (Graphics2D
) g
.create();
282 g2d
.setRenderingHints(rhints
);
284 final ArrayList
<Displayable
> top
;
285 final BufferedImage offscreen
;
286 synchronized (offscreen_lock
) {
287 offscreen
= this.offscreen
;
288 top
= this.al_top
; // will never be cleared, but may be swapped
290 final GraphicsConfiguration gc
= getGraphicsConfiguration();
291 display
.getProject().getLoader().releaseToFit(getWidth() * getHeight() * 4 * 5); // 5 images
293 // Protect volatile image while rendering it
294 synchronized (volatile_lock
) {
295 if (invalid_volatile
|| null == volatileImage
296 || volatileImage
.validate(gc
) != VolatileImage
.IMAGE_OK
)
298 // clear clip, remade in full
300 renderVolatileImage(gc
, offscreen
, top
, active
, active_layer
, layers
, c_alphas
, at
, clipRect
);
302 if (null != clipRect
) g2d
.setClip(clipRect
);
303 g2d
.drawImage(volatileImage
, 0, 0, null);
305 } while (volatileImage
.contentsLost());
309 // Flush all old offscreen images
310 synchronized (offscreen_lock
) {
311 for (final BufferedImage bi
: to_flush
) {
318 protected void invalidateVolatile() {
319 synchronized (volatile_lock
) {
320 this.invalid_volatile
= true;
326 public DisplayCanvas(Display display
, int width
, int height
) {
327 super(new FakeImagePlus(width
, height
, display
));
328 fake_win
= new FakeImageWindow(imp
, this, display
);
329 this.display
= display
;
330 this.imageWidth
= width
;
331 this.imageHeight
= height
;
332 removeKeyListener(IJ
.getInstance());
333 addKeyListener(this);
334 addMouseWheelListener(this);
337 public Display
getDisplay() { return display
; }
339 /** Used to constrain magnification so that only snapshots are used for painting when opening a new, large and filled Display. */
340 protected void setInitialMagnification(double mag
) { // calling this method 'setMagnification' would conflict with the super class homonimous method.
341 this.magnification
= mag
; // don't save in the database. This value is overriden when reopening from the database by calling the setup method.
344 /** Used for restoring properties from the database. */
345 public void setup(double mag
, Rectangle srcRect
) {
346 this.magnification
= mag
;
347 this.srcRect
= (Rectangle
)srcRect
.clone(); // just in case
348 super.setDrawingSize((int)Math
.ceil(srcRect
.width
* mag
), (int)Math
.ceil(srcRect
.height
* mag
));
349 setMagnification(mag
);
350 //no longer needed//display.pack(); // TODO should be run via invokeLater ... need to check many potential locks of invokeLater calling each other.
353 /** Does not repaint. */
354 public void setDimensions(double width
, double height
) {
355 this.imageWidth
= (int)Math
.ceil(width
);
356 this.imageHeight
= (int)Math
.ceil(height
);
357 ((FakeImagePlus
)imp
).setDimensions(imageWidth
, imageHeight
);
360 /** Overriding to disable it. */
361 public void handlePopupMenu() {}
363 public final void update(final Graphics g
) {
364 // overriding to avoid default behaviour in java.awt.Canvas which consists in first repainting the entire drawable area with the background color, and then calling method paint.
368 /** Handles repaint event requests and the generation of offscreen threads. */
369 private final AbstractRepaintThread RT
= new AbstractRepaintThread(this, "T2-Canvas-Repainter", new OffscreenThread()) {
370 protected void handleUpdateGraphics(final Component target
, final Rectangle clipRect
) {
371 final Layer active_layer
= display
.getLayer();
372 this.off
.setProperties(new RepaintProperties(clipRect
, active_layer
, active_layer
.getParent().getColorCueLayerRange(active_layer
), target
.getWidth(), target
.getHeight(), srcRect
, magnification
, display
.getActive(), display
.getDisplayChannelAlphas(), display
.getMode().getGraphicsSource()));
377 private final void setRenderingHints(final Graphics2D g) {
378 // so slow!! Particularly the first one.
379 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
380 //g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
381 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // to smooth edges of the images
382 //g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
383 //g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
387 public void setMagnification(double mag
) {
388 if (mag
< 0.00000001) mag
= 0.00000001;
389 // ensure a stroke of thickness 1.0 regardless of magnification
390 this.stroke
= new BasicStroke((float)(1.0/mag
), BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
);
391 // FIXES MAG TO ImageCanvas.zoomLevel LIMITS!!
392 //super.setMagnification(mag);
394 this.magnification
= mag
;
395 imp
.setTitle(imp
.getTitle());
396 display
.getMode().magnificationUpdated(srcRect
, mag
);
399 /** Paint lines always with a thickness of 1 pixel. This stroke is modified when the magnification is changed, to compensate. */
400 private BasicStroke stroke
= new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
);
402 /** The affine transform representing the srcRect displacement and the magnification. */
403 private final AffineTransform atc
= new AffineTransform();
405 public void paint(final Graphics g
) {
406 if (null == g
) return;
408 synchronized (lock_paint
) {
412 // ensure proper positioning
413 g
.translate(0, 0); // ints!
415 final Rectangle clipRect
= g
.getClipBounds();
417 final Displayable active
= display
.getActive();
418 final int c_alphas
= display
.getDisplayChannelAlphas();
420 final Layer active_layer
= display
.getLayer();
421 final List
<Layer
> layers
= active_layer
.getParent().getColorCueLayerRange(active_layer
);
423 final Graphics2D g2d
= (Graphics2D
)g
;
425 // prepare the canvas for the srcRect and magnification
426 final AffineTransform at_original
= g2d
.getTransform();
428 atc
.scale(magnification
, magnification
);
429 atc
.translate(-srcRect
.x
, -srcRect
.y
);
430 at_original
.preConcatenate(atc
);
432 if (null != offscreen
&& dragging
) invalidateVolatile(); // to update the active at least
433 render(g
, active
, active_layer
, layers
, c_alphas
, at_original
, clipRect
);
435 g2d
.setTransform(at_original
);
437 g2d
.setStroke(this.stroke
);
440 //if (null != display.getLayer().root) display.getLayer().root.paint(g2d, srcRect, magnification, Color.red);
441 //if (null != display.getLayer().getParent().lbucks.get(display.getLayer()).root) display.getLayer().getParent().lbucks.get(display.getLayer()).root.paint(g2d, srcRect, magnification, Color.blue);
445 g2d
.setTransform(new AffineTransform());
446 // reset to 1.0 thickness
447 g2d
.setStroke(new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
));
449 // paint brush outline for AreaList, or fast-marching area
450 if (mouse_in
&& null != active
&& AreaContainer
.class.isInstance(active
)) {
451 switch (ProjectToolbar
.getToolId()) {
452 case ProjectToolbar
.BRUSH
:
453 int brushSize
= ProjectToolbar
.getBrushSize();
454 g
.setColor(active
.getColor());
455 g
.drawOval((int)((xMouse
-srcRect
.x
-brushSize
/2)*magnification
), (int)((yMouse
- srcRect
.y
-brushSize
/2)*magnification
), (int)(brushSize
* magnification
), (int)(brushSize
* magnification
));
457 case ProjectToolbar
.PENCIL
:
458 case ProjectToolbar
.WAND
:
459 Composite co
= g2d
.getComposite();
460 if (IJ
.isWindows()) g2d
.setColor(Color
.yellow
);
461 else g2d
.setXORMode(Color
.yellow
); // XOR on yellow for best contrast
462 g2d
.drawRect((int)((xMouse
-srcRect
.x
-Segmentation
.fmp
.width
/2) * magnification
),
463 (int)((yMouse
-srcRect
.y
-Segmentation
.fmp
.height
/2) * magnification
),
464 (int)(Segmentation
.fmp
.width
* magnification
),
465 (int)(Segmentation
.fmp
.height
* magnification
));
466 g2d
.setComposite(co
); // undo XOR mode
472 final Roi roi
= imp
.getRoi();
478 if (null != freehandProfile
) {
479 freehandProfile
.paint(g
, magnification
, srcRect
, true);
481 noCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(new BufferedImage(1,1,BufferedImage
.TYPE_BYTE_BINARY
), new Point(0,0), "noCursor");
484 } catch (Exception e
) {
485 Utils
.log2("DisplayCanvas.paint(Graphics) Error: " + e
);
488 synchronized (lock_paint
) {
494 public void waitForRepaint() {
495 // wait for all offscreen methods to finish painting
497 // wait for the paint method to finish painting
498 synchronized (lock_paint
) {
504 /** Paints a handle on the screen coords. Adapted from ij.gui.Roi class. */
505 static public void drawHandle(final Graphics g
, final int x
, final int y
, final double magnification
) {
506 final int width5
= (int)(5 / magnification
+ 0.5);
507 final int width3
= (int)(3 / magnification
+ 0.5);
508 final int corr2
= (int)(2 / magnification
+ 0.5);
509 final int corr1
= (int)(1 / magnification
+ 0.5);
510 g
.setColor(Color
.white
);
511 g
.drawRect(x
- corr2
, y
- corr2
, width5
, width5
);
512 g
.setColor(Color
.black
);
513 g
.drawRect(x
- corr1
, y
- corr1
, width3
, width3
);
514 g
.setColor(Color
.white
);
515 g
.fillRect(x
, y
, corr1
, corr1
);
518 /** Paints a handle at x,y screen coords. */
519 static public void drawScreenHandle(final Graphics g
, final int x
, final int y
) {
520 g
.setColor(Color
.orange
);
521 g
.drawRect(x
- 2, y
- 2, 5, 5);
522 g
.setColor(Color
.black
);
523 g
.drawRect(x
- 1, y
- 1, 3, 3);
524 g
.setColor(Color
.orange
);
525 g
.fillRect(x
, y
, 1, 1);
528 /** Paints a handle on the offscreen x,y. Adapted from ij.gui.Roi class. */
530 private void drawHandle(Graphics g, double x, double y) {
531 g.setColor(Color.black);
532 g.fillRect((int) ((x - srcRect.x) * magnification) - 1, (int) ((y - srcRect.y) * magnification) - 1, 3, 3);
533 g.setColor(Color.white);
534 g.drawRect((int) ((x - srcRect.x) * magnification) - 2, (int) ((y - srcRect.y) * magnification) - 2, 5, 5);
538 static protected BasicStroke DEFAULT_STROKE
= new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
);
539 static protected AffineTransform DEFAULT_AFFINE
= new AffineTransform();
541 static public void drawHandle(Graphics2D g
, double x
, double y
, Rectangle srcRect
, double magnification
) {
542 AffineTransform original
= g
.getTransform();
543 g
.setTransform(DEFAULT_AFFINE
);
544 Stroke st
= g
.getStroke();
545 g
.setStroke(DEFAULT_STROKE
);
547 g
.setColor(Color
.black
);
548 g
.fillRect((int) ((x
- srcRect
.x
) * magnification
) - 1, (int) ((y
- srcRect
.y
) * magnification
) - 1, 3, 3);
549 g
.setColor(Color
.white
);
550 g
.drawRect((int) ((x
- srcRect
.x
) * magnification
) - 2, (int) ((y
- srcRect
.y
) * magnification
) - 2, 5, 5);
553 g
.setTransform(original
);
557 private int x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
;
559 private boolean popup
= false;
561 private boolean locked
= false;
563 private int tmp_tool
= -1;
565 /** In world coordinates. */
566 protected Point last_popup
= null;
568 protected Point
consumeLastPopupPoint() {
569 Point p
= last_popup
;
574 public void mousePressed(MouseEvent me
) {
576 super.flags
= me
.getModifiers();
578 x_p
= x_d
= srcRect
.x
+ (int) (me
.getX() / magnification
); // offScreenX(me.getX());
579 y_p
= y_d
= srcRect
.y
+ (int) (me
.getY() / magnification
); // offScreenY(me.getY());
584 // ban if beyond bounds:
585 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
590 popup
= false; // not reset properly in macosx
591 if (Utils
.isPopupTrigger(me
)) {
593 last_popup
= new Point(x_p
, y_p
);
594 display
.getPopupMenu().show(this, me
.getX(), me
.getY());
601 int tool
= ProjectToolbar
.getToolId();
603 // pan with middle mouse like in inkscape
604 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
605 if (0 != (flags & InputEvent.BUTTON2_MASK))
607 if (me
.getButton() == MouseEvent
.BUTTON2
) {
609 ProjectToolbar
.setTool(Toolbar
.HAND
);
612 //Utils.log2("button: " + me.getButton() + " BUTTON2: " + MouseEvent.BUTTON2);
615 // stop animations when clicking (and on pushing ESC)
620 case Toolbar
.MAGNIFIER
:
621 if (me
.isAltDown()) zoomOut(me
.getX(), me
.getY());
622 else zoomIn(me
.getX(), me
.getY());
625 super.setupScroll(x_p
, y_p
); // offscreen coords.
629 if (input_disabled
) {
630 input_disabled2
= true;
631 Utils
.showMessage("Please wait while completing the task.\nOnly the glass and hand tool are enabled.");
632 return; // only zoom and pan are allowed
635 Displayable active
= display
.getActive();
637 if (isTransforming() && ProjectToolbar
.SELECT
!= tool
) {
638 Utils
.logAll("Notice: the 'Select' tool is not active!\n Activate the 'Select' tool to operate transformation modes.");
642 case ProjectToolbar
.PENCIL
:
643 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
644 Profile prof
= (Profile
) active
;
645 this.freehandProfile
= new FreeHandProfile(prof
);
646 freehandProfile
.mousePressed(x_p
, y_p
);
650 case Toolbar
.RECTANGLE
:
652 case Toolbar
.POLYGON
:
653 case Toolbar
.FREEROI
:
655 case Toolbar
.POLYLINE
:
656 case Toolbar
.FREELINE
:
659 // pass the mouse event to superclass ImageCanvas.
660 super.mousePressed(me
);
663 case Toolbar
.DROPPER
:
665 setDrawingColor(x_p
, y_p
, me
.isAltDown());
670 if (display
.isReadOnly()) return;
674 if (!isTransforming()) {
675 // edit a label, or add a new one
676 if (null == active
|| !active
.contains(x_p
, y_p
)) {
677 // find a Displayable to activate, if any
678 display
.choose(me
.getX(), me
.getY(), x_p
, y_p
, DLabel
.class);
679 active
= display
.getActive();
681 if (null != active
&& active
.isVisible() && active
instanceof DLabel
) {
683 ((DLabel
) active
).edit();
686 DLabel label
= new DLabel(display
.getProject(), " ", x_p
, y_p
);
687 display
.getLayer().add(label
);
694 // SPECIFIC for SELECT and above tools
696 // no ROIs allowed past this point
697 if (tool
>= ProjectToolbar
.SELECT
) imp
.killRoi();
700 Selection selection
= display
.getSelection();
701 if (isTransforming()) {
702 box
= display
.getMode().getRepaintBounds();
703 display
.getMode().mousePressed(me
, x_p
, y_p
, magnification
);
706 // select or deselect another active Displayable, or add it to the selection group:
707 if (ProjectToolbar
.SELECT
== tool
) {
708 display
.choose(me
.getX(), me
.getY(), x_p
, y_p
, me
.isShiftDown(), null);
710 active
= display
.getActive();
711 selection
= display
.getSelection();
713 if (null == active
|| !active
.isVisible()) return;
716 case ProjectToolbar
.SELECT
:
717 // gather initial box (for repainting purposes)
718 box
= display
.getMode().getRepaintBounds();
719 // check if the active is usable:
720 // check if the selection contains locked objects
721 if (selection
.isLocked()) {
725 display
.getMode().mousePressed(me
, x_p
, y_p
, magnification
);
727 default: // the PEN and PENCIL tools, and any other custom tool
728 display
.getLayerSet().addPreDataEditStep(active
);
729 box
= active
.getBoundingBox();
730 active
.mousePressed(me
, display
.getLayer(), x_p
, y_p
, magnification
);
731 invalidateVolatile();
736 public void mouseDragged(MouseEvent me
) {
738 super.flags
= me
.getModifiers();
742 // ban if beyond bounds:
743 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
747 if (ProjectToolbar
.SELECT
== ProjectToolbar
.getToolId() && locked
) {
748 Utils
.log2("Selection is locked.");
757 x_d
= srcRect
.x
+ (int) (me
.getX() / magnification
); // offscreen
758 y_d
= srcRect
.y
+ (int) (me
.getY() / magnification
);
764 int me_x
= me
.getX();
765 int me_y
= me
.getY();
766 if (me_x
< 0 || me_x
> this.getWidth() || me_y
< 0 || me_y
> this.getHeight()) {
772 int tool
= ProjectToolbar
.getToolId();
775 // pan with middle mouse like in inkscape
776 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
777 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
780 */ // so the above has been implemented as a temporary switch to the HAND tool at the mousePressed function.
783 case Toolbar
.MAGNIFIER
: // TODO : create a zooms-area tool
788 scroll(me
.getX(), me
.getY());
789 if (0 != srx
- srcRect
.x
|| 0 != sry
- srcRect
.y
) {
790 update_graphics
= true; // update the offscreen images.
791 display
.getNavigator().repaint(false);
797 if (input_disabled2
) return;
800 //Utils.log2("x_d,y_d : " + x_d + "," + y_d + " x_d_old, y_d_old : " + x_d_old + "," + y_d_old + " dx, dy : " + (x_d_old - x_d) + "," + (y_d_old - y_d));
802 // Code for Matthias' FreehandProfile (TODO this should be done on mousePressed, not on mouseDragged)
804 Displayable active
= display
.getActive();
805 if (null != active
&& active
.getClass() == Profile
.class) {
808 r
= new Robot(this.getGraphicsConfiguration().getDevice());
810 } catch (AWTException e
) {
816 case ProjectToolbar
.PENCIL
:
817 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
818 if (freehandProfile
== null)
819 return; // starting painting out of the DisplayCanvas border
820 double dx
= x_d
- x_d_old
;
821 double dy
= y_d
- y_d_old
;
822 freehandProfile
.mouseDragged(me
, x_d
, y_d
, dx
, dy
);
824 // Point screenLocation = getLocationOnScreen();
825 // mousePos[0] += screenLocation.x;
826 // mousePos[1] += screenLocation.y;
827 // r.mouseMove( mousePos[0], mousePos[1]);
831 case Toolbar
.RECTANGLE
:
833 case Toolbar
.POLYGON
:
834 case Toolbar
.FREEROI
:
836 case Toolbar
.POLYLINE
:
837 case Toolbar
.FREELINE
:
840 // pass the mouse event to superclass ImageCanvas.
841 super.mouseDragged(me
);
845 // no ROIs beyond this point
846 if (tool
>= ProjectToolbar
.SELECT
) imp
.killRoi();
850 if (display
.isReadOnly()) return;
852 if (null != active
&& active
.isVisible()) {
853 // prevent dragging beyond the layer limits
854 if (display
.getLayer().contains(x_d
, y_d
, 1)) {
857 case ProjectToolbar
.SELECT
:
858 display
.getMode().mouseDragged(me
, x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
859 box2
= display
.getMode().getRepaintBounds();
861 // repaint all Displays (where it was and where it is now, hence the sum of both boxes):
862 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, false, active
.isLinked() || active
.getClass() == Patch
.class);
863 // box for next mouse dragged iteration
867 active
.mouseDragged(me
, display
.getLayer(), x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
868 // the line above must repaint on its own
872 //beyond_srcRect = true;
873 Utils
.log("DisplayCanvas.mouseDragged: preventing drag beyond layer limits.");
875 } else if (display
.getMode() instanceof ManualAlignMode
876 || display
.getMode() instanceof InspectPatchTrianglesMode
) {
877 if (display
.getLayer().contains(x_d
, y_d
, 1)) {
878 if (tool
>= ProjectToolbar
.SELECT
) {
879 display
.getMode().mouseDragged(me
, x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
885 public void mouseReleased(MouseEvent me
) {
887 super.flags
= me
.getModifiers();
889 boolean dragging2
= dragging
;
891 boolean locked2
= locked
;
899 // ban if beyond bounds:
900 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
904 int tool
= ProjectToolbar
.getToolId();
906 // pan with middle mouse like in inkscape
907 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
908 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
914 case Toolbar
.MAGNIFIER
:
915 // display.updateInDatabase("srcRect"); // TODO if the display.frame
916 // is shrinked, the pack() in the zoom methods will also call the
917 // updateInDatabase("srcRect") (so it's going to be done twice)
918 display
.updateFrameTitle();
921 display
.updateInDatabase("srcRect");
922 if (-1 != tmp_tool
) {
923 ProjectToolbar
.setTool(tmp_tool
);
926 if (!dragging2
) repaint(true); // TEMPORARY just to allow fixing bad screen when simply cliking with the hand
927 display
.getMode().srcRectUpdated(srcRect
, magnification
);
931 if (input_disabled2
) {
932 input_disabled2
= false; // reset
937 if (ProjectToolbar
.SELECT
== tool
) {
939 Utils
.showMessage("Selection is locked!");
945 // pan with middle mouse like in inkscape
946 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
947 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
952 super.flags
&= ~InputEvent
.BUTTON1_MASK
; // make sure button 1 bit is not set
953 super.flags
&= ~InputEvent
.BUTTON2_MASK
; // make sure button 2 bit is not set
954 super.flags
&= ~InputEvent
.BUTTON3_MASK
; // make sure button 3 bit is not set
956 int x_r
= srcRect
.x
+ (int)(me
.getX() / magnification
);
957 int y_r
= srcRect
.y
+ (int)(me
.getY() / magnification
);
960 if (beyond_srcRect) {
961 // Artificial release on the last dragged point
970 Displayable active
= display
.getActive();
973 case ProjectToolbar
.PENCIL
:
974 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
975 if (freehandProfile
== null)
976 return; // starting painting out of the DisplayCanvas boarder
977 freehandProfile
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
978 freehandProfile
= null;
980 Selection selection
= display
.getSelection();
981 selection
.updateTransform(display
.getActive());
982 Display
.repaint(display
.getLayer(), selection
.getBox(), Selection
.PADDING
); // repaints the navigator as well
986 case Toolbar
.RECTANGLE
:
988 case Toolbar
.POLYGON
:
989 case Toolbar
.FREEROI
:
991 case Toolbar
.POLYLINE
:
992 case Toolbar
.FREELINE
:
995 // pass the mouse event to superclass ImageCanvas.
996 super.mouseReleased(me
);
998 // return; // replaced by #SET_ROI
1001 final Roi roi
= imp
.getRoi();
1004 if (display
.isReadOnly()) return;
1006 if (tool
>= ProjectToolbar
.SELECT
) {
1007 if (null != roi
) imp
.killRoi();
1008 } else return; // #SET_ROI
1010 Selection selection
= display
.getSelection();
1014 display
.getMode().mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
1015 box
.add(display
.getMode().getRepaintBounds());
1016 Display
.repaint(display
.getLayer(), box
, Selection
.PADDING
); // repaints the navigator as well
1017 Display
.snap((Patch
)active
);
1023 if (null != active
&& active
.isVisible()) {
1025 case ProjectToolbar
.SELECT
:
1026 display
.getMode().mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
1027 box
.add(display
.getMode().getRepaintBounds());
1028 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, !isTransforming(), active
.isLinked() || active
.getClass() == Patch
.class); // does not repaint the navigator
1030 case ProjectToolbar
.PENCIL
:
1031 case ProjectToolbar
.PEN
:
1032 case ProjectToolbar
.BRUSH
:
1033 active
.mouseReleased(me
, display
.getLayer(), x_p
, y_p
, x_d
, y_d
, x_r
, y_r
); // active, not selection (Selection only handles transforms, not active's data editions)
1034 // update active's bounding box
1035 selection
.updateTransform(active
);
1036 box
.add(selection
.getBox());
1037 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, !isTransforming(), active
.isLinked() || active
.getClass() == Patch
.class); // does not repaint the navigator
1038 //if (!active.getClass().equals(AreaList.class)) Display.repaint(display.getLayer(), box, Selection.PADDING); // repaints the navigator as well
1039 // TODO: this last repaint call is unnecessary, if the box was properly repainted on mouse drag for Profile etc.
1041 if (null != old_brush_box
) {
1042 repaint(old_brush_box
, 0, false);
1043 old_brush_box
= null; // from mouseMoved
1045 // The current state:
1046 display
.getLayerSet().addDataEditStep(active
);
1052 private boolean mouse_in
= false;
1054 public void mouseEntered(MouseEvent me
) {
1056 // try to catch focus if the JFrame is front most
1057 if (display
.isActiveWindow() && !this.hasFocus()) {
1058 this.requestFocus();
1060 // bring dragged point to mouse pointer
1061 // TODO doesn't work as expected.
1063 Displayable active = display.getActive();
1064 int x = offScreenX(me.getX());
1065 int y = offScreenY(me.getY());
1066 if (null != active) {
1067 active.snapTo(x, y, x_p, y_p);
1068 x_p = x_d = x_d_old = x;
1069 y_p = y_d = y_d_old = y;
1072 //Utils.log2("mouseEntered x,y: " + offScreenX(me.getX()) + "," + offScreenY(me.getY()));
1075 public void mouseExited(MouseEvent me
) {
1077 // paint away the circular brush if any
1078 if (ProjectToolbar
.getToolId() == ProjectToolbar
.BRUSH
) {
1079 Displayable active
= display
.getActive();
1080 if (null != active
&& active
.isVisible() && AreaContainer
.class.isInstance(active
)) {
1081 if (null != old_brush_box
) {
1082 this.repaint(old_brush_box
, 0);
1083 old_brush_box
= null;
1089 /** Sets the cursor based on the current tool and cursor location. */
1090 public void setCursor(int sx
, int sy
, int ox
, int oy
) {
1091 // copy of ImageCanvas.setCursor without the win==null
1094 Roi roi
= imp
.getRoi();
1096 * ImageWindow win = imp.getWindow(); if (win==null) return;
1098 if (IJ
.spaceBarDown()) {
1099 setCursor(handCursor
);
1102 switch (Toolbar
.getToolId()) {
1103 case Toolbar
.MAGNIFIER
:
1104 if (IJ
.isMacintosh())
1105 setCursor(defaultCursor
);
1107 setCursor(moveCursor
);
1110 setCursor(handCursor
);
1112 case ProjectToolbar
.SELECT
:
1113 case ProjectToolbar
.PENCIL
:
1114 setCursor(defaultCursor
);
1116 default: // selection tool
1118 if (roi
!= null && roi
.getState() != Roi
.CONSTRUCTING
&& roi
.isHandle(sx
, sy
) >= 0)
1119 setCursor(handCursor
);
1120 else if (Prefs
.usePointerCursor
|| (roi
!= null && roi
.getState() != Roi
.CONSTRUCTING
&& roi
.contains(ox
, oy
)))
1121 setCursor(defaultCursor
);
1123 setCursor(crosshairCursor
);
1127 /** Set the srcRect - used by the DisplayNavigator. */
1128 protected void setSrcRect(int x
, int y
, int width
, int height
) {
1129 if (width
< 1) width
= 1;
1130 if (height
< 1) height
= 1;
1131 this.srcRect
.setRect(x
, y
, width
, height
);
1132 display
.updateInDatabase("srcRect");
1133 display
.getMode().srcRectUpdated(srcRect
, magnification
);
1136 public void setDrawingSize(int new_width
, int new_height
) {
1137 adjustSrcRect(new_width
, new_height
);
1138 super.setDrawingSize(new_width
, new_height
);
1141 /** Adjust srcRect and internal variables to the canvas' bounds. */
1142 public void adjustDimensions() {
1143 final Rectangle r
= getBounds();
1144 adjustSrcRect(r
.width
, r
.height
);
1145 super.dstWidth
= r
.width
;
1146 super.dstHeight
= r
.height
;
1149 /** Adjust srcRect to new dimensions. */
1150 public void adjustSrcRect(int new_width
, int new_height
) {
1151 double mag
= super.getMagnification();
1152 // This method is very important! Make it fit perfectly.
1153 if (srcRect
.width
* mag
< new_width
) {
1155 if (new_width
> imageWidth
* mag
) {
1158 srcRect
.width
= imageWidth
;
1160 srcRect
.width
= (int) Math
.ceil(new_width
/ mag
);
1161 if (srcRect
.x
+ srcRect
.width
> imageWidth
) {
1162 srcRect
.x
= imageWidth
- srcRect
.width
;
1167 srcRect
.width
= (int) Math
.ceil(new_width
/ mag
);
1169 if (srcRect
.height
* mag
< new_height
) {
1171 if (new_height
> imageHeight
* mag
) {
1174 srcRect
.height
= imageHeight
;
1176 srcRect
.height
= (int) Math
.ceil(new_height
/ mag
);
1177 if (srcRect
.y
+ srcRect
.height
> imageHeight
) {
1178 srcRect
.y
= imageHeight
- srcRect
.height
;
1183 srcRect
.height
= (int) Math
.ceil(new_height
/ mag
);
1187 private void zoomIn2(int x
, int y
) {
1188 // copy of ImageCanvas.zoomIn except for the canEnlarge is different and
1189 // there's no call to the non-existing ImageWindow
1190 if (magnification
>= 32)
1192 double newMag
= getHigherZoomLevel2(magnification
);
1194 // zoom at point: correct mag drift
1195 int cx
= getWidth() / 2;
1196 int cy
= getHeight() / 2;
1197 int dx
= (int)(((x
- cx
) * magnification
) / newMag
);
1198 int dy
= (int)(((y
- cy
) * magnification
) / newMag
);
1202 // Adjust the srcRect to the new dimensions
1203 int w
= (int) Math
.round(dstWidth
/ newMag
);
1204 if (w
* newMag
< dstWidth
)
1208 int h
= (int) Math
.round(dstHeight
/ newMag
);
1209 if (h
* newMag
< dstHeight
)
1211 if (h
> imageHeight
)
1215 final Rectangle r
= new Rectangle(x
- w
/ 2, y
- h
/ 2, w
, h
);
1220 if (r
.x
+ w
> imageWidth
)
1221 r
.x
= imageWidth
- w
;
1222 if (r
.y
+ h
> imageHeight
)
1223 r
.y
= imageHeight
- h
;
1228 setMagnification(newMag
);
1229 display
.updateInDatabase("srcRect");
1230 display
.repaintAll2(); // this repaint includes this canvas's repaint as well, but also the navigator, etc. // repaint();
1233 private void zoomOut2(int x
, int y
) {
1234 //if (magnification <= 0.03125)
1236 double newMag
= getLowerZoomLevel2(magnification
);
1238 // zoom at point: correct mag drift
1239 int cx
= getWidth() / 2;
1240 int cy
= getHeight() / 2;
1241 int dx
= (int)(((x
- cx
) * magnification
) / newMag
);
1242 int dy
= (int)(((y
- cy
) * magnification
) / newMag
);
1246 if (imageWidth
* newMag
> dstWidth
|| imageHeight
* newMag
> dstHeight
) {
1247 int w
= (int) Math
.round(dstWidth
/ newMag
);
1248 if (w
* newMag
< dstWidth
)
1250 int h
= (int) Math
.round(dstHeight
/ newMag
);
1251 if (h
* newMag
< dstHeight
)
1255 Rectangle r
= new Rectangle(x
- w
/ 2, y
- h
/ 2, w
, h
);
1260 if (r
.x
+ w
> imageWidth
)
1261 r
.x
= imageWidth
- w
;
1262 if (r
.y
+ h
> imageHeight
)
1263 r
.y
= imageHeight
- h
;
1266 // Shrink srcRect, but NOT the dstWidth,dstHeight of the canvas, which remain the same:
1267 srcRect
= new Rectangle(0, 0, imageWidth
, imageHeight
);
1270 setMagnification(newMag
);
1271 display
.repaintAll2(); // this repaint includes this canvas's repaint, but updates the navigator without update_graphics
1272 display
.updateInDatabase("srcRect");
1275 /** The minimum amout of pixels allowed for width or height when zooming out. */
1276 static private final int MIN_DIMENSION
= 10; // pixels
1278 /** Enable zooming out up to the point where the display becomes 10 pixels in width or height. */
1279 protected double getLowerZoomLevel2(final double currentMag
) {
1280 // if it is 1/72 or lower, then:
1281 if (Math
.abs(currentMag
- 1/72.0) < 0.00000001 || currentMag
< 1/72.0) { // lowest zoomLevel in ImageCanvas is 1/72.0
1282 // find nearest power of two under currentMag
1283 // start at level 7, which is 1/128
1285 double scale
= currentMag
;
1286 while (scale
* srcRect
.width
> MIN_DIMENSION
&& scale
* srcRect
.height
> MIN_DIMENSION
) {
1287 scale
= 1 / Math
.pow(2, level
);
1288 // if not equal and actually smaller, break:
1289 if (Math
.abs(scale
- currentMag
) != 0.00000001 && scale
< currentMag
) break;
1294 return ImageCanvas
.getLowerZoomLevel(currentMag
);
1297 protected double getHigherZoomLevel2(final double currentMag
) {
1298 // if it is not 1/72 and its lower, then:
1299 if (Math
.abs(currentMag
- 1/72.0) > 0.00000001 && currentMag
< 1/72.0) { // lowest zoomLevel in ImageCanvas is 1/72.0
1300 // find nearest power of two above currentMag
1301 // start at level 14, which is 0.00006103515625 (0.006 %)
1302 int level
= 14; // this value may be increased in the future
1303 double scale
= currentMag
;
1304 while (level
>= 0) {
1305 scale
= 1 / Math
.pow(2, level
);
1306 if (scale
> currentMag
) break;
1311 return ImageCanvas
.getHigherZoomLevel(currentMag
);
1317 * // OBSOLETE: modified ij.gui.ImageCanvas directly
1318 public void mouseMoved(MouseEvent e) { if (IJ.getInstance()==null) return; int sx =
1319 * e.getX(); int sy = e.getY(); int ox = offScreenX(sx); int oy =
1320 * offScreenY(sy); flags = e.getModifiers(); setCursor(sx, sy, ox, oy);
1321 * IJ.setInputEvent(e); Roi roi = imp.getRoi(); if (roi!=null &&
1322 * (roi.getType()==Roi.POLYGON || roi.getType()==Roi.POLYLINE ||
1323 * roi.getType()==Roi.ANGLE) && roi.getState()==roi.CONSTRUCTING) {
1324 * PolygonRoi pRoi = (PolygonRoi)roi; pRoi.handleMouseMove(ox, oy); } else {
1325 * if (ox<imageWidth && oy<imageHeight) { //ImageWindow win =
1326 * imp.getWindow(); //if (win!=null) win.mouseMoved(ox, oy);
1327 * imp.mouseMoved(ox, oy); } else IJ.showStatus(""); } }
1330 private Rectangle old_brush_box
= null;
1332 private MouseMovedThread mouse_moved
= new MouseMovedThread();
1334 private class MouseMovedThread
extends Thread
{
1335 private volatile MouseEvent me
= null;
1336 MouseMovedThread() {
1337 super("T2-mouseMoved");
1339 setPriority(Thread
.NORM_PRIORITY
);
1342 void dispatch(MouseEvent me
) {
1343 //Utils.log2("before");
1344 synchronized (this) {
1352 synchronized (this) { notifyAll(); }
1355 while (!isInterrupted()) {
1356 MouseEvent me
= this.me
;
1358 try { mouseMoved(me
); } catch (Exception e
) { IJError
.print(e
); }
1360 // Wait only if the event has not changed
1361 synchronized (this) {
1362 if (me
== this.me
) {
1363 // Release the pointer
1366 if (isInterrupted()) return;
1367 // Wait until there is a new event
1368 try { wait(); } catch (Exception e
) {}
1373 private void mouseMoved(MouseEvent me
) {
1374 if (null == me
) return;
1376 if (input_disabled
|| display
.getMode().isDragging()) return;
1378 xMouse
= (int)(me
.getX() / magnification
) + srcRect
.x
;
1379 yMouse
= (int)(me
.getY() / magnification
) + srcRect
.y
;
1380 final Displayable active
= display
.getActive();
1382 // only when no mouse buttons are down
1383 final int flags
= DisplayCanvas
.super.flags
;
1384 if (0 == (flags
& InputEvent
.BUTTON1_MASK
)
1385 /* && 0 == (flags & InputEvent.BUTTON2_MASK) */ // this is the alt key down ..
1386 && 0 == (flags
& InputEvent
.BUTTON3_MASK
)
1387 //if (me.getButton() == MouseEvent.NOBUTTON
1388 && null != active
&& active
.isVisible() && AreaContainer
.class.isInstance(active
)) {
1389 final int tool
= ProjectToolbar
.getToolId();
1391 if (ProjectToolbar
.BRUSH
== tool
) {
1392 // repaint area where the brush circle is
1393 int brushSize
= ProjectToolbar
.getBrushSize() +2; // +2 padding
1394 r
= new Rectangle( xMouse
- brushSize
/2,
1395 yMouse
- brushSize
/2,
1398 } else if (ProjectToolbar
.PENCIL
== tool
|| ProjectToolbar
.WAND
== tool
) {
1399 // repaint area where the fast-marching box is
1400 r
= new Rectangle( xMouse
- Segmentation
.fmp
.width
/2 - 2,
1401 yMouse
- Segmentation
.fmp
.height
/2 - 2,
1402 Segmentation
.fmp
.width
+ 4,
1403 Segmentation
.fmp
.height
+ 4 );
1406 Rectangle copy
= (Rectangle
)r
.clone();
1407 if (null != old_brush_box
) r
.add(old_brush_box
);
1408 old_brush_box
= copy
;
1409 repaint(r
, 1); // padding because of painting rounding which would live dirty trails
1413 if (me
.isShiftDown()) {
1414 // Print a comma-separated list of objects under the mouse pointer
1415 final Layer layer
= DisplayCanvas
.this.display
.getLayer();
1416 final List
<Displayable
> al
= getDisplayablesUnderMouse(me
);
1417 if (0 == al
.size()) {
1418 Utils
.showStatus("", false);
1421 final StringBuilder sb
= new StringBuilder();
1422 final Project pr
= layer
.getProject();
1423 for (Displayable d
: al
) sb
.append(pr
.getShortMeaningfulTitle(d
)).append(", ");
1424 sb
.setLength(sb
.length()-2);
1425 Utils
.showStatus(sb
.toString(), false);
1427 // For very large images, the Patch.getPixel can take even half a minute
1428 // to do the pixel grab operation.
1429 //DisplayCanvas.super.mouseMoved(me);
1430 // Instead, find out over what are we
1431 final List
<Displayable
> under
= getDisplayablesUnderMouse(me
);
1432 final Calibration cal
= display
.getLayerSet().getCalibration();
1433 if (under
.isEmpty()) {
1434 Utils
.showStatus("x=" + (int)(xMouse
* cal
.pixelWidth
) + " " + cal
.getUnit()
1435 + ", y=" + (int)(yMouse
* cal
.pixelHeight
) + " " + cal
.getUnit());
1438 final Displayable top
= under
.get(0);
1440 "x=" + (int)(xMouse
* cal
.pixelWidth
) + " " + cal
.getUnit()
1441 + ", y=" + (int)(yMouse
* cal
.pixelHeight
) + " " + cal
.getUnit();
1442 if (top
.getClass() == Patch
.class) {
1443 final Patch patch
= (Patch
)top
;
1444 final int[] p
= new int[4];
1445 BufferedImage offsc
;
1446 synchronized (offscreen_lock
) {
1449 if (null == offsc
) return;
1451 PixelGrabber pg
= new PixelGrabber(offsc
, me
.getX(), me
.getY(), 1, 1, p
, 0, offsc
.getWidth(null));
1453 } catch (InterruptedException ie
) {
1456 } catch (Throwable t
) {
1457 // The offscreen might have been flushed. Just ignore; pixel value will be reported next.
1460 patch
.approximateTransferPixel(p
);
1462 switch (patch
.getType()) {
1463 case ImagePlus
.GRAY16
:
1464 case ImagePlus
.GRAY8
:
1467 case ImagePlus
.GRAY32
:
1468 msg
+= Float
.intBitsToFloat(p
[0]);
1470 case ImagePlus
.COLOR_RGB
:
1471 case ImagePlus
.COLOR_256
:
1472 msg
+= "(" + p
[0] + "," + p
[1] + "," + p
[2] + ")";
1475 msg
+= " [Patch #" + patch
.getId() + "]";
1477 final Color c
= top
.getColor();
1478 msg
+= ", value=[" + c
.getRed() + "," + c
.getGreen() + "," + c
.getBlue() + "] [" + Project
.getName(top
.getClass()) + " #" + top
.getId() + "]";
1480 Utils
.showStatus(msg
);
1485 /** See {@link DisplayCanvas#getDisplayablesUnderMouse(MouseEvent)}. */
1486 public List
<Displayable
> getDisplayablesUnderMouse() {
1487 return getDisplayablesUnderMouse(new MouseEvent(this, -1, 0, 0, xMouse
, yMouse
, 1, false));
1490 /** Return the list of Displayable objects under the mouse,
1491 * sorted by proper stack order. */
1492 public List
<Displayable
> getDisplayablesUnderMouse(MouseEvent me
) {
1493 final Layer layer
= display
.getLayer();
1494 final int x_p
= offScreenX(me
.getX()),
1495 y_p
= offScreenY(me
.getY());
1496 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>(layer
.getParent().findZDisplayables(layer
, x_p
, y_p
, true));
1497 Collections
.reverse(al
);
1498 final ArrayList
<Displayable
> al2
= new ArrayList
<Displayable
>(layer
.find(x_p
, y_p
, true));
1499 Collections
.reverse(al2
);
1504 public boolean isDragging() {
1505 return display
.getMode().isDragging();
1508 public void mouseMoved(final MouseEvent me
) {
1509 super.flags
= me
.getModifiers();
1510 mouse_moved
.dispatch(me
);
1513 /** Zoom in using the current mouse position, or the center if the mouse is out. */
1514 public void zoomIn() {
1515 if (xMouse
< 0 || screenX(xMouse
) > dstWidth
|| yMouse
< 0 || screenY(yMouse
) > dstHeight
) {
1516 zoomIn(dstWidth
/2, dstHeight
/2);
1518 zoomIn(screenX(xMouse
), screenY(yMouse
));
1522 /** Overriding to repaint the DisplayNavigator as well. */
1523 public void zoomIn(int x
, int y
) {
1524 update_graphics
= true; // update the offscreen images.
1528 /** Zoom out using the current mouse position, or the center if the mouse is out. */
1529 public void zoomOut() {
1530 if (xMouse
< 0 || screenX(xMouse
) > dstWidth
|| yMouse
< 0 || screenY(yMouse
) > dstHeight
) {
1531 zoomOut(dstWidth
/2, dstHeight
/2);
1532 } else zoomOut(screenX(xMouse
), screenY(yMouse
));
1535 /** Overriding to repaint the DisplayNavigator as well. */
1536 public void zoomOut(int x
, int y
) {
1537 update_graphics
= true; // update the offscreen images.
1541 /** Center the srcRect around the given object(s) bounding box, zooming if necessary,
1542 * so that the given r becomes a rectangle centered in the srcRect and zoomed out by a factor of 2. */
1543 public void showCentered(Rectangle r
) {
1544 // multiply bounding box dimensions by two
1546 r
.y
-= r
.height
/ 2;
1548 r
.height
+= r
.height
;
1549 // compute target magnification
1550 double magn
= getWidth() / (double)(r
.width
> r
.height ? r
.width
: r
.height
);
1554 /** Show the given r as the srcRect (or as much of it as possible) at the given magnification. */
1555 public void center(Rectangle r
, double magn
) {
1556 // bring bounds within limits of the layer and the canvas' drawing size
1557 double lw
= display
.getLayer().getLayerWidth();
1558 double lh
= display
.getLayer().getLayerHeight();
1559 int cw
= (int) (getWidth() / magn
); // canvas dimensions in offscreen coords
1560 int ch
= (int) (getHeight() / magn
);
1563 // fit to canvas drawing size:
1564 r
.y
+= (r
.height
- ch
) / 2;
1567 // place within layer bounds
1568 if (r
.x
< 0) r
.x
= 0;
1569 if (r
.y
< 0) r
.y
= 0;
1574 if (r
.height
> lh
) {
1578 if (r
.x
+ r
.width
> lw
) r
.x
= (int)(lw
- cw
);
1579 if (r
.y
+ r
.height
> lh
) r
.y
= (int)(lh
- ch
);
1580 // compute magn again, since the desired width may have changed:
1581 magn
= getWidth() / (double)r
.width
;
1583 // set magnification and srcRect
1585 try { Thread
.sleep(200); } catch (Exception e
) {} // swing ... waiting for the display.pack()
1586 update_graphics
= true;
1587 RT
.paint(null, update_graphics
);
1588 display
.updateInDatabase("srcRect");
1589 display
.updateFrameTitle();
1590 display
.getNavigator().repaint(false);
1593 /** Repaint as much as the bounding box around the given Displayable. If the Displayable is null, the entire canvas is repainted, remaking the offscreen images. */
1594 public void repaint(Displayable d
) {
1599 * Repaint as much as the bounding box around the given Displayable plus the
1600 * extra padding. If the Displayable is null, the entire canvas is
1601 * repainted, remaking the offscreen images.
1603 public void repaint(Displayable displ
, int extra
) {
1604 repaint(displ
, extra
, update_graphics
);
1606 public void repaint(Displayable displ
, int extra
, boolean update_graphics
) {
1607 if (null != displ
) {
1608 Rectangle r
= displ
.getBoundingBox();
1609 r
.x
= (int) ((r
.x
- srcRect
.x
) * magnification
) - extra
;
1610 r
.y
= (int) ((r
.y
- srcRect
.y
) * magnification
) - extra
;
1611 r
.width
= (int) Math
.ceil(r
.width
* magnification
) + extra
+ extra
;
1612 r
.height
= (int) Math
.ceil(r
.height
* magnification
) + extra
+ extra
;
1613 invalidateVolatile();
1614 RT
.paint(r
, update_graphics
);
1622 * Repaint the clip corresponding to the sum of all boundingboxes of
1623 * Displayable objects in the hashset.
1625 // it is assumed that the linked objects are close to each other, otherwise
1626 // the clip rectangle grows enormously.
1627 public void repaint(final HashSet
<Displayable
> hs
) {
1628 if (null == hs
) return;
1630 final Layer dl
= display
.getLayer();
1631 for (final Displayable d
: hs
) {
1632 if (d
.getLayer() == dl
) {
1633 if (null == r
) r
= d
.getBoundingBox();
1634 else r
.add(d
.getBoundingBox());
1638 //repaint(r.x, r.y, r.width, r.height);
1639 invalidateVolatile();
1640 RT
.paint(r
, update_graphics
);
1645 * Repaint the given offscreen Rectangle after transforming its data on the fly to the
1646 * srcRect and magnification of this DisplayCanvas. The Rectangle is not
1649 public void repaint(final Rectangle r
, final int extra
) {
1650 invalidateVolatile();
1652 //Utils.log2("DisplayCanvas.repaint(Rectangle, int) warning: null r");
1653 RT
.paint(null, update_graphics
);
1656 // repaint((int) ((r.x - srcRect.x) * magnification) - extra, (int) ((r.y - srcRect.y) * magnification) - extra, (int) Math .ceil(r.width * magnification) + extra + extra, (int) Math.ceil(r.height * magnification) + extra + extra);
1657 RT
.paint(new Rectangle((int) ((r
.x
- srcRect
.x
) * magnification
) - extra
, (int) ((r
.y
- srcRect
.y
) * magnification
) - extra
, (int) Math
.ceil(r
.width
* magnification
) + extra
+ extra
, (int) Math
.ceil(r
.height
* magnification
) + extra
+ extra
), update_graphics
);
1661 * Repaint the given Rectangle after transforming its data on the fly to the
1662 * srcRect and magnification of this DisplayCanvas. The Rectangle is not
1664 * @param box The rectangle to repaint
1665 * @param extra The extra outbound padding to add to the rectangle
1666 * @param update_graphics Whether to recreate the offscreen images or not
1668 public void repaint(Rectangle box
, int extra
, boolean update_graphics
) {
1669 this.update_graphics
= update_graphics
;
1670 repaint(box
, extra
);
1673 /** Repaint everything, updating offscreen graphics if so specified. */
1674 public void repaint(final boolean update_graphics
) {
1675 this.update_graphics
= update_graphics
| this.update_graphics
;
1676 invalidateVolatile();
1677 RT
.paint(null, this.update_graphics
);
1680 /** Overridden to multithread. This method is here basically to enable calls to the FakeImagePlus.draw from the HAND and other tools to repaint properly.*/
1681 public void repaint() {
1682 //Utils.log2("issuing thread");
1683 invalidateVolatile();
1684 RT
.paint(null, update_graphics
);
1687 /** Overridden to multithread. */
1688 /* // saved as unoveridden to make sure there are no infinite thread loops when calling super in buggy JVMs
1689 public void repaint(long ms, int x, int y, int width, int height) {
1690 RT.paint(new Rectangle(x, y, width, height), update_graphics);
1694 /** Overridden to multithread. */
1695 public void repaint(int x
, int y
, int width
, int height
) {
1696 invalidateVolatile();
1697 RT
.paint(new Rectangle(x
, y
, width
, height
), update_graphics
);
1700 public void setUpdateGraphics(boolean b
) {
1701 update_graphics
= b
;
1704 /** Release offscreen images and stop threads. */
1705 public void flush() {
1706 // cleanup update graphics thread if any
1708 synchronized (offscreen_lock
) {
1709 if (null != offscreen
) {
1713 update_graphics
= true;
1714 for (final BufferedImage bi
: to_flush
) bi
.flush();
1719 synchronized (this) { if (null != animator
) animator
.shutdownNow(); }
1721 } catch (Exception e
) {}
1725 public void destroy() {
1727 WindowManager
.setTempCurrentImage(imp
); // the FakeImagePlus
1728 WindowManager
.removeWindow(fake_win
); // the FakeImageWindow
1731 public boolean applyTransform() {
1732 boolean b
= display
.getMode().apply();
1734 display
.setMode(new DefaultMode(display
));
1740 public boolean isTransforming() {
1741 // TODO this may have to change if modes start getting used for a task other than transformation.
1742 // Perhaps "isTransforming" will have to broaden its meaning to "isNotDefaultMode"
1743 return display
.getMode().getClass() != DefaultMode
.class;
1746 public void cancelTransform() {
1747 display
.getMode().cancel();
1748 display
.setMode(new DefaultMode(display
));
1752 private int last_keyCode
= KeyEvent
.VK_ESCAPE
;
1753 private boolean tagging
= false;
1755 public void keyPressed(KeyEvent ke
) {
1757 Displayable active
= display
.getActive();
1759 if (null != freehandProfile
1760 && ProjectToolbar
.getToolId() == ProjectToolbar
.PENCIL
1761 && ke
.getKeyCode() == KeyEvent
.VK_ESCAPE
1762 && null != freehandProfile
)
1764 freehandProfile
.abort();
1769 final int keyCode
= ke
.getKeyCode();
1772 // Enable tagging system for any alphanumeric key:
1773 if (!input_disabled
&& null != active
&& active
instanceof Tree
<?
> && ProjectToolbar
.isDataEditTool(ProjectToolbar
.getToolId())) {
1775 if (KeyEvent
.VK_0
== keyCode
&& KeyEvent
.VK_0
!= last_keyCode
) {
1776 // do nothing: keep tagging as true
1778 // last step of tagging: a char after t or after t and a number (and the char itself can be a number)
1781 active
.keyPressed(ke
);
1783 } else if (KeyEvent
.VK_T
== keyCode
) {
1785 active
.keyPressed(ke
);
1790 last_keyCode
= keyCode
;
1795 if (ke
.isConsumed()) return;
1798 * TODO screen editor ... TEMPORARY if (active instanceof DLabel) {
1799 * active.keyPressed(ke); ke.consume(); return; }
1802 if (!zoom_and_pan
) {
1803 if (KeyEvent
.VK_ESCAPE
== keyCode
) {
1809 final int keyChar
= ke
.getKeyChar();
1811 boolean used
= false;
1830 ke
.consume(); // otherwise ImageJ would use it!
1834 if (input_disabled
) {
1835 if (KeyEvent
.VK_ESCAPE
== keyCode
) {
1836 // cancel last job if any
1837 if (Utils
.checkYN("Really cancel job?")) {
1838 display
.getProject().getLoader().quitJob(null);
1839 display
.repairGUI();
1843 return; // only zoom is enabled, above
1846 if (KeyEvent
.VK_S
== keyCode
&& 0 == ke
.getModifiers() && display
.getProject().getLoader().isAsynchronous()) {
1847 display
.getProject().getLoader().saveTask(display
.getProject(), "Save");
1850 } else if (KeyEvent
.VK_F
== keyCode
&& Utils
.isControlDown(ke
)) {
1851 Search
.showWindow();
1856 // if display is not read-only, check for other keys:
1859 case ',': // select next Layer up
1860 display
.previousLayer(ke
.getModifiers()); // repaints as well
1864 case '.': // select next Layer down
1865 display
.nextLayer(ke
.getModifiers());
1870 if (null == active
&& null != imp
.getRoi() && KeyEvent
.VK_A
!= keyCode
) { // control+a and a roi should select under roi
1871 IJ
.getInstance().keyPressed(ke
);
1875 // end here if display is read-only
1876 if (display
.isReadOnly()) {
1878 display
.repaintAll();
1882 if (KeyEvent
.VK_ENTER
== keyCode
) {
1883 if (isTransforming()) {
1888 IJ
.getInstance().toFront();
1894 // check preconditions (or the keys are meaningless). Allow 'enter' to
1895 // bring forward the ImageJ window, and 'v' to paste a patch.
1896 /*if (null == active && KeyEvent.VK_ENTER != keyCode && KeyEvent.VK_V != keyCode && KeyEvent) {
1900 Layer layer
= display
.getLayer();
1902 final int mod
= ke
.getModifiers();
1905 case KeyEvent
.VK_COMMA
:
1906 case 0xbc: // select next Layer up
1907 display
.nextLayer(ke
.getModifiers());
1909 case KeyEvent
.VK_PERIOD
:
1910 case 0xbe: // select next Layer down
1911 display
.previousLayer(ke
.getModifiers());
1914 // UNDO: shift+z or ctrl+z
1915 if (0 == (mod ^ Event
.SHIFT_MASK
) || 0 == (mod ^ Utils
.getControlModifier())) {
1916 Bureaucrat
.createAndStart(new Worker
.Task("Undo") { public void exec() {
1917 if (isTransforming()) display
.getMode().undoOneStep();
1918 else display
.getLayerSet().undoOneStep();
1919 Display
.repaint(display
.getLayerSet());
1920 }}, display
.getProject());
1922 // REDO: alt+z or ctrl+shift+z
1923 } else if (0 == (mod ^ Event
.ALT_MASK
) || 0 == (mod ^
(Event
.SHIFT_MASK
| Utils
.getControlModifier())) ) {
1924 Bureaucrat
.createAndStart(new Worker
.Task("Redo") { public void exec() {
1925 if (isTransforming()) display
.getMode().redoOneStep();
1926 else display
.getLayerSet().redoOneStep();
1927 Display
.repaint(display
.getLayerSet());
1928 }}, display
.getProject());
1931 // else, the 'z' command restores the image using ImageJ internal undo
1934 // Enable with any tool to the left of the PENCIL
1935 if (null != active
&& !isTransforming() && ProjectToolbar
.getToolId() < ProjectToolbar
.PENCIL
) {
1936 ProjectToolbar
.setTool(ProjectToolbar
.SELECT
);
1937 if (0 == ke
.getModifiers()) {
1938 display
.setMode(new AffineTransformMode(display
));
1939 } else if (Event
.SHIFT_MASK
== ke
.getModifiers()) {
1940 for (final Displayable d
: display
.getSelection().getSelected()) {
1942 Utils
.showMessage("Can't enter manual non-linear transformation mode:\nat least one image is linked.");
1946 display
.setMode(new NonLinearTransformMode(display
));
1950 // else, let ImageJ grab the ROI into the Manager, if any
1953 if (0 == (ke
.getModifiers() ^ Utils
.getControlModifier())) {
1954 Roi roi
= getFakeImagePlus().getRoi();
1955 if (null != roi
) display
.getSelection().selectAll(roi
, true);
1956 else display
.getSelection().selectAllVisible();
1957 Display
.repaint(display
.getLayer(), display
.getSelection().getBox(), 0);
1959 break; // INSIDE the 'if' block, so that it can bleed to the default block which forwards to active!
1960 } else if (null != active
) {
1961 active
.keyPressed(ke
);
1962 if (ke
.isConsumed()) break;
1963 // TODO this is just a hack really. Should just fall back to default switch option.
1964 // The whole keyPressed method needs revision: should not break from it when not using the key.
1967 case KeyEvent
.VK_ESCAPE
: // cancel transformation
1968 if (isTransforming()) cancelTransform();
1970 display
.select(null); // deselect
1971 // repaint out the brush if present
1972 if (ProjectToolbar
.BRUSH
== ProjectToolbar
.getToolId()) {
1973 repaint(old_brush_box
, 0);
1978 case KeyEvent
.VK_SPACE
:
1979 if (0 == ke
.getModifiers()) {
1980 if (null != active
) {
1981 invalidateVolatile();
1982 if (Math
.abs(active
.getAlpha() - 0.5f
) > 0.001f
) active
.setAlpha(0.5f
);
1983 else active
.setAlpha(1.0f
);
1984 display
.setTransparencySlider(active
.getAlpha());
1990 int kem
= ke
.getModifiers();
1991 if (0 != (kem
& KeyEvent
.SHIFT_MASK
)
1992 && 0 != (kem
& KeyEvent
.ALT_MASK
)
1993 && 0 != (kem
& KeyEvent
.CTRL_MASK
)) {
1994 Utils
.showMessage("A mathematician, like a painter or poet,\nis a maker of patterns.\nIf his patterns are more permanent than theirs,\nit is because they are made with ideas\n \nG. H. Hardy.");
2000 if (ke
.isAltDown()) {
2003 } else if (dragging
) {
2004 // ignore improper 's' that open ImageJ's save dialog (linux problem ... in macosx, a single dialog opens with lots of 'ssss...' in the text field)
2013 if (!display
.getSelection().isEmpty()) {
2014 display
.adjustMinAndMaxGUI();
2018 case KeyEvent
.VK_F1
:
2019 case KeyEvent
.VK_F2
:
2020 case KeyEvent
.VK_F3
:
2021 case KeyEvent
.VK_F4
:
2022 case KeyEvent
.VK_F5
:
2023 case KeyEvent
.VK_F6
:
2024 case KeyEvent
.VK_F7
:
2025 case KeyEvent
.VK_F8
:
2026 case KeyEvent
.VK_F9
:
2027 case KeyEvent
.VK_F10
:
2028 case KeyEvent
.VK_F11
:
2029 case KeyEvent
.VK_F12
:
2030 ProjectToolbar
.keyPressed(ke
);
2034 if (0 == ke
.getModifiers() && ProjectToolbar
.getToolId() == ProjectToolbar
.SELECT
) {
2035 display
.getSelection().measure();
2044 if (null != active
&& active
instanceof ZDisplayable
) {
2045 if (null != display
.getProject().getProjectTree().tryAddNewConnector(active
, true)) {
2046 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
2053 if (ke
.isConsumed()) return;
2055 if (null != active
) {
2056 if (display
.getMode().getClass() == DefaultMode
.class) {
2057 active
.keyPressed(ke
);
2059 if (ke
.isConsumed()) return;
2065 if (browseToNodeLayer(ke
.isShiftDown())) {
2070 if (ke
.isAltDown()) {
2071 if (ke
.isShiftDown()) display
.importImage();
2072 else display
.importNextImage();
2076 case KeyEvent
.VK_PAGE_UP
: // as in Inkscape
2077 if (null != active
) {
2078 update_graphics
= true;
2079 layer
.getParent().addUndoMoveStep(active
);
2080 layer
.getParent().move(LayerSet
.UP
, active
);
2081 layer
.getParent().addUndoMoveStep(active
);
2082 Display
.repaint(layer
, active
, 5);
2083 Display
.updatePanelIndex(layer
, active
);
2087 case KeyEvent
.VK_PAGE_DOWN
: // as in Inkscape
2088 if (null != active
) {
2089 update_graphics
= true;
2090 layer
.getParent().addUndoMoveStep(active
);
2091 layer
.getParent().move(LayerSet
.DOWN
, active
);
2092 layer
.getParent().addUndoMoveStep(active
);
2093 Display
.repaint(layer
, active
, 5);
2094 Display
.updatePanelIndex(layer
, active
);
2098 case KeyEvent
.VK_HOME
: // as in Inkscape
2099 if (null != active
) {
2100 update_graphics
= true;
2101 layer
.getParent().addUndoMoveStep(active
);
2102 layer
.getParent().move(LayerSet
.TOP
, active
);
2103 layer
.getParent().addUndoMoveStep(active
);
2104 Display
.repaint(layer
, active
, 5);
2105 Display
.updatePanelIndex(layer
, active
);
2109 case KeyEvent
.VK_END
: // as in Inkscape
2110 if (null != active
) {
2111 update_graphics
= true;
2112 layer
.getParent().addUndoMoveStep(active
);
2113 layer
.getParent().move(LayerSet
.BOTTOM
, active
);
2114 layer
.getParent().addUndoMoveStep(active
);
2115 Display
.repaint(layer
, active
, 5);
2116 Display
.updatePanelIndex(layer
, active
);
2121 if (0 == ke
.getModifiers()) {
2122 if (null == active
|| active
.getClass() == Patch
.class) {
2123 // paste a new image
2124 ImagePlus clipboard
= ImagePlus
.getClipboard();
2125 if (null != clipboard
) {
2126 ImagePlus imp
= new ImagePlus(clipboard
.getTitle() + "_" + System
.currentTimeMillis(), clipboard
.getProcessor().crop());
2127 Object info
= clipboard
.getProperty("Info");
2128 if (null != info
) imp
.setProperty("Info", (String
)info
);
2129 double x
= srcRect
.x
+ srcRect
.width
/2 - imp
.getWidth()/2;
2130 double y
= srcRect
.y
+ srcRect
.height
/2 - imp
.getHeight()/2;
2131 // save the image somewhere:
2132 Patch pa
= display
.getProject().getLoader().addNewImage(imp
, x
, y
);
2133 display
.getLayer().add(pa
);
2135 } // TODO there isn't much ImageJ integration in the pasting. Can't paste to a selected image, for example.
2137 // Each type may know how to paste data from the copy buffer into itself:
2138 active
.keyPressed(ke
);
2144 if (0 == ke
.getModifiers()) {
2145 display
.getLayerSet().color_cues
= !display
.getLayerSet().color_cues
;
2146 Display
.repaint(display
.getLayerSet());
2151 if (0 == (ke
.getModifiers() ^ KeyEvent
.SHIFT_MASK
)) {
2152 // toggle visibility of tags
2153 display
.getLayerSet().paint_tags
= !display
.getLayerSet().paint_tags
;
2156 } else if (0 == (ke
.getModifiers() ^ KeyEvent
.ALT_MASK
)) {
2157 // toggle visibility of edge arrows
2158 display
.getLayerSet().paint_arrows
= !display
.getLayerSet().paint_arrows
;
2161 } else if (0 == ke
.getModifiers()) {
2162 // toggle visibility of edge confidence boxes
2163 display
.getLayerSet().paint_edge_confidence_boxes
= !display
.getLayerSet().paint_edge_confidence_boxes
;
2168 case KeyEvent
.VK_DELETE
:
2169 if (0 == ke
.getModifiers()) {
2170 display
.getSelection().deleteAll();
2174 if (0 == ke
.getModifiers() && null != active
&& active
.getClass() == Profile
.class) {
2175 display
.duplicateLinkAndSendTo(active
, 0, active
.getLayer().getParent().previous(layer
));
2180 if (0 == ke
.getModifiers() && null != active
&& active
.getClass() == Profile
.class) {
2181 display
.duplicateLinkAndSendTo(active
, 1, active
.getLayer().getParent().next(layer
));
2194 // run a plugin, if any
2195 if (null != Utils
.launchTPlugIn(ke
, "Display", display
.getProject(), display
.getActive())) {
2201 if ( !(keyCode
== KeyEvent
.VK_UNDEFINED
|| keyChar
== KeyEvent
.CHAR_UNDEFINED
) && !ke
.isConsumed() && null != active
&& active
instanceof Patch
) {
2202 // TODO should allow forwarding for all, not just Patch
2203 // forward to ImageJ for a final try
2204 IJ
.getInstance().keyPressed(ke
);
2208 //Utils.log2("keyCode, keyChar: " + keyCode + ", " + keyChar + " ref: " + KeyEvent.VK_UNDEFINED + ", " + KeyEvent.CHAR_UNDEFINED);
2211 public void keyTyped(KeyEvent ke
) {}
2213 public void keyReleased(KeyEvent ke
) {}
2215 public void zoomToFit() {
2216 double magw
= (double) getWidth() / imageWidth
;
2217 double magh
= (double) getHeight() / imageHeight
;
2218 this.magnification
= magw
< magh ? magw
: magh
;
2219 this.srcRect
.setRect(0, 0, imageWidth
, imageHeight
);
2220 setMagnification(magnification
);
2221 display
.updateInDatabase("srcRect"); // includes magnification
2225 public void setReceivesInput(boolean b
) {
2226 this.input_disabled
= !b
;
2229 public boolean isInputEnabled() {
2230 return !input_disabled
;
2233 /** CAREFUL: the ImageProcessor of the returned ImagePlus is fake, that is, a 4x4 byte array; but the dimensions that it returns are those of the host LayerSet. Used to retrieve ROIs for example.*/
2234 public ImagePlus
getFakeImagePlus() {
2238 /** Key/Mouse bindings like:
2239 * - ij.gui.StackWindow: wheel to scroll slices (in this case Layers)
2240 * - Inkscape: control+wheel to zoom (apple+wheel in macosx, since control+wheel zooms desktop)
2242 public void mouseWheelMoved(MouseWheelEvent mwe
) {
2243 if (dragging
) return; // prevent unexpected mouse wheel movements
2244 final int modifiers
= mwe
.getModifiers();
2245 final int rotation
= mwe
.getWheelRotation();
2246 final int tool
= ProjectToolbar
.getToolId();
2247 if (0 != (modifiers
& Utils
.getControlModifier())) {
2248 if (!zoom_and_pan
) return;
2249 // scroll zoom under pointer
2252 if (x
< 0 || y
< 0 || x
>= getWidth() || y
>= getHeight()) {
2257 // Current mouse point in world coords
2258 final double xx
= x
/magnification
+ srcRect
.x
;
2259 final double yy
= y
/magnification
+ srcRect
.y
;
2260 // Delta of view, in screen pixels:
2262 if ( 0 != (modifiers
& MouseWheelEvent
.SHIFT_MASK
)) {
2263 if (0 != (modifiers
& MouseWheelEvent
.ALT_MASK
)) px_inc
= 1;
2266 final double inc
= px_inc
/magnification
;
2268 final Rectangle r
= new Rectangle();
2272 r
.width
= srcRect
.width
+ (int)(inc
+0.5);
2273 r
.height
= srcRect
.height
+ (int)(inc
+0.5);
2274 r
.x
= (int)(xx
- ((xx
- srcRect
.x
)/srcRect
.width
) * r
.width
+ 0.5);
2275 r
.y
= (int)(yy
- ((yy
- srcRect
.y
)/srcRect
.height
) * r
.height
+ 0.5);
2277 if (r
.width
* magnification
< getWidth()
2278 || r
.height
* magnification
< getHeight()) {
2279 // Can't zoom at point: would chage field of view's flow or would have to shift the canvas position!
2280 Utils
.showStatus("To zoom more, use -/+ keys");
2285 r
.width
= srcRect
.width
- (int)(inc
+0.5);
2286 r
.height
= srcRect
.height
- (int)(inc
+0.5);
2287 if (r
.width
< 1 || r
.height
< 1) {
2290 r
.x
= (int)(xx
- ((xx
- srcRect
.x
)/srcRect
.width
) * r
.width
+ 0.5);
2291 r
.y
= (int)(yy
- ((yy
- srcRect
.y
)/srcRect
.height
) * r
.height
+ 0.5);
2293 final double newMag
= magnification
* (srcRect
.width
/ (double)r
.width
);
2294 // correct floating-point-induced erroneous drift: the int-precision offscreen point under the mouse shoud remain the same
2295 r
.x
-= (int)((x
/newMag
+ r
.x
) - xx
);
2296 r
.y
-= (int)((y
/newMag
+ r
.y
) - yy
);
2299 int w
= (int) Math
.round(dstWidth
/ newMag
);
2300 if (w
* newMag
< dstWidth
) w
++;
2301 if (w
> imageWidth
) w
= imageWidth
;
2302 int h
= (int) Math
.round(dstHeight
/ newMag
);
2303 if (h
* newMag
< dstHeight
) h
++;
2304 if (h
> imageHeight
) h
= imageHeight
;
2305 if (r
.x
< 0) r
.x
= 0;
2306 if (r
.y
< 0) r
.y
= 0;
2307 if (r
.x
+ w
> imageWidth
) r
.x
= imageWidth
- w
;
2308 if (r
.y
+ h
> imageHeight
) r
.y
= imageHeight
- h
; //imageWidth and imageHeight are the LayerSet's width,height, ie. the world's 2D dimensions.
2311 this.setMagnification(newMag
);
2312 this.setSrcRect(r
.x
, r
.y
, w
, h
);
2313 display
.repaintAll2();
2315 } else if (0 == (modifiers ^ InputEvent
.SHIFT_MASK
) && null != display
.getActive() && ProjectToolbar
.PEN
!= tool
&& AreaContainer
.class.isInstance(display
.getActive())) {
2316 final int sign
= rotation
> 0 ?
1 : -1;
2317 if (ProjectToolbar
.BRUSH
== tool
) {
2318 int brushSize_old
= ProjectToolbar
.getBrushSize();
2319 // resize brush for AreaList/AreaTree painting
2320 int brushSize
= ProjectToolbar
.setBrushSize((int)(5 * sign
/ magnification
)); // the getWheelRotation provides the sign
2321 if (brushSize_old
> brushSize
) brushSize
= brushSize_old
; // for repainting purposes alone
2322 int extra
= (int)(10 / magnification
);
2323 if (extra
< 2) extra
= 2;
2324 extra
+= 4; // for good measure
2325 this.repaint(new Rectangle((int)(mwe
.getX() / magnification
) + srcRect
.x
- brushSize
/2 - extra
, (int)(mwe
.getY() / magnification
) + srcRect
.y
- brushSize
/2 - extra
, brushSize
+extra
, brushSize
+extra
), 0);
2326 } else if (ProjectToolbar
.PENCIL
== tool
|| ProjectToolbar
.WAND
== tool
) {
2327 // resize area to consider for fast-marching
2328 int w
= Segmentation
.fmp
.width
;
2329 int h
= Segmentation
.fmp
.height
;
2330 Segmentation
.fmp
.resizeArea(sign
, magnification
);
2331 w
= Math
.max(w
, Segmentation
.fmp
.width
);
2332 h
= Math
.max(h
, Segmentation
.fmp
.height
);
2333 this.repaint(new Rectangle((int)(mwe
.getX() / magnification
) + srcRect
.x
- w
/2 + 2,
2334 (int)(mwe
.getY() / magnification
) + srcRect
.y
- h
/2 + 2,
2337 } else if (0 == modifiers
) {
2339 if (rotation
> 0) display
.nextLayer(modifiers
);
2340 else display
.previousLayer(modifiers
);
2341 } else if (null != display
.getActive()) {
2342 // forward to active
2343 display
.getActive().mouseWheelMoved(mwe
);
2347 protected class RepaintProperties
implements AbstractOffscreenThread
.RepaintProperties
{
2348 final private Layer layer
;
2349 final private List
<Layer
> layers
;
2350 final private int g_width
;
2351 final private int g_height
;
2352 final private Rectangle srcRect
;
2353 final private double magnification
;
2354 final private Displayable active
;
2355 final private int c_alphas
;
2356 final private Rectangle clipRect
;
2357 final private int mode
;
2358 final private HashMap
<Color
,Layer
> hm
;
2359 final private ArrayList
<LayerPanel
> blending_list
;
2360 final private GraphicsSource graphics_source
;
2362 RepaintProperties(final Rectangle clipRect
, final Layer layer
, final List
<Layer
> layers
, final int g_width
, final int g_height
, final Rectangle srcRect
, final double magnification
, final Displayable active
, final int c_alphas
, final GraphicsSource graphics_source
) {
2363 this.clipRect
= clipRect
;
2365 this.layers
= layers
;
2366 this.g_width
= g_width
;
2367 this.g_height
= g_height
;
2368 this.srcRect
= srcRect
;
2369 this.magnification
= magnification
;
2370 this.active
= active
;
2371 this.c_alphas
= c_alphas
;
2373 // query the display for repainting mode
2374 this.hm
= new HashMap
<Color
,Layer
>();
2375 this.blending_list
= new ArrayList
<LayerPanel
>();
2376 this.mode
= display
.getPaintMode(hm
, blending_list
);
2377 this.graphics_source
= graphics_source
;
2381 private final class OffscreenThread
extends AbstractOffscreenThread
{
2384 super("T2-Canvas-Offscreen");
2387 public void paint() {
2388 final Layer active_layer
;
2389 final List
<Layer
> layers
;
2392 final Rectangle srcRect
;
2393 final double magnification
;
2394 final Displayable active
;
2396 final Rectangle clipRect
;
2397 final Loader loader
;
2398 final HashMap
<Color
,Layer
> hm
;
2399 final ArrayList
<LayerPanel
> blending_list
;
2401 final GraphicsSource graphics_source
;
2403 synchronized (this) {
2404 final DisplayCanvas
.RepaintProperties rp
= (DisplayCanvas
.RepaintProperties
) this.rp
;
2405 active_layer
= rp
.layer
;
2407 g_width
= rp
.g_width
;
2408 g_height
= rp
.g_height
;
2409 srcRect
= rp
.srcRect
;
2410 magnification
= rp
.magnification
;
2412 c_alphas
= rp
.c_alphas
;
2413 clipRect
= rp
.clipRect
;
2414 loader
= active_layer
.getProject().getLoader();
2417 blending_list
= rp
.blending_list
;
2418 graphics_source
= rp
.graphics_source
;
2421 BufferedImage target
= null;
2423 final ArrayList
<Displayable
> al_top
= new ArrayList
<Displayable
>();
2425 // Check if the image is cached
2426 Screenshot sc
= null;
2428 if (display
.getMode().getClass() == DefaultMode
.class) {
2429 sc
= active_layer
.getParent().getScreenshot(new ScreenshotProperties(active_layer
, srcRect
, magnification
, g_width
, g_height
, c_alphas
, graphics_source
));
2431 //Utils.log2("Using cached screenshot " + sc + " with srcRect " + sc.srcRect);
2432 target
= (BufferedImage
) loader
.getCachedAWT(sc
.sid
, 0);
2433 if (null == target
) active_layer
.getParent().removeFromOffscreens(sc
); // the image was thrown out of the cache
2434 else if ( (sc
.al_top
.size() > 0 && sc
.al_top
.get(0) != display
.getActive())
2435 || (0 == sc
.al_top
.size() && null != display
.getActive()) ) {
2436 // Can't accept: different active object
2437 Utils
.log2("rejecting: different active object");
2440 al_top
.addAll(sc
.al_top
);
2441 display
.applyFilters(target
);
2445 } catch (Throwable t
) {
2449 //Utils.log2("Found target " + target + "\n with al_top.size() = " + al_top.size());
2451 if (null == target
) {
2452 target
= paintOffscreen(active_layer
, layers
, g_width
, g_height
, srcRect
, magnification
, active
, c_alphas
, clipRect
, loader
, hm
, blending_list
, mode
, graphics_source
, active_layer
.getParent().prepaint
, al_top
, true);
2454 /* CAN'T, may have prePaint in it
2455 if (null != sc && display.getProject().getProperty("look_ahead_cache", 0) > 0) {
2457 layer.getParent().storeScreenshot(sc);
2462 synchronized (offscreen_lock
) {
2464 if (null != offscreen
) to_flush
.add(offscreen
);
2466 update_graphics
= false;
2467 DisplayCanvas
.this.al_top
= al_top
;
2469 // Outside, otherwise could deadlock
2470 invalidateVolatile();
2472 // Send repaint event, without offscreen graphics
2473 RT
.paint(clipRect
, false);
2477 /** Looks into the layer and its LayerSet and finds out what needs to be painted, putting it into the three lists.
2478 * @return the index of the first non-image object. */
2479 private final int gatherDisplayables(final Layer layer
, final List
<Layer
> layers
, final Rectangle srcRect
, final Displayable active
, final ArrayList
<Displayable
> al_paint
, final ArrayList
<Displayable
> al_top
, final boolean preload_patches
) {
2480 layer
.getParent().checkBuckets();
2481 layer
.checkBuckets();
2482 final Iterator
<Displayable
> ital
= layer
.find(srcRect
, true).iterator();
2483 final Collection
<Displayable
> zdal
;
2484 final LayerSet layer_set
= layer
.getParent();
2485 // Which layers to color cue, if any?
2486 if (layer_set
.color_cues
) {
2487 final Collection
<Displayable
> atlayer
= layer_set
.roughlyFindZDisplayables(layer
, srcRect
, true);
2488 final Set
<Displayable
> others
= new HashSet
<Displayable
>();
2489 for (final Layer la
: layers
) {
2490 if (la
== layer
) continue;
2491 others
.addAll(layer_set
.roughlyFindZDisplayables(la
, srcRect
, true));
2493 others
.removeAll(atlayer
);
2494 zdal
= new ArrayList
<Displayable
>(others
); // in whatever order, to paint under
2495 zdal
.addAll(atlayer
); // in proper stack-index order
2497 zdal
= layer_set
.roughlyFindZDisplayables(layer
, srcRect
, true);
2499 final Iterator
<Displayable
> itzd
= zdal
.iterator();
2501 // Assumes the Layer has its objects in order:
2503 // 2 - Profiles, Balls
2504 // 3 - Pipes and ZDisplayables (from the parent LayerSet)
2507 Displayable tmp
= null;
2508 boolean top
= false;
2509 final ArrayList
<Patch
> al_patches
= preload_patches ?
new ArrayList
<Patch
>() : null;
2511 int first_non_patch
= 0;
2513 while (ital
.hasNext()) {
2514 final Displayable d
= ital
.next();
2515 final Class
<?
> c
= d
.getClass();
2516 if (DLabel
.class == c
|| LayerSet
.class == c
) {
2517 tmp
= d
; // since ital.next() has moved forward already
2520 if (Patch
.class == c
) {
2522 if (preload_patches
) al_patches
.add((Patch
)d
);
2524 if (!top
&& d
== active
) top
= true; // no Patch on al_top ever
2525 if (top
) al_top
.add(d
); // so active is added to al_top, if it's not a Patch
2526 else al_paint
.add(d
);
2528 first_non_patch
+= 1;
2531 // preload concurrently as many as possible
2532 if (preload_patches
) Loader
.preload(al_patches
, magnification
, false); // must be false; a 'true' would incur in an infinite loop.
2534 // paint the ZDisplayables here, before the labels and LayerSets, if any
2535 while (itzd
.hasNext()) {
2536 final Displayable zd
= itzd
.next();
2537 if (zd
== active
) top
= true;
2538 if (top
) al_top
.add(zd
);
2539 else al_paint
.add(zd
);
2541 // paint LayerSet and DLabel objects!
2543 if (tmp
== active
) top
= true;
2544 if (top
) al_top
.add(tmp
);
2545 else al_paint
.add(tmp
);
2547 while (ital
.hasNext()) {
2548 final Displayable d
= ital
.next();
2549 if (d
== active
) top
= true;
2550 if (top
) al_top
.add(d
);
2551 else al_paint
.add(d
);
2554 return first_non_patch
;
2558 public BufferedImage
paintOffscreen(final Layer active_layer
, final int g_width
, final int g_height
,
2559 final Rectangle srcRect
, final double magnification
, final Displayable active
,
2560 final int c_alphas
, final Rectangle clipRect
, final Loader loader
, final HashMap
<Color
,Layer
> hm
,
2561 final ArrayList
<LayerPanel
> blending_list
, final int mode
, final GraphicsSource graphics_source
,
2562 final boolean prepaint
, final ArrayList
<Displayable
> al_top
) {
2563 return paintOffscreen(active_layer
, active_layer
.getParent().getColorCueLayerRange(active_layer
), g_width
, g_height
, srcRect
, magnification
, active
,
2564 c_alphas
, clipRect
, loader
, hm
, blending_list
, mode
, graphics_source
,
2565 prepaint
, al_top
, false);
2568 /** This method uses data only from the arguments, and changes none.
2569 * Will fill @param al_top with proper Displayable objects, or none when none are selected. */
2570 public BufferedImage
paintOffscreen(final Layer active_layer
, final List
<Layer
> layers
, final int g_width
, final int g_height
,
2571 final Rectangle srcRect
, final double magnification
, final Displayable active
,
2572 final int c_alphas
, final Rectangle clipRect
, final Loader loader
, final HashMap
<Color
,Layer
> hm
,
2573 final ArrayList
<LayerPanel
> blending_list
, final int mode
, final GraphicsSource graphics_source
,
2574 final boolean prepaint
, final ArrayList
<Displayable
> al_top
, final boolean preload
) {
2576 final ArrayList
<Displayable
> al_paint
= new ArrayList
<Displayable
>();
2577 int first_non_patch
= gatherDisplayables(active_layer
, layers
, srcRect
, active
, al_paint
, al_top
, preload
);
2579 return paintOffscreen(active_layer
, layers
, al_paint
, active
, g_width
, g_height
, c_alphas
, loader
, hm
, blending_list
, mode
, graphics_source
, prepaint
, first_non_patch
);
2582 public BufferedImage
paintOffscreen(final Layer active_layer
, final List
<Layer
> layers
, final ArrayList
<Displayable
> al_paint
, final Displayable active
, final int g_width
, final int g_height
, final int c_alphas
, final Loader loader
, final HashMap
<Color
,Layer
> hm
, final ArrayList
<LayerPanel
> blending_list
, final int mode
, final GraphicsSource graphics_source
, final boolean prepaint
, int first_non_patch
) {
2584 if (0 == g_width
|| 0 == g_height
) return null;
2585 // ALMOST, but not always perfect //if (null != clipRect) g.setClip(clipRect);
2587 // prepare the canvas for the srcRect and magnification
2588 final AffineTransform atc
= new AffineTransform();
2589 atc
.scale(magnification
, magnification
);
2590 atc
.translate(-srcRect
.x
, -srcRect
.y
);
2592 // the non-srcRect areas, in offscreen coords
2593 final Rectangle r1
= new Rectangle(srcRect
.x
+ srcRect
.width
, srcRect
.y
, (int)(g_width
/ magnification
) - srcRect
.width
, (int)(g_height
/ magnification
));
2594 final Rectangle r2
= new Rectangle(srcRect
.x
, srcRect
.y
+ srcRect
.height
, srcRect
.width
, (int)(g_height
/ magnification
) - srcRect
.height
);
2596 // create new graphics
2598 display
.getProject().getLoader().releaseToFit(g_width
* g_height
* 10);
2599 } catch (Exception e
) {} // when closing, asynch state may throw for a null loader.
2601 final BufferedImage target
= getGraphicsConfiguration().createCompatibleImage(g_width
, g_height
, Transparency
.TRANSLUCENT
); // creates a BufferedImage.TYPE_INT_ARGB image in my T60p ATI FireGL laptop
2602 //Utils.log2("offscreen acceleration priority: " + target.getAccelerationPriority());
2603 final Graphics2D g
= target
.createGraphics();
2605 g
.setTransform(atc
); //at_original);
2607 //setRenderingHints(g);
2608 // always a stroke of 1.0, regardless of magnification; the stroke below corrects for that
2609 g
.setStroke(stroke
);
2613 // Testing: removed Area.subtract, now need to fill in background
2614 g
.setColor(Color
.black
);
2615 g
.fillRect(0, 0, g_width
- r1
.x
, g_height
- r2
.y
);
2620 // 2 - images and anything else not on al_top
2621 // 3 - non-srcRect areas
2623 //Utils.log2("offscreen painting: " + al_paint.size());
2625 // filter paintables
2626 final Collection
<?
extends Paintable
> paintables
= graphics_source
.asPaintable(al_paint
);
2629 first_non_patch
= paintables
.size() - (al_paint
.size() - first_non_patch
);
2631 // Determine painting mode
2632 if (Display
.REPAINT_SINGLE_LAYER
== mode
) {
2633 if (display
.isLiveFilteringEnabled()) {
2634 paintWithFiltering(g
, al_paint
, paintables
, first_non_patch
, g_width
, g_height
, active
, c_alphas
, active_layer
, layers
, true);
2636 // Direct painting mode, with prePaint abilities
2638 for (final Paintable d
: paintables
) {
2639 if (i
== first_non_patch
) {
2640 //Object antialias = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
2641 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
); // to smooth edges of the images
2642 //Object text_antialias = g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
2643 g
.setRenderingHint(RenderingHints
.KEY_TEXT_ANTIALIASING
, RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
);
2644 //Object render_quality = g.getRenderingHint(RenderingHints.KEY_RENDERING);
2645 g
.setRenderingHint(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
2647 if (prepaint
) d
.prePaint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
2648 else d
.paint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
2652 } else if (Display
.REPAINT_MULTI_LAYER
== mode
) {
2653 // paint first the current layer Patches only (to set the background)
2654 // With prePaint capabilities:
2655 if (display
.isLiveFilteringEnabled()) {
2656 paintWithFiltering(g
, al_paint
, paintables
, first_non_patch
, g_width
, g_height
, active
, c_alphas
, active_layer
, layers
, false);
2660 for (final Paintable d
: paintables
) {
2661 if (first_non_patch
== i
) break;
2662 d
.prePaint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
2666 for (final Paintable d
: paintables
) {
2667 if (first_non_patch
== i
) break;
2668 d
.paint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
2674 // then blend on top the ImageData of the others, in reverse Z order and using the alpha of the LayerPanel
2675 final Composite original
= g
.getComposite();
2677 g
.setTransform(new AffineTransform());
2679 final Set
<Class
<?
>> included
= display
.classes_to_multipaint
;
2680 for (final ListIterator
<LayerPanel
> it
= blending_list
.listIterator(blending_list
.size()); it
.hasPrevious(); ) {
2681 final LayerPanel lp
= it
.previous();
2682 if (lp
.layer
== active_layer
) continue;
2683 active_layer
.getProject().getLoader().releaseToFit(g_width
* g_height
* 4 + 1024);
2684 final BufferedImage bi
= getGraphicsConfiguration().createCompatibleImage(g_width
, g_height
, Transparency
.TRANSLUCENT
);
2685 final Graphics2D gb
= bi
.createGraphics();
2686 gb
.setTransform(atc
);
2687 for (final Displayable d
: lp
.layer
.find(srcRect
, true)) {
2688 if (included
.contains(d
.getClass()))
2689 d
.paint(gb
, srcRect
, magnification
, false, c_alphas
, lp
.layer
, layers
); // not prePaint! We want direct painting, even if potentially slow
2691 // Repeating loop ... the human compiler at work, just because one cannot lazily concatenate both sequences:
2692 for (final Displayable d
: lp
.layer
.getParent().roughlyFindZDisplayables(lp
.layer
, srcRect
, true)) {
2693 if (included
.contains(d
.getClass()))
2694 d
.paint(gb
, srcRect
, magnification
, false, c_alphas
, lp
.layer
, layers
); // not prePaint! We want direct painting, even if potentially slow
2697 g
.setComposite(Displayable
.getComposite(display
.getLayerCompositeMode(lp
.layer
), lp
.getAlpha()));
2698 g
.drawImage(display
.applyFilters(bi
), 0, 0, null);
2699 } catch (Throwable t
) {
2700 Utils
.log("Could not use composite mode for layer overlays! Your graphics card may not support it.");
2701 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, lp
.getAlpha()));
2702 g
.drawImage(bi
, 0, 0, null);
2708 g
.setComposite(original
);
2709 g
.setTransform(atc
);
2711 // then paint the non-Patch objects of the current layer
2713 //Object antialias = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
2714 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
); // to smooth edges of the images
2715 //Object text_antialias = g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
2716 g
.setRenderingHint(RenderingHints
.KEY_TEXT_ANTIALIASING
, RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
);
2717 //Object render_quality = g.getRenderingHint(RenderingHints.KEY_RENDERING);
2718 g
.setRenderingHint(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
2720 // TODO this loop should be reading from the paintable_patches and paintables, since their length/order *could* have changed
2721 // For the current layer:
2722 for (int i
= first_non_patch
; i
< al_paint
.size(); i
++) {
2723 final Displayable d
= al_paint
.get(i
);
2724 d
.paint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
2726 } else if(Display
.REPAINT_RGB_LAYER
== mode
) {
2727 // TODO rewrite to avoid calling the list twice
2728 final Collection
<?
extends Paintable
> paintable_patches
= graphics_source
.asPaintable(al_paint
);
2730 final HashMap
<Color
,byte[]> channels
= new HashMap
<Color
,byte[]>();
2731 hm
.put(Color
.green
, active_layer
);
2732 for (final Map
.Entry
<Color
,Layer
> e
: hm
.entrySet()) {
2733 final BufferedImage bi
= new BufferedImage(g_width
, g_height
, BufferedImage
.TYPE_BYTE_GRAY
); //INDEXED, Loader.GRAY_LUT);
2734 final Graphics2D gb
= bi
.createGraphics();
2735 gb
.setTransform(atc
);
2736 final Layer la
= e
.getValue();
2737 ArrayList
<Paintable
> list
= new ArrayList
<Paintable
>();
2738 if (la
== active_layer
) {
2739 if (Color
.green
!= e
.getKey()) continue; // don't paint current layer in two channels
2740 list
.addAll(paintable_patches
);
2742 list
.addAll(la
.find(Patch
.class, srcRect
, true));
2744 list
.addAll(la
.getParent().getZDisplayables(ImageData
.class, true)); // Stack.class and perhaps others
2745 for (final Paintable d
: list
) {
2746 d
.paint(gb
, srcRect
, magnification
, false, c_alphas
, la
, layers
);
2748 channels
.put(e
.getKey(), (byte[])new ByteProcessor(bi
).getPixels());
2750 final byte[] red
, green
, blue
;
2751 green
= channels
.get(Color
.green
);
2752 if (null == channels
.get(Color
.red
)) red
= new byte[green
.length
];
2753 else red
= channels
.get(Color
.red
);
2754 if (null == channels
.get(Color
.blue
)) blue
= new byte[green
.length
];
2755 else blue
= channels
.get(Color
.blue
);
2756 final int[] pix
= new int[green
.length
];
2757 for (int i
=0; i
<green
.length
; i
++) {
2758 pix
[i
] = ((red
[i
] & 0xff) << 16) + ((green
[i
] & 0xff) << 8) + (blue
[i
] & 0xff);
2760 // undo transform, is intended for Displayable objects
2761 g
.setTransform(new AffineTransform());
2762 final ColorProcessor cp
= new ColorProcessor(g_width
, g_height
, pix
);
2763 if (display
.invert_colors
) cp
.invert();
2764 display
.applyFilters(cp
);
2765 final Image img
= cp
.createImage();
2766 g
.drawImage(img
, 0, 0, null);
2769 g
.setTransform(atc
);
2771 // then paint the non-Image objects of the current layer
2772 //Object antialias = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
2773 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
); // to smooth edges of the images
2774 //Object text_antialias = g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
2775 g
.setRenderingHint(RenderingHints
.KEY_TEXT_ANTIALIASING
, RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
);
2776 //Object render_quality = g.getRenderingHint(RenderingHints.KEY_RENDERING);
2777 g
.setRenderingHint(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
2779 for (final Displayable d
: al_paint
) {
2780 if (ImageData
.class.isInstance(d
)) continue;
2781 d
.paint(g
, srcRect
, magnification
, d
== active
, c_alphas
, active_layer
, layers
);
2783 // TODO having each object type in a key/list<type> table would be so much easier and likely performant.
2786 // finally, paint non-srcRect areas
2787 if (r1
.width
> 0 || r1
.height
> 0 || r2
.width
> 0 || r2
.height
> 0) {
2788 g
.setColor(Color
.gray
);
2796 } catch (OutOfMemoryError oome
) {
2797 // so OutOfMemoryError won't generate locks
2798 IJError
.print(oome
);
2799 } catch (Exception e
) {
2805 private final void paintWithFiltering(final Graphics2D g
, final ArrayList
<Displayable
> al_paint
,
2806 final Collection
<?
extends Paintable
> paintables
,
2807 final int first_non_patch
,
2808 final int g_width
, final int g_height
,
2809 final Displayable active
, final int c_alphas
,
2810 final Layer layer
, final List
<Layer
> layers
, final boolean paint_non_images
) {
2811 // Determine the type of the image: if any Patch is of type COLOR_RGB or COLOR_256, use RGB
2812 int type
= BufferedImage
.TYPE_BYTE_GRAY
;
2813 search
: for (final Displayable d
: al_paint
) {
2814 if (d
.getClass() == Patch
.class) {
2815 switch (((Patch
)d
).getType()) {
2816 case ImagePlus
.COLOR_256
:
2817 case ImagePlus
.COLOR_RGB
:
2818 type
= BufferedImage
.TYPE_INT_ARGB
;
2824 // Paint all patches to an image
2825 final BufferedImage bi
= new BufferedImage(g_width
, g_height
, type
);
2826 final Graphics2D gpre
= bi
.createGraphics();
2827 gpre
.setTransform(atc
);
2829 for (final Paintable p
: paintables
) {
2830 if (i
== first_non_patch
) break;
2831 p
.paint(gpre
, srcRect
, magnification
, p
== active
, c_alphas
, layer
, layers
);
2835 final ImagePlus imp
= new ImagePlus("filtered", type
== BufferedImage
.TYPE_BYTE_GRAY ?
new ByteProcessor(bi
) : new ColorProcessor(bi
));
2838 display
.applyFilters(imp
);
2840 // Paint the filtered image
2841 final AffineTransform aff
= g
.getTransform();
2842 g
.setTransform(new AffineTransform()); // reset
2843 g
.drawImage(imp
.getProcessor().createImage(), 0, 0, null);
2844 // Paint the remaining elements if any
2845 if (paint_non_images
&& first_non_patch
!= paintables
.size()) {
2846 g
.setTransform(aff
); // restore srcRect and magnification
2847 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
); // to smooth edges of the images
2848 g
.setRenderingHint(RenderingHints
.KEY_TEXT_ANTIALIASING
, RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
);
2849 g
.setRenderingHint(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
2851 for (final Paintable p
: paintables
) {
2852 if (i
< first_non_patch
) {
2856 p
.paint(g
, srcRect
, magnification
, p
== active
, c_alphas
, layer
, layers
);
2863 // added here to prevent flickering, but doesn't help. All it does is avoid a call to imp.redraw()
2864 protected void scroll(int sx
, int sy
) {
2865 int ox
= xSrcStart
+ (int)(sx
/magnification
); //convert to offscreen coordinates
2866 int oy
= ySrcStart
+ (int)(sy
/magnification
);
2867 int newx
= xSrcStart
+ (xMouseStart
-ox
);
2868 int newy
= ySrcStart
+ (yMouseStart
-oy
);
2869 if (newx
<0) newx
= 0;
2870 if (newy
<0) newy
= 0;
2871 if ((newx
+srcRect
.width
)>imageWidth
) newx
= imageWidth
-srcRect
.width
;
2872 if ((newy
+srcRect
.height
)>imageHeight
) newy
= imageHeight
-srcRect
.height
;
2875 display
.getMode().srcRectUpdated(srcRect
, magnification
);
2878 private void handleHide(final KeyEvent ke
) {
2879 if (ke
.isAltDown() && !ke
.isShiftDown()) {
2881 Display
.updateCheckboxes(display
.getLayer().getParent().setAllVisible(false), DisplayablePanel
.VISIBILITY_STATE
);
2882 //Display.repaint(display.getLayer());
2883 Display
.update(display
.getLayer());
2887 if (ke
.isShiftDown()) {
2889 display
.hideDeselected(ke
.isAltDown());
2893 // else, hide selected
2894 display
.getSelection().setVisible(false);
2895 Display
.update(display
.getLayer());
2899 DisplayCanvas
.Screenshot
createScreenshot(Layer layer
) {
2900 return new Screenshot(layer
);
2903 protected class ScreenshotProperties
{
2905 final Rectangle srcRect
;
2906 final double magnification
;
2907 final int g_width
, g_height
, c_alphas
;
2908 final GraphicsSource graphics_source
;
2909 final ArrayList
<LayerPanel
> blending_list
;
2910 final HashMap
<Color
,Layer
> hm
;
2912 ScreenshotProperties(Layer layer
, Rectangle srcRect
, double magnification
, int g_width
, int g_height
, int c_alphas
, GraphicsSource graphics_source
) {
2913 this.srcRect
= new Rectangle(srcRect
);
2914 this.magnification
= magnification
;
2916 this.blending_list
= new ArrayList
<LayerPanel
>();
2917 this.hm
= new HashMap
<Color
,Layer
>();
2918 this.mode
= display
.getPaintMode(hm
, blending_list
);
2919 this.g_width
= g_width
;
2920 this.g_height
= g_height
;
2921 this.graphics_source
= graphics_source
;
2922 this.c_alphas
= c_alphas
;
2923 Layer current_layer
= display
.getLayer();
2924 if (Display
.REPAINT_RGB_LAYER
== mode
) {
2925 Layer red
= hm
.get(Color
.red
);
2926 Layer blue
= hm
.get(Color
.blue
);
2927 if (null != red
|| null != blue
) {
2928 LayerSet ls
= layer
.getParent();
2929 int i_layer
= ls
.indexOf(layer
);
2930 int i_current
= ls
.indexOf(current_layer
);
2932 int i_red
= ls
.indexOf(red
);
2933 Layer l
= red
.getParent().getLayer(i_red
+ i_current
- i_layer
);
2935 hm
.put(Color
.red
, l
);
2937 hm
.remove(Color
.red
);
2941 int i_blue
= ls
.indexOf(blue
);
2942 Layer l
= blue
.getParent().getLayer(i_blue
+ i_current
- i_layer
);
2944 hm
.put(Color
.blue
, l
);
2946 hm
.remove(Color
.blue
);
2952 public final boolean equals(final Object o
) {
2953 final ScreenshotProperties s
= (ScreenshotProperties
)o
;
2954 return s
.layer
== this.layer
2955 && s
.magnification
== this.magnification
2956 && s
.srcRect
.x
== this.srcRect
.x
&& s
.srcRect
.y
== this.srcRect
.y
2957 && s
.srcRect
.width
== this.srcRect
.width
&& s
.srcRect
.height
== this.srcRect
.height
2958 && s
.mode
== this.mode
2959 && s
.c_alphas
== this.c_alphas
2960 && Utils
.equalContent(s
.blending_list
, this.blending_list
)
2961 && Utils
.equalContent(s
.hm
, this.hm
);
2963 public int hashCode() { return 0; } //$%^&$#@!
2966 public class Screenshot
{
2968 long sid
= Long
.MIN_VALUE
;
2970 final ArrayList
<Displayable
> al_top
= new ArrayList
<Displayable
>();
2971 final ScreenshotProperties props
;
2973 Screenshot(Layer layer
) {
2974 this(layer
, DisplayCanvas
.this.srcRect
, DisplayCanvas
.this.magnification
, DisplayCanvas
.this.getWidth(), DisplayCanvas
.this.getHeight(), DisplayCanvas
.this.display
.getDisplayChannelAlphas(), DisplayCanvas
.this.display
.getMode().getGraphicsSource());
2977 Screenshot(Layer layer
, Rectangle srcRect
, double magnification
, int g_width
, int g_height
, int c_alphas
, GraphicsSource graphics_source
) {
2979 this.props
= new ScreenshotProperties(layer
, srcRect
, magnification
, g_width
, g_height
, c_alphas
, graphics_source
);
2982 public long init() {
2983 this.born
= System
.currentTimeMillis();
2984 this.sid
= layer
.getProject().getLoader().getNextTempId();
2987 /** Associate @param img to this, with a new sid. */
2988 public long assoc(BufferedImage img
) {
2990 if (null != img
) layer
.getProject().getLoader().cacheAWT(this.sid
, img
);
2993 public void createImage() {
2994 BufferedImage img
= paintOffscreen(layer
, layer
.getParent().getColorCueLayerRange(layer
), props
.g_width
, props
.g_height
, props
.srcRect
, props
.magnification
,
2995 display
.getActive(), props
.c_alphas
, null, layer
.getProject().getLoader(),
2996 props
.hm
, props
.blending_list
, props
.mode
, props
.graphics_source
, false, al_top
, false);
2997 layer
.getProject().getLoader().cacheAWT(sid
, img
);
2999 public void flush() {
3000 layer
.getProject().getLoader().decacheAWT(sid
);
3004 private boolean browseToNodeLayer(final boolean is_shift_down
) {
3005 // find visible instances of Tree that are currently painting in the canvas
3007 final Layer active_layer
= display
.getLayer();
3008 final Point po
= getCursorLoc(); // in offscreen coords
3009 for (final ZDisplayable zd
: display
.getLayerSet().getDisplayableList()) {
3010 if (!zd
.isVisible()) continue;
3011 if (!(zd
instanceof Tree
<?
>)) continue;
3012 final Tree
<?
> t
= (Tree
<?
>)zd
;
3013 final Layer la
= t
.toClosestPaintedNode(active_layer
, po
.x
, po
.y
, magnification
);
3014 if (null == la
) continue;
3016 display
.toLayer(la
);
3017 if (!is_shift_down
) display
.getSelection().clear();
3018 display
.getSelection().add(t
);
3019 switch (ProjectToolbar
.getToolId()) {
3020 case ProjectToolbar
.PEN
:
3021 case ProjectToolbar
.BRUSH
:
3024 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
3029 } catch (Exception e
) {
3030 Utils
.log2("Oops: " + e
);
3035 /** Smoothly move the canvas from x0,y0,layer0 to x1,y1,layer1 */
3036 protected void animateBrowsing(final int dx
, final int dy
) {
3037 // check preconditions
3038 final float mag
= (float)this.magnification
;
3039 final Rectangle startSrcRect
= (Rectangle
)this.srcRect
.clone();
3040 // The motion will be displaced by some screen pixels at every time step.
3041 final Vector2f v
= new Vector2f(dx
, dy
);
3042 final float sqdist_to_travel
= v
.lengthSquared();
3045 final Point2f cp
= new Point2f(0, 0); // the current deltas
3047 final ScheduledFuture
<?
>[] sf
= new ScheduledFuture
[1];
3048 sf
[0] = animate(new Runnable() {
3051 //Utils.log2("advanced by x,y = " + cp.x + ", " + cp.y);
3053 if (v
.lengthSquared() >= sqdist_to_travel
) {
3054 // set target position
3055 x
= startSrcRect
.x
+ dx
;
3056 y
= startSrcRect
.y
+ dy
;
3058 cancelAnimation(sf
[0]);
3061 x
= startSrcRect
.x
+ (int)(cp
.x
);
3062 y
= startSrcRect
.y
+ (int)(cp
.y
);
3064 setSrcRect(x
, y
, startSrcRect
.width
, startSrcRect
.height
);
3065 display
.repaintAll2();
3067 }, 0, 50, TimeUnit
.MILLISECONDS
);
3070 /** Smoothly move the canvas from its current position until the given rectangle is included within the srcRect.
3071 * If the given rectangle is larger than the srcRect, it will refuse to work (for now). */
3072 public boolean animateBrowsing(final Rectangle target_
, final Layer target_layer
) {
3073 // Crop target to world's 2D dimensions
3074 Area a
= new Area(target_
);
3075 a
.intersect(new Area(display
.getLayerSet().get2DBounds()));
3076 final Rectangle target
= a
.getBounds();
3077 if (0 == target
.width
|| 0 == target
.height
) {
3081 if (this.srcRect
.contains(target
) && target_layer
== display
.getLayer()) {
3082 // So: don't animate, but at least highlight the target
3083 playHighlight(target
);
3087 // The motion will be displaced by some screen pixels at every time step.
3088 final int ox
= srcRect
.x
+ srcRect
.width
/2;
3089 final int oy
= srcRect
.y
+ srcRect
.height
/2;
3090 final int tx
= target
.x
+ target
.width
/2;
3091 final int ty
= target
.y
+ target
.height
/2;
3092 final Vector2f v
= new Vector2f(tx
- ox
, ty
- oy
);
3094 v
.scale(20/(float)magnification
);
3098 final Layer start_layer
= display
.getLayer();
3100 int ithis = display.getLayerSet().indexOf(start_layer);
3101 int itarget = display.getLayerSet().indexOf(target_layer);
3102 final java.util.List<Layer> layers = display.getLayerSet().getLayers(ithis, itarget);
3104 Calibration cal
= display
.getLayerSet().getCalibrationCopy();
3105 final double pixelWidth
= cal
.pixelWidth
;
3106 final double pixelHeight
= cal
.pixelHeight
;
3108 //final double dist_to_travel = Math.sqrt(Math.pow((tx - ox)*pixelWidth, 2) + Math.pow((ty - oy)*pixelHeight, 2)
3109 // + Math.pow((start_layer.getZ() - target_layer.getZ()) * pixelWidth, 2));
3111 // vector in calibrated coords between origin and target
3112 final Vector3d g
= new Vector3d((tx
- ox
)*pixelWidth
, (ty
- oy
)*pixelHeight
, (target_layer
.getZ() - start_layer
.getZ())*pixelWidth
);
3114 final ScheduledFuture
<?
>[] sf
= new ScheduledFuture
[1];
3115 sf
[0] = animate(new Runnable() {
3117 if (DisplayCanvas
.this.srcRect
.contains(target
)) {
3118 // reached destination
3119 if (display
.getLayer() != target_layer
) display
.toLayer(target_layer
);
3120 playHighlight(target
);
3121 cancelAnimation(sf
[0]);
3123 setSrcRect(srcRect
.x
+ (int)v
.x
, srcRect
.y
+ (int)v
.y
, srcRect
.width
, srcRect
.height
);
3125 if (start_layer
!= target_layer
) {
3126 int cx
= srcRect
.x
+ srcRect
.width
/2;
3127 int cy
= srcRect
.y
+ srcRect
.height
/2;
3128 double dist
= Math
.sqrt(Math
.pow((cx
- ox
)*pixelWidth
, 2) + Math
.pow((cy
- oy
)*pixelHeight
, 2)
3129 + Math
.pow((display
.getLayer().getZ() - start_layer
.getZ()) * pixelWidth
, 2));
3131 Vector3d gg
= new Vector3d(g
);
3133 gg
.scale((float)dist
);
3134 Layer la
= display
.getLayerSet().getNearestLayer(start_layer
.getZ() + gg
.z
/pixelWidth
);
3135 if (la
!= display
.getLayer()) {
3136 display
.toLayer(la
);
3139 display
.repaintAll2();
3142 }, 0, 50, TimeUnit
.MILLISECONDS
);
3146 private ScheduledExecutorService animator
= null;
3147 private boolean zoom_and_pan
= true;
3148 private final Vector
<ScheduledFuture
<?
>> sfs
= new Vector
<ScheduledFuture
<?
>>();
3150 private void cancelAnimations() {
3151 if (sfs
.isEmpty()) return;
3152 Vector
<ScheduledFuture
<?
>> sfs
;
3153 synchronized (this.sfs
) { sfs
= new Vector
<ScheduledFuture
<?
>>(this.sfs
); }
3154 for (ScheduledFuture
<?
> sf
: sfs
) {
3161 } catch (InterruptedException ie
) {}
3162 // Re-enable input, in case the watcher task is canceled as well:
3163 // (It's necessary since there isn't any easy way to tell the scheduler to execute a code block when it cancels its tasks).
3166 private void cancelAnimation(final ScheduledFuture
<?
> sf
) {
3172 private void restoreUserInput() {
3173 zoom_and_pan
= true;
3174 display
.getProject().setReceivesInput(true);
3177 private ScheduledFuture
<?
> animate(Runnable run
, long initialDelay
, long delay
, TimeUnit units
) {
3179 // Cancel any animations currently running
3181 // Disable user input
3182 display
.getProject().setReceivesInput(false);
3183 zoom_and_pan
= false;
3184 // Create tasks to run periodically: a task and a watcher task
3185 final ScheduledFuture
<?
>[] sf
= new ScheduledFuture
[2];
3186 sf
[0] = animator
.scheduleWithFixedDelay(run
, initialDelay
, delay
, units
);
3187 sf
[1] = animator
.scheduleWithFixedDelay(new Runnable() {
3189 if (sf
[0].isCancelled()) {
3190 // Enable user input
3191 zoom_and_pan
= true;
3192 display
.getProject().setReceivesInput(true);
3197 }, 100, 700, TimeUnit
.MILLISECONDS
);
3198 // Store task for future cancelation
3200 // but not the watcher task, which must finish on its own after the main task finishes.
3204 /** Draw a dotted circle centered on the given Rectangle. */
3205 private final class Highlighter
{
3206 Ellipse2D
.Float elf
;
3207 final Stroke stroke
= new BasicStroke(2, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
, 3, new float[]{4,4,4,4}, 0);
3209 final Rectangle target
;
3210 Highlighter(final Rectangle target
) {
3211 this.target
= target
;
3212 elf
= new Ellipse2D
.Float(target
.x
, target
.y
, target
.width
, target
.height
);
3213 display
.getLayerSet().getOverlay().add(elf
, Color
.yellow
, stroke
, true);
3214 dec
= (float)((Math
.max(target
.width
, target
.height
)*magnification
/ 10)/magnification
);
3217 invalidateVolatile();
3218 repaint(target
, 5, false);
3219 // setup next iteration
3220 display
.getLayerSet().getOverlay().remove(elf
);
3221 Ellipse2D
.Float elf2
= (Ellipse2D
.Float
) elf
.clone();
3224 elf2
.width
-= (dec
+dec
);
3225 elf2
.height
-= (dec
+dec
);
3226 if (elf2
.width
> 1 || elf2
.height
> 1) {
3228 display
.getLayerSet().getOverlay().add(elf
, Color
.yellow
, stroke
, true);
3231 display
.getLayerSet().getOverlay().remove(elf
);
3236 display
.getLayerSet().getOverlay().remove(elf
);
3240 private interface Animation
extends Runnable
{}
3242 private ScheduledFuture
<?
> playHighlight(final Rectangle target
) {
3244 final Highlighter highlight
= new Highlighter(target
);
3245 final ScheduledFuture
<?
>[] sf
= (ScheduledFuture
<?
>[])new ScheduledFuture
[2];
3246 sf
[0] = animator
.scheduleWithFixedDelay(new Animation() {
3248 if (!highlight
.next()) {
3249 cancelAnimation(sf
[0]);
3250 highlight
.cleanup();
3253 }, 10, 100, TimeUnit
.MILLISECONDS
);
3254 sf
[1] = animator
.scheduleWithFixedDelay(new Animation() {
3256 if (sf
[0].isCancelled()) {
3257 highlight
.cleanup();
3258 sf
[1].cancel(true); // itself
3261 }, 50, 100, TimeUnit
.MILLISECONDS
);
3266 synchronized private void initAnimator() {
3267 if (null == animator
) animator
= Executors
.newScheduledThreadPool(2);