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
;
27 import ij
.process
.ByteProcessor
;
28 import ij
.process
.ColorProcessor
;
29 import ini
.trakem2
.Project
;
30 import ini
.trakem2
.persistence
.Loader
;
31 import ini
.trakem2
.utils
.ProjectToolbar
;
32 import ini
.trakem2
.utils
.*;
33 import ini
.trakem2
.imaging
.*;
35 import java
.awt
.event
.*;
37 import java
.awt
.geom
.AffineTransform
;
38 import java
.awt
.geom
.Area
;
39 import java
.awt
.image
.BufferedImage
;
40 import java
.awt
.image
.BufferStrategy
;
41 import java
.awt
.image
.VolatileImage
;
43 import java
.awt
.Cursor
;
44 import java
.util
.concurrent
.atomic
.AtomicInteger
;
46 import ini
.trakem2
.utils
.Lock
;
49 public final class DisplayCanvas
extends ImageCanvas
implements KeyListener
/*, FocusListener*/, MouseWheelListener
{
51 private Display display
;
53 private boolean update_graphics
= false;
54 private BufferedImage offscreen
= null;
55 private ArrayList al_top
= new ArrayList();
57 private final Lock lock_paint
= new Lock();
59 private Rectangle box
= null; // the bounding box of the active
61 private FakeImageWindow fake_win
;
63 private FreeHandProfile freehandProfile
= null;
64 private Robot r
;// used for setting the mouse pointer
66 //private RepaintThread rt_old = null;
67 /** Any painting operation should wait on this object, set the controling flag to true, and when done set controling to false and controler.notifyAll(). */
68 private final Object controler_ob
= new Object();
69 private boolean controling
= false;
71 private final Lock offscreen_lock
= new Lock();
73 private Cursor noCursor
;
75 private boolean snapping
= false;
76 private boolean dragging
= false;
77 private boolean input_disabled
= false;
78 private boolean input_disabled2
= false;
80 private static final int NONE
= 0;
81 private static final int MOUSE
= 1;
82 private static final int KEY_MOVE
= 2;
83 private static final int Z_KEY
= 4; // the undo system
85 /** Store a copy of whatever data as each Class may define it, one such data object per class.
86 * Private to the package. */
87 static private Hashtable
<Class
,Object
> copy_buffer
= new Hashtable
<Class
,Object
>();
89 static void setCopyBuffer(final Class c
, final Object ob
) { copy_buffer
.put(c
, ob
); }
90 static Object
getCopyBuffer(final Class c
) { return copy_buffer
.get(c
); }
92 static private boolean openglEnabled
= false;
93 static private boolean quartzEnabled
= false;
94 static private boolean ddscaleEnabled
= false;
96 // Private to the display package:
97 static final RenderingHints rhints
;
99 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent; */
101 final Map
<RenderingHints
.Key
, Object
> hints
= new HashMap
<RenderingHints
.Key
, Object
>();
103 String openglProperty
= System
.getProperty("sun.java2d.opengl");
104 openglEnabled
= openglProperty
!= null && Boolean
.parseBoolean(openglProperty
);
105 } catch (Exception ex
) { }
107 String quartzProperty
= System
.getProperty("apple.awt.graphics.UseQuartz");
108 quartzEnabled
= Boolean
.parseBoolean(quartzProperty
);
109 } catch (Exception ex
) { }
111 String ddscaleProperty
= System
.getProperty("sun.java2d.ddscale");
112 String d3dProperty
= System
.getProperty("sun.java2d.d3d");
113 ddscaleEnabled
= Boolean
.parseBoolean(ddscaleProperty
) && Boolean
.parseBoolean(d3dProperty
);
114 } catch (Exception ex
) { }
117 // Bilinear interpolation can be accelerated by the OpenGL pipeline
118 hints
.put(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_BILINEAR
);
119 hints
.put(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
121 } else if (quartzEnabled
) {
122 //hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
123 hints
.put(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
);
124 hints
.put(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_SPEED
);
125 hints
.put(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_OFF
);
127 } else if (ddscaleEnabled
) {
128 hints
.put(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_BILINEAR
);
131 rhints
= new RenderingHints(hints
);
134 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent; */
136 private ActionListener resourceReaper = new ActionListener() {
137 public void actionPerformed(final ActionEvent ae) {
138 if (!frameRendered) {
139 if (volatileImage != null) {
140 volatileImage.flush();
141 volatileImage = null;
143 frameRendered = false;
145 // Stop the timer so we don't wakeup needlessly
146 resourceTimer.stop();
152 private VolatileImage volatileImage
;
153 //private javax.swing.Timer resourceTimer = new javax.swing.Timer(10000, resourceReaper);
154 //private boolean frameRendered = false;
155 private boolean invalid_volatile
= false;
157 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent; */
158 private void renderVolatileImage(final BufferedImage bufferedImage
, final Displayable active
, final Displayable
[] top
, final Layer active_layer
, final int c_alphas
, final AffineTransform at
, Rectangle clipRect
) {
160 final int w
= getWidth(), h
= getHeight();
161 final GraphicsConfiguration gc
= getGraphicsConfiguration();
162 if (invalid_volatile
|| volatileImage
== null || volatileImage
.getWidth() != w
163 || volatileImage
.getHeight() != h
164 || volatileImage
.validate(gc
) == VolatileImage
.IMAGE_INCOMPATIBLE
) {
165 if (volatileImage
!= null) {
166 volatileImage
.flush();
168 volatileImage
= gc
.createCompatibleVolatileImage(w
, h
);
169 volatileImage
.setAccelerationPriority(1.0f
);
170 invalid_volatile
= false;
171 clipRect
= null; // paint all
174 // Now paint the BufferedImage into the accelerated image
176 final Graphics2D g
= volatileImage
.createGraphics();
179 if (null != clipRect
) g
.setClip(clipRect
);
181 // 1 - Erase any background
182 g
.setColor(Color
.black
);
183 if (null == clipRect
) g
.fillRect(0, 0, w
, h
);
184 else g
.fillRect(clipRect
.x
, clipRect
.y
, clipRect
.width
, clipRect
.height
);
186 // 2 - Paint offscreen image
187 g
.drawImage(bufferedImage
, 0, 0, null);
189 // 3 - Paint the active Displayable and all cached on top
190 if (null != active_layer
) {
192 g
.setStroke(this.stroke
); // AFTER setting the transform
193 // Active has to be painted wherever it is, within al_top
194 //if (null != active && active.getClass() != Patch.class && !active.isOutOfRepaintingClip(magnification, srcRect, clipRect)) active.paint(g, magnification, true, c_alphas, active_layer);
196 final Rectangle tmp
= null != clipRect ?
new Rectangle() : null;
197 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;
198 for (int i
=0; i
<top
.length
; i
++) {
199 if (null != clipRect
&& !top
[i
].getBoundingBox(tmp
).intersects(clip
)) continue;
200 top
[i
].paint(g
, magnification
, top
[i
] == active
, c_alphas
, active_layer
);
206 } while (volatileImage
.contentsLost());
209 /** Adapted code from Wayne Meissner, for gstreamer-java org.gstreamer.swing.GstVideoComponent;
210 * Paints (and re-renders, if necessary) the volatile image onto the given Graphics object, which
211 * is that of the DisplayCanvas as provided to the paint(Graphics g) method.
213 * Expects clipRect in screen coordinates
215 private void render(final Graphics g
, final Displayable active
, final Displayable
[] top
, final Layer active_layer
, final int c_alphas
, final AffineTransform at
, Rectangle clipRect
) {
216 final Graphics2D g2d
= (Graphics2D
) g
.create();
217 g2d
.setRenderingHints(rhints
);
219 if (invalid_volatile
|| null == volatileImage
220 || volatileImage
.validate(getGraphicsConfiguration()) != VolatileImage
.IMAGE_OK
)
222 // clear clip, remake in full
224 renderVolatileImage(offscreen
, active
, top
, active_layer
, c_alphas
, at
, clipRect
);
226 if (null != clipRect
) g2d
.setClip(clipRect
);
227 g2d
.drawImage(volatileImage
, 0, 0, null);
228 } while (volatileImage
.contentsLost());
233 // Restart the resource reaper timer if neccessary
236 if (!frameRendered) {
237 frameRendered = true;
238 if (!resourceTimer.isRunning()) {
239 resourceTimer.restart();
245 protected void invalidateVolatile() {
246 this.invalid_volatile
= true;
251 public DisplayCanvas(Display display
, int width
, int height
) {
252 super(new FakeImagePlus(width
, height
, display
));
253 fake_win
= new FakeImageWindow(imp
, this, display
);
254 this.display
= display
;
255 this.imageWidth
= width
;
256 this.imageHeight
= height
;
257 removeKeyListener(IJ
.getInstance());
258 addKeyListener(this);
259 addMouseWheelListener(this);
262 public Display
getDisplay() { return display
; }
264 /** Used to constrain magnification so that only snapshots are used for painting when opening a new, large and filled Display. */
265 protected void setInitialMagnification(double mag
) { // calling this method 'setMagnification' would conflict with the super class homonimous method.
266 this.magnification
= mag
; // don't save in the database. This value is overriden when reopening from the database by calling the setup method.
269 /** Used for restoring properties from the database. */
270 public void setup(double mag
, Rectangle srcRect
) {
271 this.magnification
= mag
;
272 this.srcRect
= (Rectangle
)srcRect
.clone(); // just in case
273 super.setDrawingSize((int)Math
.ceil(srcRect
.width
* mag
), (int)Math
.ceil(srcRect
.height
* mag
));
274 setMagnification(mag
);
275 display
.pack(); // TODO should be run via invokeLater ... need to check many potential locks of invokeLater calling each other.
278 /** Does not repaint. */
279 public void setDimensions(double width
, double height
) {
280 this.imageWidth
= (int)Math
.ceil(width
);
281 this.imageHeight
= (int)Math
.ceil(height
);
282 ((FakeImagePlus
)imp
).setDimensions(imageWidth
, imageHeight
);
286 /** Overriding to disable it. */
287 public void handlePopupMenu() {}
289 public void update(Graphics g
) {
290 // 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.
294 //private AtomicInteger counter = new AtomicInteger(0); // threads' tag
296 private long last_paint
= 0;
298 /** Handles repaint event requests and the generation of offscreen threads. */
299 private final AbstractRepaintThread RT
= new AbstractRepaintThread(this, "T2-Canvas-Repainter", new OffscreenThread()) {
300 protected void handleUpdateGraphics(final Component target
, final Rectangle clipRect
) {
301 this.off
.setProperties(new RepaintProperties(clipRect
, display
.getLayer(), target
.getWidth(), target
.getHeight(), display
.getActive(), display
.getDisplayChannelAlphas()));
306 private final void setRenderingHints(final Graphics2D g) {
307 // so slow!! Particularly the first one.
308 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
309 //g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
310 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // to smooth edges of the images
311 //g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
312 //g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
316 public void setMagnification(double mag
) {
317 // ensure a stroke of thickness 1.0 regardless of magnification
318 this.stroke
= new BasicStroke((float)(1.0/mag
), BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
);
319 // FIXES MAG TO ImageCanvas.zoomLevel LIMITS!!
320 //super.setMagnification(mag);
322 this.magnification
= mag
;
323 imp
.setTitle(imp
.getTitle());
326 /** Paint lines always with a thickness of 1 pixel. This stroke is modified when the magnification is changed, to compensate. */
327 private BasicStroke stroke
= new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
);
329 /** The affine transform representing the srcRect displacement and the magnification. */
330 private final AffineTransform atc
= new AffineTransform();
332 public void paint(final Graphics g
) {
334 synchronized (lock_paint
) {
338 // ensure proper positioning
339 g
.translate(0, 0); // ints!
341 final Rectangle clipRect
= g
.getClipBounds();
343 final Displayable active
= display
.getActive();
344 final int c_alphas
= display
.getDisplayChannelAlphas();
346 final Layer active_layer
= display
.getLayer();
348 final Graphics2D g2d
= (Graphics2D
)g
;
350 Displayable
[] di
= null;
352 synchronized (offscreen_lock
) {
353 offscreen_lock
.lock();
356 // prepare the canvas for the srcRect and magnification
357 final AffineTransform at_original
= g2d
.getTransform();
359 atc
.scale(magnification
, magnification
);
360 atc
.translate(-srcRect
.x
, -srcRect
.y
);
361 at_original
.preConcatenate(atc
);
363 di
= new Displayable
[al_top
.size()];
366 //Utils.log2("al_top.size(): " + di.length);
368 if (null != offscreen
) {
369 //g.drawImage(offscreen, 0, 0, null);
370 if (dragging
) invalidateVolatile(); // to update the active at least
371 render(g
, active
, di
, active_layer
, c_alphas
, at_original
, clipRect
);
374 g2d
.setTransform(at_original
);
376 } catch (Exception e
) {
379 offscreen_lock
.unlock();
382 g2d
.setStroke(this.stroke
);
384 // paint a pink frame around selected objects, and a white frame around the active object
385 final Selection selection
= display
.getSelection();
386 if (null != selection
&& ProjectToolbar
.getToolId() < ProjectToolbar
.PENCIL
) { // i.e. PENCIL, PEN and ALIGN
387 selection
.paint(g2d
, srcRect
, magnification
);
391 //if (null != display.getLayer().root) display.getLayer().root.paint(g2d, srcRect, magnification, Color.red);
392 //if (null != display.getLayer().getParent().root) display.getLayer().getParent().root.paint(g2d, srcRect, magnification, Color.blue);
396 g2d
.setTransform(new AffineTransform());
397 // reset to 1.0 thickness
398 g2d
.setStroke(new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
));
400 final Align align
= null != active_layer ? active_layer
.getParent().getAlign() : null;
402 align
.paint(active_layer
, g2d
, srcRect
, magnification
);
405 // paint brush outline for AreaList
406 if (mouse_in
&& null != active
&& ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
&& active
.getClass() == AreaList
.class) {
407 int brushSize
= ProjectToolbar
.getBrushSize();
408 g
.setColor(active
.getColor());
409 g
.drawOval((int)((xMouse
-srcRect
.x
-brushSize
/2)*magnification
), (int)((yMouse
- srcRect
.y
-brushSize
/2)*magnification
), (int)(brushSize
* magnification
), (int)(brushSize
* magnification
));
412 final Roi roi
= imp
.getRoi();
418 if (null != freehandProfile
) {
419 freehandProfile
.paint(g
, magnification
, srcRect
, true);
421 noCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(new BufferedImage(1,1,BufferedImage
.TYPE_BYTE_BINARY
), new Point(0,0), "noCursor");
424 final long now
= System
.currentTimeMillis();
425 //p("interval: " + (now - last_paint));
428 } catch (Exception e
) {
429 Utils
.log2("DisplayCanvas.paint(Graphics) Error: " + e
);
432 synchronized (lock_paint
) {
438 public void waitForRepaint() {
439 // wait for all offscreen methods to finish painting
441 // wait for the paint method to finish painting
442 synchronized (lock_paint
) {
443 // wait until painting is done
447 /** Paints a handle on the screen coords. Adapted from ij.gui.Roi class. */
448 static public void drawHandle(final Graphics g
, final int x
, final int y
, final double magnification
) {
449 final int width5
= (int)Math
.round(5 / magnification
);
450 final int width3
= (int)Math
.round(3 / magnification
);
451 final int corr2
= (int)Math
.round(2 / magnification
);
452 final int corr1
= (int)Math
.ceil(1 / magnification
);
453 g
.setColor(Color
.white
);
454 g
.drawRect(x
- corr2
, y
- corr2
, width5
, width5
);
455 g
.setColor(Color
.black
);
456 g
.drawRect(x
- corr1
, y
- corr1
, width3
, width3
);
457 g
.setColor(Color
.white
);
458 g
.fillRect(x
, y
, corr1
, corr1
);
461 /** Paints a handle on the offscreen x,y. Adapted from ij.gui.Roi class. */
462 private void drawHandle(Graphics g
, double x
, double y
) {
463 g
.setColor(Color
.black
);
464 g
.fillRect((int) ((x
- srcRect
.x
) * magnification
) - 1, (int) ((y
- srcRect
.y
) * magnification
) - 1, 3, 3);
465 g
.setColor(Color
.white
);
466 g
.drawRect((int) ((x
- srcRect
.x
) * magnification
) - 2, (int) ((y
- srcRect
.y
) * magnification
) - 2, 5, 5);
469 protected void setDrawingColor(int ox
, int oy
, boolean setBackground
) {
470 super.setDrawingColor(ox
, oy
, setBackground
);
474 private int x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
;
476 private boolean popup
= false;
478 private boolean locked
= false; // TODO temporary!
480 private int tmp_tool
= -1;
482 public void mousePressed(MouseEvent me
) {
484 this.flags
= me
.getModifiers();
486 x_p
= x_d
= srcRect
.x
+ (int) (me
.getX() / magnification
); // offScreenX(me.getX());
487 y_p
= y_d
= srcRect
.y
+ (int) (me
.getY() / magnification
); // offScreenY(me.getY());
492 // ban if beyond bounds:
493 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
498 popup
= false; // not reset properly in macosx
499 if (Utils
.isPopupTrigger(me
)) {
501 display
.getPopupMenu().show(this, me
.getX(), me
.getY());
508 int tool
= ProjectToolbar
.getToolId();
510 // pan with middle mouse like in inkscape
511 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
512 if (0 != (flags & InputEvent.BUTTON2_MASK))
514 if (me
.getButton() == MouseEvent
.BUTTON2
) {
516 ProjectToolbar
.setTool(Toolbar
.HAND
);
519 //Utils.log2("button: " + me.getButton() + " BUTTON2: " + MouseEvent.BUTTON2);
522 case Toolbar
.MAGNIFIER
:
523 if (me
.isAltDown()) zoomOut(me
.getX(), me
.getY());
524 else zoomIn(me
.getX(), me
.getY());
527 super.setupScroll(x_p
, y_p
); // offscreen coords.
528 //display.repaintAll();
532 if (input_disabled
) {
533 input_disabled2
= true;
534 Utils
.showMessage("Please wait while completing the task.\nOnly the glass and hand tool are enabled.");
535 return; // only zoom and pan are allowed
538 Displayable active
= display
.getActive();
543 if (null != active
&& active
instanceof Patch
) {
544 me
.translatePoint(-(int) active
.getX(), -(int) active
.getY());
545 super.mousePressed(me
);
547 // TODO should use LayerStack virtualization ... then scale back the ROI
550 case ProjectToolbar
.PENCIL
:
551 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
552 Profile prof
= (Profile
) active
;
553 this.freehandProfile
= new FreeHandProfile(prof
);
554 freehandProfile
.mousePressed(x_p
, y_p
);
558 case Toolbar
.RECTANGLE
:
560 case Toolbar
.POLYGON
:
561 case Toolbar
.FREEROI
:
563 case Toolbar
.POLYLINE
:
564 case Toolbar
.FREELINE
:
567 // pass the mouse event to superclass ImageCanvas.
568 super.mousePressed(me
);
571 case Toolbar
.DROPPER
:
573 setDrawingColor(x_p
, y_p
, me
.isAltDown());
578 if (display
.isReadOnly()) return;
582 // edit a label, or add a new one
583 if (null == active
|| !active
.contains(x_p
, y_p
)) {
584 // find a Displayable to activate, if any
585 display
.choose(me
.getX(), me
.getY(), x_p
, y_p
, DLabel
.class);
586 active
= display
.getActive();
588 if (null != active
&& active
.isVisible() && active
instanceof DLabel
) {
590 ((DLabel
) active
).edit();
593 DLabel label
= new DLabel(display
.getProject(), " ", x_p
, y_p
);
594 display
.getLayer().add(label
);
600 // SPECIFIC for SELECT and above tools
602 // no ROIs allowed past this point
603 if (tool
>= ProjectToolbar
.SELECT
) imp
.killRoi();
606 Selection selection
= display
.getSelection();
607 if (selection
.isTransforming()) {
608 box
= selection
.getLinkedBox();
609 selection
.mousePressed(me
, x_p
, y_p
, magnification
);
612 // select or deselect another active Displayable, or add it to the selection group:
613 if (ProjectToolbar
.SELECT
== tool
) {
614 display
.choose(me
.getX(), me
.getY(), x_p
, y_p
, me
.isShiftDown(), null);
616 active
= display
.getActive();
617 selection
= display
.getSelection();
619 if (ProjectToolbar
.ALIGN
== tool
) {
620 LayerSet set
= display
.getLayer().getParent();
621 if (!set
.isAligning()) {
622 set
.startAlign(display
);
624 set
.getAlign().mousePressed(display
.getLayer(), me
, x_p
, y_p
, magnification
);
628 if (null == active
|| !active
.isVisible()) return;
631 case ProjectToolbar
.SELECT
:
632 // check if the active is usable:
633 // check if the selection contains locked objects
634 if (selection
.isLocked()) {
638 if (selection
.isEmpty()) {
642 // gather initial box (for repainting purposes)
643 box
= selection
.getLinkedBox();
644 selection
.mousePressed(me
, x_p
, y_p
, magnification
);
646 default: // the PEN and PENCIL tools, and any other custom tool
647 display
.getLayerSet().addPreDataEditStep(active
);
648 box
= active
.getBoundingBox();
649 active
.mousePressed(me
, x_p
, y_p
, magnification
);
650 invalidateVolatile();
653 //Utils.log("locked: " + locked + " popup: " + popup + " input_disabled2: " + input_disabled2);
656 public void mouseDragged(MouseEvent me
) {
657 // ban if beyond bounds:
658 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
662 Selection selection
= display
.getSelection();
663 if (locked
&& !selection
.isEmpty()) {
664 Utils
.log("Selection is locked.");
672 this.flags
= me
.getModifiers();
677 x_d
= srcRect
.x
+ (int) (me
.getX() / magnification
); // offscreen
678 y_d
= srcRect
.y
+ (int) (me
.getY() / magnification
);
684 int me_x
= me
.getX();
685 int me_y
= me
.getY();
686 if (me_x
< 0 || me_x
> this.getWidth() || me_y
< 0 || me_y
> this.getHeight()) {
692 int tool
= ProjectToolbar
.getToolId();
695 // pan with middle mouse like in inkscape
696 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
697 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
700 */ // so the above has been implemented as a temporary switch to the HAND tool at the mousePressed function.
703 case Toolbar
.MAGNIFIER
: // TODO : create a zooms-area tool
708 scroll(me
.getX(), me
.getY());
709 if (0 != srx
- srcRect
.x
|| 0 != sry
- srcRect
.y
) {
710 update_graphics
= true; // update the offscreen images.
711 display
.getNavigator().repaint(false);
717 if (input_disabled2
) return;
719 if (null != display
.getLayer().getParent().getAlign()) return;
723 //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));
725 // Code for Matthias' FreehandProfile (TODO this should be done on mousePressed, not on mouseDragged)
727 Displayable active
= display
.getActive();
728 if (null != active
&& active
.getClass() == Profile
.class) {
731 r
= new Robot(this.getGraphicsConfiguration().getDevice());
733 } catch (AWTException e
) {
739 case ProjectToolbar
.PENCIL
:
740 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
741 if (freehandProfile
== null)
742 return; // starting painting out of the DisplayCanvas border
743 double dx
= x_d
- x_d_old
;
744 double dy
= y_d
- y_d_old
;
745 freehandProfile
.mouseDragged(me
, x_d
, y_d
, dx
, dy
);
747 // Point screenLocation = getLocationOnScreen();
748 // mousePos[0] += screenLocation.x;
749 // mousePos[1] += screenLocation.y;
750 // r.mouseMove( mousePos[0], mousePos[1]);
754 case Toolbar
.RECTANGLE
:
756 case Toolbar
.POLYGON
:
757 case Toolbar
.FREEROI
:
759 case Toolbar
.POLYLINE
:
760 case Toolbar
.FREELINE
:
763 // pass the mouse event to superclass ImageCanvas.
764 super.mouseDragged(me
);
768 // no ROIs beyond this point
769 if (tool
>= ProjectToolbar
.SELECT
) imp
.killRoi();
773 if (display
.isReadOnly()) return;
775 if (null != active
&& active
.isVisible()) {
776 // prevent dragging beyond the layer limits
777 if (display
.getLayer().contains(x_d
, y_d
, 1)) {
780 case ProjectToolbar
.SELECT
:
781 selection
.mouseDragged(me
, x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
782 box2
= selection
.getLinkedBox();
784 // repaint all Displays (where it was and where it is now, hence the sum of both boxes):
785 //TODO//Utils.log2("md: " + box.toString());
786 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, false, active
.isLinked() || active
.getClass() == Patch
.class);
787 // box for next mouse dragged iteration
790 case ProjectToolbar
.ALIGN
:
793 active
.mouseDragged(me
, x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
794 // the line above must repaint on its own
798 locked
= true; // TODO temporary until the snapTo and mouseEntered issues are fixed
799 Utils
.log("DisplayCanvas.mouseDragged: preventing drag beyond layer limits.");
804 public void mouseReleased(MouseEvent me
) {
805 boolean dragging2
= dragging
;
812 // ban if beyond bounds:
813 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
817 int tool
= ProjectToolbar
.getToolId();
819 // pan with middle mouse like in inkscape
820 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
821 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
827 case Toolbar
.MAGNIFIER
:
828 // display.updateInDatabase("srcRect"); // TODO if the display.frame
829 // is shrinked, the pack() in the zoom methods will also call the
830 // updateInDatabase("srcRect") (so it's going to be done twice)
831 display
.updateFrameTitle();
834 display
.updateInDatabase("srcRect");
835 if (-1 != tmp_tool
) {
836 ProjectToolbar
.setTool(tmp_tool
);
839 if (!dragging2
) repaint(true); // TEMPORARY just to allow fixing bad screen when simply cliking with the hand
843 if (input_disabled2
) {
844 input_disabled2
= false; // reset
851 String msg
= "\nRight-click and select\"";
852 if (null != display
.getActive()) {
853 msg
+= display
.getActive().getClass() == Patch
.class ?
"Unlock" : "Unlink";
856 Utils
.showMessage("Selection is locked or contains links to a locked object." + msg
);
861 if (display
.getLayer().getParent().isAligning()) {
865 // pan with middle mouse like in inkscape
866 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
867 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
872 this.flags
= me
.getModifiers();
873 flags
&= ~InputEvent
.BUTTON1_MASK
; // make sure button 1 bit is not set (FOR AreaList brush-like)
874 flags
&= ~InputEvent
.BUTTON2_MASK
; // make sure button 2 bit is not set
875 flags
&= ~InputEvent
.BUTTON3_MASK
; // make sure button 3 bit is not set
877 int x_r
= srcRect
.x
+ (int)(me
.getX() / magnification
);
878 int y_r
= srcRect
.y
+ (int)(me
.getY() / magnification
);
883 Displayable active
= display
.getActive();
886 case ProjectToolbar
.PENCIL
:
887 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
888 if (freehandProfile
== null)
889 return; // starting painting out of the DisplayCanvas boarder
890 freehandProfile
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
891 freehandProfile
= null;
893 Selection selection
= display
.getSelection();
894 selection
.updateTransform(display
.getActive());
895 Display
.repaint(display
.getLayer(), selection
.getBox(), Selection
.PADDING
); // repaints the navigator as well
899 case Toolbar
.RECTANGLE
:
901 case Toolbar
.POLYGON
:
902 case Toolbar
.FREEROI
:
904 case Toolbar
.POLYLINE
:
905 case Toolbar
.FREELINE
:
908 // pass the mouse event to superclass ImageCanvas.
909 super.mouseReleased(me
);
911 // return; // replaced by #SET_ROI
914 final Roi roi
= imp
.getRoi();
917 if (display
.isReadOnly()) return;
919 if (tool
>= ProjectToolbar
.SELECT
) {
920 if (null != roi
) imp
.killRoi();
921 } else return; // #SET_ROI
923 Selection selection
= display
.getSelection();
927 selection
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
928 box
.add(selection
.getLinkedBox());
929 Display
.repaint(display
.getLayer(), box
, Selection
.PADDING
); // repaints the navigator as well
930 StitchingTEM
.snap(active
, display
); // will repaint whatever is appropriate (the visible linked group snapped along)
936 if (null != active
&& active
.isVisible()) {
938 case ProjectToolbar
.SELECT
:
939 selection
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
940 box
.add(selection
.getLinkedBox());
941 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, !selection
.isTransforming(), active
.isLinked() || active
.getClass() == Patch
.class); // does not repaint the navigator
943 case ProjectToolbar
.PENCIL
:
944 case ProjectToolbar
.PEN
:
945 active
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
); // active, not selection (Selection only handles transforms, not active's data editions)
946 // update active's bounding box
947 selection
.updateTransform(active
);
948 box
.add(selection
.getBox());
949 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, !selection
.isTransforming(), active
.isLinked() || active
.getClass() == Patch
.class); // does not repaint the navigator
950 //if (!active.getClass().equals(AreaList.class)) Display.repaint(display.getLayer(), box, Selection.PADDING); // repaints the navigator as well
951 // TODO: this last repaint call is unnecessary, if the box was properly repainted on mouse drag for Profile etc.
953 if (null != old_brush_box
) {
954 repaint(old_brush_box
, 0, false);
955 old_brush_box
= null; // from mouseMoved
957 // The current state:
958 display
.getLayerSet().addDataEditStep(active
);
964 // private to the package
965 boolean isDragging() {
966 if (null == display
.getSelection()) {
967 Utils
.log2("WARNING DisplayCanvas.isDragging thinks the display.getSelection() gives a null object ?!?");
970 return display
.getSelection().isDragging();
973 private boolean mouse_in
= false;
975 public void mouseEntered(MouseEvent me
) {
977 // try to catch focus if the JFrame is front most
978 if (display
.isActiveWindow() && !this.hasFocus()) {
981 // bring dragged point to mouse pointer
982 // TODO doesn't work as expected.
984 Displayable active = display.getActive();
985 int x = offScreenX(me.getX());
986 int y = offScreenY(me.getY());
987 if (null != active) {
988 active.snapTo(x, y, x_p, y_p);
989 x_p = x_d = x_d_old = x;
990 y_p = y_d = y_d_old = y;
993 //Utils.log2("mouseEntered x,y: " + offScreenX(me.getX()) + "," + offScreenY(me.getY()));
996 public void mouseExited(MouseEvent me
) {
998 // paint away the circular brush if any
999 if (ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
) {
1000 Displayable active
= display
.getActive();
1001 if (null != active
&& active
.isVisible() && AreaList
.class == active
.getClass()) {
1002 if (null != old_brush_box
) {
1003 this.repaint(old_brush_box
, 0);
1004 old_brush_box
= null;
1010 /** Sets the cursor based on the current tool and cursor location. */
1011 public void setCursor(int sx
, int sy
, int ox
, int oy
) {
1012 // copy of ImageCanvas.setCursor without the win==null
1015 Roi roi
= imp
.getRoi();
1017 * ImageWindow win = imp.getWindow(); if (win==null) return;
1019 if (IJ
.spaceBarDown()) {
1020 setCursor(handCursor
);
1023 switch (Toolbar
.getToolId()) {
1024 case Toolbar
.MAGNIFIER
:
1025 if (IJ
.isMacintosh())
1026 setCursor(defaultCursor
);
1028 setCursor(moveCursor
);
1031 setCursor(handCursor
);
1033 case ProjectToolbar
.SELECT
:
1034 case ProjectToolbar
.PENCIL
:
1035 case ProjectToolbar
.ALIGN
:
1036 setCursor(defaultCursor
);
1038 default: // selection tool
1040 if (roi
!= null && roi
.getState() != roi
.CONSTRUCTING
&& roi
.isHandle(sx
, sy
) >= 0)
1041 setCursor(handCursor
);
1042 else if (Prefs
.usePointerCursor
|| (roi
!= null && roi
.getState() != roi
.CONSTRUCTING
&& roi
.contains(ox
, oy
)))
1043 setCursor(defaultCursor
);
1045 setCursor(crosshairCursor
);
1049 /** Set the srcRect - used by the DisplayNavigator. */
1050 protected void setSrcRect(int x
, int y
, int width
, int height
) {
1051 this.srcRect
.setRect(x
, y
, width
, height
);
1052 display
.updateInDatabase("srcRect");
1055 public void setDrawingSize(int new_width
, int new_height
,
1056 boolean adjust_srcRect
) {
1058 if (adjust_srcRect
) {
1059 double mag
= super.getMagnification();
1060 // This method is very important! Make it fit perfectly.
1061 if (srcRect
.width
* mag
< new_width
) {
1063 if (new_width
> imageWidth
* mag
) {
1066 srcRect
.width
= imageWidth
;
1068 srcRect
.width
= (int) Math
.ceil(new_width
/ mag
);
1069 if (srcRect
.x
+ srcRect
.width
> imageWidth
) {
1070 srcRect
.x
= imageWidth
- srcRect
.width
;
1075 srcRect
.width
= (int) Math
.ceil(new_width
/ mag
);
1077 if (srcRect
.height
* mag
< new_height
) {
1079 if (new_height
> imageHeight
* mag
) {
1082 srcRect
.height
= imageHeight
;
1084 srcRect
.height
= (int) Math
.ceil(new_height
/ mag
);
1085 if (srcRect
.y
+ srcRect
.height
> imageHeight
) {
1086 srcRect
.y
= imageHeight
- srcRect
.height
;
1091 srcRect
.height
= (int) Math
.ceil(new_height
/ mag
);
1094 super.setDrawingSize(new_width
, new_height
);
1097 private void zoomIn2(int x
, int y
) {
1098 // copy of ImageCanvas.zoomIn except for the canEnlarge is different and
1099 // there's no call to the non-existing ImageWindow
1100 if (magnification
>= 32)
1102 double newMag
= getHigherZoomLevel2(magnification
);
1104 // zoom at point: correct mag drift
1105 int cx
= getWidth() / 2;
1106 int cy
= getHeight() / 2;
1107 int dx
= (int)(((x
- cx
) * magnification
) / newMag
);
1108 int dy
= (int)(((y
- cy
) * magnification
) / newMag
);
1112 // Adjust the srcRect to the new dimensions
1113 int w
= (int) Math
.round(dstWidth
/ newMag
);
1114 if (w
* newMag
< dstWidth
)
1118 int h
= (int) Math
.round(dstHeight
/ newMag
);
1119 if (h
* newMag
< dstHeight
)
1121 if (h
> imageHeight
)
1125 final Rectangle r
= new Rectangle(x
- w
/ 2, y
- h
/ 2, w
, h
);
1130 if (r
.x
+ w
> imageWidth
)
1131 r
.x
= imageWidth
- w
;
1132 if (r
.y
+ h
> imageHeight
)
1133 r
.y
= imageHeight
- h
;
1138 setMagnification(newMag
);
1139 display
.updateInDatabase("srcRect");
1140 display
.repaintAll2(); // this repaint includes this canvas's repaint as well, but also the navigator, etc. // repaint();
1143 private void zoomOut2(int x
, int y
) {
1144 //if (magnification <= 0.03125)
1146 double newMag
= getLowerZoomLevel2(magnification
);
1148 // zoom at point: correct mag drift
1149 int cx
= getWidth() / 2;
1150 int cy
= getHeight() / 2;
1151 int dx
= (int)(((x
- cx
) * magnification
) / newMag
);
1152 int dy
= (int)(((y
- cy
) * magnification
) / newMag
);
1156 if (imageWidth
* newMag
> dstWidth
|| imageHeight
* newMag
> dstHeight
) {
1157 int w
= (int) Math
.round(dstWidth
/ newMag
);
1158 if (w
* newMag
< dstWidth
)
1160 int h
= (int) Math
.round(dstHeight
/ newMag
);
1161 if (h
* newMag
< dstHeight
)
1165 Rectangle r
= new Rectangle(x
- w
/ 2, y
- h
/ 2, w
, h
);
1170 if (r
.x
+ w
> imageWidth
)
1171 r
.x
= imageWidth
- w
;
1172 if (r
.y
+ h
> imageHeight
)
1173 r
.y
= imageHeight
- h
;
1176 // Shrink srcRect, but NOT the dstWidth,dstHeight of the canvas, which remain the same:
1177 srcRect
= new Rectangle(0, 0, imageWidth
, imageHeight
);
1180 setMagnification(newMag
);
1181 display
.repaintAll2(); // this repaint includes this canvas's repaint
1182 // as well, but also the navigator, etc.
1184 display
.updateInDatabase("srcRect");
1187 /** The minimum amout of pixels allowed for width or height when zooming out. */
1188 static private final int MIN_DIMENSION
= 10; // pixels
1190 /** Enable zooming out up to the point where the display becomes 10 pixels in width or height. */
1191 protected double getLowerZoomLevel2(final double currentMag
) {
1192 // if it is 1/72 or lower, then:
1193 if (Math
.abs(currentMag
- 1/72.0) < 0.00000001 || currentMag
< 1/72.0) { // lowest zoomLevel in ImageCanvas is 1/72.0
1194 // find nearest power of two under currentMag
1195 // start at level 7, which is 1/128
1197 double scale
= currentMag
;
1198 while (scale
* srcRect
.width
> MIN_DIMENSION
&& scale
* srcRect
.height
> MIN_DIMENSION
) {
1199 scale
= 1 / Math
.pow(2, level
);
1200 // if not equal and actually smaller, break:
1201 if (Math
.abs(scale
- currentMag
) != 0.00000001 && scale
< currentMag
) break;
1206 return ImageCanvas
.getLowerZoomLevel(currentMag
);
1209 protected double getHigherZoomLevel2(final double currentMag
) {
1210 // if it is not 1/72 and its lower, then:
1211 if (Math
.abs(currentMag
- 1/72.0) > 0.00000001 && currentMag
< 1/72.0) { // lowest zoomLevel in ImageCanvas is 1/72.0
1212 // find nearest power of two above currentMag
1213 // start at level 14, which is 0.00006103515625 (0.006 %)
1214 int level
= 14; // this value may be increased in the future
1215 double scale
= currentMag
;
1216 while (level
>= 0) {
1217 scale
= 1 / Math
.pow(2, level
);
1218 if (scale
> currentMag
) break;
1223 return ImageCanvas
.getHigherZoomLevel(currentMag
);
1229 * // OBSOLETE: modified ij.gui.ImageCanvas directly
1230 public void mouseMoved(MouseEvent e) { if (IJ.getInstance()==null) return; int sx =
1231 * e.getX(); int sy = e.getY(); int ox = offScreenX(sx); int oy =
1232 * offScreenY(sy); flags = e.getModifiers(); setCursor(sx, sy, ox, oy);
1233 * IJ.setInputEvent(e); Roi roi = imp.getRoi(); if (roi!=null &&
1234 * (roi.getType()==Roi.POLYGON || roi.getType()==Roi.POLYLINE ||
1235 * roi.getType()==Roi.ANGLE) && roi.getState()==roi.CONSTRUCTING) {
1236 * PolygonRoi pRoi = (PolygonRoi)roi; pRoi.handleMouseMove(ox, oy); } else {
1237 * if (ox<imageWidth && oy<imageHeight) { //ImageWindow win =
1238 * imp.getWindow(); //if (win!=null) win.mouseMoved(ox, oy);
1239 * imp.mouseMoved(ox, oy); } else IJ.showStatus(""); } }
1242 private Rectangle old_brush_box
= null;
1244 private MouseMovedThread mouse_moved
= new MouseMovedThread();
1246 private class MouseMovedThread
extends Thread
{
1247 private MouseEvent me
= null;
1248 private boolean go
= true;
1249 MouseMovedThread() {
1250 super("T2-mouseMoved");
1252 setPriority(Thread
.NORM_PRIORITY
);
1255 void dispatch(MouseEvent me
) {
1256 //Utils.log2("before");
1257 synchronized (this) {
1265 synchronized (this) { notify(); }
1269 MouseEvent me
= null;
1270 synchronized (this) {
1271 try { this.wait(); } catch (Exception e
) {}
1275 try { mouseMoved(me
); } catch (Exception e
) { IJError
.print(e
); }
1278 private void mouseMoved(MouseEvent me
) {
1279 if (null == me
) return;
1280 if (input_disabled
|| display
.getSelection().isDragging()) return;
1282 final Displayable active
= display
.getActive();
1284 // only when no mouse buttons are down
1285 final int flags
= me
.getModifiers(); // override, the super fails for some reason
1286 if (0 == (flags
& InputEvent
.BUTTON1_MASK
)
1287 /* && 0 == (flags & InputEvent.BUTTON2_MASK) */ // this is the alt key down ..
1288 && 0 == (flags
& InputEvent
.BUTTON3_MASK
)
1289 //if (me.getButton() == MouseEvent.NOBUTTON
1290 && ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
&& null != active
&& active
.isVisible() && AreaList
.class == active
.getClass()) {
1291 // repaint area where the brush circle is
1292 int brushSize
= ProjectToolbar
.getBrushSize() +2; // +2 padding
1293 Rectangle r
= new Rectangle( xMouse
- brushSize
/2,
1294 yMouse
- brushSize
/2,
1297 Rectangle copy
= (Rectangle
)r
.clone();
1298 if (null != old_brush_box
) r
.add(old_brush_box
);
1299 old_brush_box
= copy
;
1300 repaint(r
, 1); // padding because of painting rounding which would live dirty trails
1303 if (me
.isShiftDown()) {
1304 // Print a comma-separated list of objects under the mouse pointer
1305 final Layer layer
= DisplayCanvas
.this.display
.getLayer();
1306 final int x_p
= offScreenX(me
.getX()),
1307 y_p
= offScreenY(me
.getY());
1308 final ArrayList
<Displayable
> al
= new ArrayList(layer
.getParent().findZDisplayables(layer
, x_p
, y_p
, true));
1309 final ArrayList al2
= new ArrayList(layer
.find(x_p
, y_p
, true));
1310 Collections
.reverse(al2
); // text labels first
1312 if (0 == al
.size()) {
1313 Utils
.showStatus("", false);
1316 final StringBuilder sb
= new StringBuilder();
1317 final Project pr
= layer
.getProject();
1318 for (Displayable d
: al
) sb
.append(pr
.getShortMeaningfulTitle(d
)).append(", ");
1319 sb
.setLength(sb
.length()-2);
1320 Utils
.showStatus(sb
.toString(), false);
1322 // set xMouse, yMouse, and print pixel value
1323 DisplayCanvas
.super.mouseMoved(me
);
1328 public void mouseMoved(final MouseEvent me
) {
1329 mouse_moved
.dispatch(me
);
1332 /** Zoom in using the current mouse position, or the center if the mouse is out. */
1333 public void zoomIn() {
1334 if (xMouse
< 0 || screenX(xMouse
) > dstWidth
|| yMouse
< 0 || screenY(yMouse
) > dstHeight
) {
1335 zoomIn(dstWidth
/2, dstHeight
/2);
1337 zoomIn(screenX(xMouse
), screenY(yMouse
));
1341 /** Overriding to repaint the DisplayNavigator as well. */
1342 public void zoomIn(int x
, int y
) {
1343 update_graphics
= true; // update the offscreen images.
1347 /** Zoom out using the current mouse position, or the center if the mouse is out. */
1348 public void zoomOut() {
1349 if (xMouse
< 0 || screenX(xMouse
) > dstWidth
|| yMouse
< 0 || screenY(yMouse
) > dstHeight
) {
1350 zoomOut(dstWidth
/2, dstHeight
/2);
1351 } else zoomOut(screenX(xMouse
), screenY(yMouse
));
1354 /** Overriding to repaint the DisplayNavigator as well. */
1355 public void zoomOut(int x
, int y
) {
1356 update_graphics
= true; // update the offscreen images.
1360 /** Center the srcRect around the given object(s) bounding box, zooming if necessary. */
1361 public void showCentered(Rectangle r
) {
1362 // multiply bounding box dimensions by two
1364 r
.y
-= r
.height
/ 2;
1366 r
.height
+= r
.height
;
1367 // compute target magnification
1368 double magn
= getWidth() / (double)r
.width
;
1369 // bring bounds within limits of the layer and the canvas' drawing size
1370 double lw
= display
.getLayer().getLayerWidth();
1371 double lh
= display
.getLayer().getLayerHeight();
1372 int cw
= (int) (getWidth() / magn
); // canvas dimensions in offscreen coords
1373 int ch
= (int) (getHeight() / magn
);
1376 // fit to canvas drawing size:
1377 r
.y
+= (r
.height
- ch
) / 2;
1380 // place within layer bounds
1381 if (r
.x
< 0) r
.x
= 0;
1382 if (r
.y
< 0) r
.y
= 0;
1387 if (r
.height
> lh
) {
1391 if (r
.x
+ r
.width
> lw
) r
.x
= (int)(lw
- cw
);
1392 if (r
.y
+ r
.height
> lh
) r
.y
= (int)(lh
- ch
);
1393 // compute magn again, since the desired width may have changed:
1394 magn
= getWidth() / (double)r
.width
;
1396 // set magnification and srcRect
1398 try { Thread
.sleep(200); } catch (Exception e
) {} // swing ... waiting for the display.pack()
1399 update_graphics
= true;
1400 RT
.paint(null, update_graphics
);
1401 display
.updateInDatabase("srcRect");
1402 display
.updateFrameTitle();
1403 display
.getNavigator().repaint(false);
1406 /** 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. */
1407 public void repaint(Displayable d
) {
1412 * Repaint as much as the bounding box around the given Displayable plus the
1413 * extra padding. If the Displayable is null, the entire canvas is
1414 * repainted, remaking the offscreen images.
1416 public void repaint(Displayable displ
, int extra
) {
1417 if (null != displ
) {
1418 Rectangle r
= displ
.getBoundingBox();
1419 r
.x
= (int) ((r
.x
- srcRect
.x
) * magnification
) - extra
;
1420 r
.y
= (int) ((r
.y
- srcRect
.y
) * magnification
) - extra
;
1421 r
.width
= (int) Math
.ceil(r
.width
* magnification
) + extra
+ extra
;
1422 r
.height
= (int) Math
.ceil(r
.height
* magnification
) + extra
+ extra
;
1423 RT
.paint(r
, update_graphics
);
1431 * Repaint the clip corresponding to the sum of all boundingboxes of
1432 * Displayable objects in the hashset.
1434 // it is assumed that the linked objects are close to each other, otherwise
1435 // the clip rectangle grows enormously.
1436 public void repaint(final HashSet hs
) {
1437 if (null == hs
) return;
1438 final Iterator it
= hs
.iterator();
1440 Rectangle r
= new Rectangle();
1441 final Layer dl
= display
.getLayer();
1442 while (it
.hasNext()) {
1443 final Displayable d
= (Displayable
) it
.next();
1444 if (d
.getLayer() == dl
) {
1446 r
.add(d
.getBoundingBox());
1450 //repaint(r.x, r.y, r.width, r.height);
1451 RT
.paint(r
, update_graphics
);
1456 * Repaint the given offscreen Rectangle after transforming its data on the fly to the
1457 * srcRect and magnification of this DisplayCanvas. The Rectangle is not
1460 public void repaint(final Rectangle r
, final int extra
) {
1462 //Utils.log2("DisplayCanvas.repaint(Rectangle, int) warning: null r");
1463 RT
.paint(null, update_graphics
);
1466 // 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);
1467 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
);
1471 * Repaint the given Rectangle after transforming its data on the fly to the
1472 * srcRect and magnification of this DisplayCanvas. The Rectangle is not
1474 * @param box The rectangle to repaint
1475 * @param extra The extra outbound padding to add to the rectangle
1476 * @param update_graphics Whether to recreate the offscreen images or not
1478 public void repaint(Rectangle box
, int extra
, boolean update_graphics
) {
1479 this.update_graphics
= update_graphics
;
1480 repaint(box
, extra
);
1483 /** Repaint everything, updating offscreen graphics if so specified. */
1484 public void repaint(final boolean update_graphics
) {
1485 this.update_graphics
= update_graphics
| this.update_graphics
;
1486 RT
.paint(null, update_graphics
);
1489 /** Overridden to multithread. This method is here basically to enable calls to the FakeImagePlus.draw from the HAND and other tools to repaint properly.*/
1490 public void repaint() {
1491 //Utils.log2("issuing thread");
1492 RT
.paint(null, update_graphics
);
1495 /** Overridden to multithread. */
1496 /* // saved as unoveridden to make sure there are no infinite thread loops when calling super in buggy JVMs
1497 public void repaint(long ms, int x, int y, int width, int height) {
1498 RT.paint(new Rectangle(x, y, width, height), update_graphics);
1502 /** Overridden to multithread. */
1503 public void repaint(int x
, int y
, int width
, int height
) {
1504 RT
.paint(new Rectangle(x
, y
, width
, height
), update_graphics
);
1507 public void setUpdateGraphics(boolean b
) {
1508 update_graphics
= b
;
1511 /** Release offscreen images and stop threads. */
1512 public void flush() {
1513 // cleanup update graphics thread if any
1515 synchronized (offscreen_lock
) {
1516 offscreen_lock
.lock();
1519 // reset for remaking if necessary TODO doesn't work in at least java 1.6 ?
1520 update_graphics
= true;
1522 offscreen_lock
.unlock();
1526 public void destroy() {
1528 WindowManager
.setTempCurrentImage(imp
); // the FakeImagePlus
1529 WindowManager
.removeWindow(fake_win
); // the FakeImageWindow
1532 public boolean isTransforming() { // TODO: this can fail if the Display is closed quickly after creation
1533 return display
.getSelection().isTransforming();
1536 public void setTransforming(boolean b
) {
1537 if (ProjectToolbar
.getToolId() != ProjectToolbar
.SELECT
&& b
) {
1538 ProjectToolbar
.setTool(ProjectToolbar
.SELECT
);
1540 display
.getSelection().setTransforming(b
);
1541 //repaint other Displays as well!
1542 Display
.repaint(display
.getLayerSet());
1545 public void cancelTransform() {
1546 Selection selection
= display
.getSelection();
1547 Rectangle box
= selection
.getLinkedBox();
1548 selection
.cancelTransform();
1549 box
.add(selection
.getLinkedBox()); // the restored box now.
1550 if (!(selection
.getNSelected() == 1 && !display
.getActive().isLinked())) update_graphics
= true;
1555 public void keyReleased(KeyEvent ke) {
1556 int key_code = ke.getKeyCode();
1558 case KeyEvent.VK_UP:
1559 case KeyEvent.VK_DOWN:
1560 case KeyEvent.VK_LEFT:
1561 case KeyEvent.VK_RIGHT:
1562 Selection selection = display.getSelection();
1563 Rectangle b = selection.getLinkedBox();
1564 selection.resetBox();
1565 b.add(selection.getLinkedBox());
1573 public void keyPressed(KeyEvent ke
) {
1577 if (ke.getKeyCode() == KeyEvent.VK_D && ke.isShiftDown() && ke.isAltDown() && ke.isControlDown()) {
1579 java.lang.reflect.Field f = Display.class.getDeclaredField("hs_panels");
1580 f.setAccessible(true);
1581 Utils.log("Display n_panels:" + ((java.util.HashMap)f.get(display)).size());
1582 Utils.log("Display displ.: " + display.getLayer().getDisplayables().size());
1584 } catch (Exception e) {
1591 Displayable active
= display
.getActive();
1593 if (null != freehandProfile
1594 && ProjectToolbar
.getToolId() == ProjectToolbar
.PENCIL
1595 && ke
.getKeyCode() == KeyEvent
.VK_ESCAPE
1596 && null != freehandProfile
)
1598 freehandProfile
.abort();
1604 * TODO screen editor ... TEMPORARY if (active instanceof DLabel) {
1605 * active.keyPressed(ke); ke.consume(); return; }
1608 int keyCode
= ke
.getKeyCode();
1609 int keyChar
= ke
.getKeyChar();
1611 boolean used
= false;
1630 ke
.consume(); // otherwise ImageJ would use it!
1634 if (input_disabled
) {
1635 if (KeyEvent
.VK_ESCAPE
== keyCode
) {
1636 // cancel last job if any
1637 if (Utils
.checkYN("Really cancel job?")) {
1638 display
.getProject().getLoader().quitJob(null);
1639 display
.repairGUI();
1643 return; // only zoom is enabled, above
1646 if (KeyEvent
.VK_W
== keyCode
) {
1647 display
.remove(false); // will call back the canvas.flush()
1650 } else if (KeyEvent
.VK_S
== keyCode
&& 0 == ke
.getModifiers() && display
.getProject().getLoader().isAsynchronous()) {
1651 display
.getProject().getLoader().save(display
.getProject());
1656 // if display is not read-only, check for other keys:
1659 case ',': // select next Layer up
1660 display
.previousLayer(ke
.getModifiers()); // repaints as well
1664 case '.': // select next Layer down
1665 display
.nextLayer(ke
.getModifiers());
1670 if (null == active
&& null != imp
.getRoi()) {
1671 IJ
.getInstance().keyPressed(ke
);
1675 // end here if display is read-only
1676 if (display
.isReadOnly()) {
1678 display
.repaintAll();
1682 if (KeyEvent
.VK_ENTER
== keyCode
) {
1683 if (display
.getSelection().isTransforming()) {
1684 setTransforming(false); // will apply transforms and repaint
1687 } else if (display
.getLayer().getParent().isAligning()) {
1688 display
.getLayer().getParent().applyAlign(false);
1692 IJ
.getInstance().toFront();
1698 // check preconditions (or the keys are meaningless). Allow 'enter' to
1699 // bring forward the ImageJ window, and 'v' to paste a patch.
1700 /*if (null == active && KeyEvent.VK_ENTER != keyCode && KeyEvent.VK_V != keyCode && KeyEvent) {
1704 Layer layer
= display
.getLayer();
1706 final int mod
= ke
.getModifiers();
1709 case KeyEvent
.VK_COMMA
:
1710 case 0xbc: // select next Layer up
1711 display
.nextLayer(ke
.getModifiers());
1713 case KeyEvent
.VK_PERIOD
:
1714 case 0xbe: // select next Layer down
1715 display
.previousLayer(ke
.getModifiers());
1718 // UNDO: shift+z or ctrl+z
1719 if (0 == (mod ^ Event
.SHIFT_MASK
) || 0 == (mod ^ Utils
.getControlModifier())) {
1720 // If it's the last step and the last action was not Z_KEY undo action, then store current:
1721 Bureaucrat
.createAndStart(new Worker
.Task("Undo") { public void exec() {
1722 if (isTransforming()) display
.getSelection().undoOneStep();
1723 else display
.getLayerSet().undoOneStep();
1724 Display
.repaint(display
.getLayerSet());
1725 }}, display
.getProject());
1727 // REDO: alt+z or ctrl+shift+z
1728 } else if (0 == (mod ^ Event
.ALT_MASK
) || 0 == (mod ^
(Event
.SHIFT_MASK
| Utils
.getControlModifier())) ) {
1729 Bureaucrat
.createAndStart(new Worker
.Task("Redo") { public void exec() {
1730 if (isTransforming()) display
.getSelection().redoOneStep();
1731 else display
.getLayerSet().redoOneStep();
1732 Display
.repaint(display
.getLayerSet());
1733 }}, display
.getProject());
1736 // else, the 'z' command restores the image using ImageJ internal undo
1739 if (null != active
&& 0 == ke
.getModifiers() && !isTransforming()) {
1740 setTransforming(true);
1743 // else, let ImageJ grab the ROI into the Manager, if any
1746 if (0 == (ke
.getModifiers() ^ Utils
.getControlModifier())) {
1747 display
.getSelection().selectAll();
1748 Display
.repaint(display
.getLayer(), display
.getSelection().getBox(), 0);
1750 break; // INSIDE the 'if' block, so that it can bleed to the default block which forwards to active!
1751 } else if (null != active
) {
1752 active
.keyPressed(ke
);
1753 if (ke
.isConsumed()) break;
1754 // TODO this is just a hack really. Should just fall back to default switch option.
1755 // The whole keyPressed method needs revision: should not break from it when not using the key.
1757 case KeyEvent
.VK_ESCAPE
: // cancel transformation
1758 if (display
.getLayer().getParent().isAligning()) {
1759 display
.getLayer().getParent().cancelAlign();
1761 } else if (null != active
) {
1762 if (display
.getSelection().isTransforming()) cancelTransform();
1764 display
.select(null); // deselect
1765 // repaint out the brush if present
1766 if (ProjectToolbar
.PEN
== ProjectToolbar
.getToolId()) {
1767 repaint(old_brush_box
, 0);
1773 case KeyEvent
.VK_SPACE
:
1774 if (0 == ke
.getModifiers()) {
1775 if (null != active
) {
1776 invalidateVolatile();
1777 if (Math
.abs(active
.getAlpha() - 0.5f
) > 0.001f
) active
.setAlpha(0.5f
);
1778 else active
.setAlpha(1.0f
);
1779 display
.setTransparencySlider(active
.getAlpha());
1784 int kem
= ke
.getModifiers();
1785 if (0 != (kem
& KeyEvent
.SHIFT_MASK
)
1786 && 0 != (kem
& KeyEvent
.ALT_MASK
)
1787 && 0 != (kem
& KeyEvent
.CTRL_MASK
)) {
1788 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.");
1794 if (ke
.isAltDown()) {
1797 } else if (dragging
) {
1798 // 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)
1806 if (ke
.isAltDown()) {
1807 if (ke
.isShiftDown()) display
.importImage();
1808 else display
.importNextImage();
1812 case KeyEvent
.VK_PAGE_UP
: // as in Inkscape
1813 if (null != active
) {
1814 update_graphics
= true;
1815 layer
.getParent().move(LayerSet
.UP
, active
);
1816 Display
.repaint(layer
, active
, 5);
1817 Display
.updatePanelIndex(layer
, active
);
1821 case KeyEvent
.VK_PAGE_DOWN
: // as in Inkscape
1822 if (null != active
) {
1823 update_graphics
= true;
1824 layer
.getParent().move(LayerSet
.DOWN
, active
);
1825 Display
.repaint(layer
, active
, 5);
1826 Display
.updatePanelIndex(layer
, active
);
1830 case KeyEvent
.VK_HOME
: // as in Inkscape
1831 if (null != active
) {
1832 update_graphics
= true;
1833 layer
.getParent().move(LayerSet
.TOP
, active
);
1834 Display
.repaint(layer
, active
, 5);
1835 Display
.updatePanelIndex(layer
, active
);
1839 case KeyEvent
.VK_END
: // as in Inkscape
1840 if (null != active
) {
1841 update_graphics
= true;
1842 layer
.getParent().move(LayerSet
.BOTTOM
, active
);
1843 Display
.repaint(layer
, active
, 5);
1844 Display
.updatePanelIndex(layer
, active
);
1849 if (0 == ke
.getModifiers()) {
1850 if (null == active
|| active
.getClass() == Patch
.class) {
1851 // paste a new image
1852 ImagePlus clipboard
= ImagePlus
.getClipboard();
1853 if (null != clipboard
) {
1854 ImagePlus imp
= new ImagePlus(clipboard
.getTitle() + "_" + System
.currentTimeMillis(), clipboard
.getProcessor().crop());
1855 Object info
= clipboard
.getProperty("Info");
1856 if (null != info
) imp
.setProperty("Info", (String
)info
);
1857 double x
= srcRect
.x
+ srcRect
.width
/2 - imp
.getWidth()/2;
1858 double y
= srcRect
.y
+ srcRect
.height
/2 - imp
.getHeight()/2;
1859 // save the image somewhere:
1860 Patch pa
= display
.getProject().getLoader().addNewImage(imp
, x
, y
);
1861 display
.getLayer().add(pa
);
1863 } // TODO there isn't much ImageJ integration in the pasting. Can't paste to a selected image, for example.
1865 // Each type may know how to paste data from the copy buffer into itself:
1866 active
.keyPressed(ke
);
1872 if (null != active
) {
1873 active
.keyPressed(ke
);
1877 if (0 == ke
.getModifiers()) {
1878 final Project pro
= display
.getProject();
1879 if ("true".equals(pro
.getProperty("no_color_cues"))) {
1881 pro
.setProperty("no_color_cues", null);
1883 pro
.setProperty("no_color_cues", "true");
1885 Display
.repaint(display
.getLayer().getParent());
1889 case KeyEvent
.VK_DELETE
:
1890 if (0 == ke
.getModifiers()) {
1891 display
.getSelection().deleteAll();
1895 if (0 == ke
.getModifiers() && null != active
&& active
.getClass() == Profile
.class) {
1896 display
.duplicateLinkAndSendTo(active
, 0, active
.getLayer().getParent().previous(layer
));
1901 if (0 == ke
.getModifiers() && null != active
&& active
.getClass() == Profile
.class) {
1902 display
.duplicateLinkAndSendTo(active
, 1, active
.getLayer().getParent().next(layer
));
1906 case KeyEvent
.VK_F1
:
1907 case KeyEvent
.VK_F2
:
1908 case KeyEvent
.VK_F3
:
1909 case KeyEvent
.VK_F4
:
1910 case KeyEvent
.VK_F5
:
1911 case KeyEvent
.VK_F6
:
1912 case KeyEvent
.VK_F7
:
1913 case KeyEvent
.VK_F8
:
1914 case KeyEvent
.VK_F9
:
1915 case KeyEvent
.VK_F10
:
1916 case KeyEvent
.VK_F11
:
1917 case KeyEvent
.VK_F12
:
1918 ProjectToolbar
.keyPressed(ke
);
1920 case KeyEvent
.VK_UP
:
1921 case KeyEvent
.VK_DOWN
:
1922 case KeyEvent
.VK_LEFT
:
1923 case KeyEvent
.VK_RIGHT
:
1926 // forward event to active
1927 if (null != active
) {
1928 active
.keyPressed(ke
);
1929 if (ke
.isConsumed()) {
1930 Selection selection
= display
.getSelection();
1931 repaint(selection
.getLinkedBox(), Selection
.PADDING
+ 2); // optimization
1936 if ( !(keyCode
== KeyEvent
.VK_UNDEFINED
|| keyChar
== KeyEvent
.CHAR_UNDEFINED
) && !ke
.isConsumed() && null != active
&& active
instanceof Patch
) {
1937 // forward to ImageJ for a final try
1938 IJ
.getInstance().keyPressed(ke
);
1942 //Utils.log2("keyCode, keyChar: " + keyCode + ", " + keyChar + " ref: " + KeyEvent.VK_UNDEFINED + ", " + KeyEvent.CHAR_UNDEFINED);
1945 public void keyTyped(KeyEvent ke
) {}
1947 public void keyReleased(KeyEvent ke
) {}
1949 public void zoomToFit() {
1950 double magw
= (double) getWidth() / imageWidth
;
1951 double magh
= (double) getHeight() / imageHeight
;
1952 this.magnification
= magw
< magh ? magw
: magh
;
1953 this.srcRect
.setRect(0, 0, imageWidth
, imageHeight
);
1954 setMagnification(magnification
);
1955 display
.updateInDatabase("srcRect"); // includes magnification
1959 public void setReceivesInput(boolean b
) {
1960 this.input_disabled
= !b
;
1963 public boolean isInputEnabled() {
1964 return !input_disabled
;
1967 public void exportXML(StringBuffer sb_body
, String indent
, Object any
) {
1968 sb_body
.append("<canvas magnification=\"").append(magnification
).append("\" srcrect_x=\"").append(srcRect
.x
).append("\" srcrect_y=\"").append(srcRect
.y
).append("\" srcrect_width=\"").append(srcRect
.width
).append("\" srcrect_height=\"").append(srcRect
.height
).append("\">\n");
1971 /** 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.*/
1972 public ImagePlus
getFakeImagePlus() {
1976 /** Key/Mouse bindings like:
1977 * - ij.gui.StackWindow: wheel to scroll slices (in this case Layers)
1978 * - Inkscape: control+wheel to zoom (apple+wheel in macosx, since control+wheel zooms desktop)
1980 public void mouseWheelMoved(MouseWheelEvent mwe
) {
1981 if (dragging
) return; // prevent unexpected mouse wheel movements
1982 final int modifiers
= mwe
.getModifiers();
1983 final int rotation
= mwe
.getWheelRotation();
1984 if (0 == (modifiers ^ Utils
.getControlModifier())) {
1985 // scroll zooom under pointer
1988 if (x
< 0 || y
< 0 || x
>= getWidth() || y
>= getHeight()) {
1997 } else if (0 == (modifiers ^ InputEvent
.SHIFT_MASK
) && ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
) {
1998 int brushSize_old
= ProjectToolbar
.getBrushSize();
1999 // resize brush for AreaList painting
2000 final int sign
= rotation
> 0 ?
1 : -1;
2001 int brushSize
= ProjectToolbar
.setBrushSize((int)(5 * sign
/ magnification
)); // the getWheelRotation provides the sign
2002 if (brushSize_old
> brushSize
) brushSize
= brushSize_old
; // for repainting purposes alnne
2003 int extra
= (int)(5 / magnification
);
2004 if (extra
< 2) extra
= 2;
2006 Rectangle r
= new Rectangle((int)(mwe
.getX() / magnification
) + srcRect
.x
- brushSize
/2 - extra
, (int)(mwe
.getY() / magnification
) + srcRect
.y
- brushSize
/2 - extra
, brushSize
+extra
, brushSize
+extra
);
2008 } else if (0 == modifiers
) {
2010 if (rotation
> 0) display
.nextLayer(modifiers
);
2011 else display
.previousLayer(modifiers
);
2015 protected class RepaintProperties
implements AbstractOffscreenThread
.RepaintProperties
{
2016 final private Layer layer
;
2017 final private int g_width
;
2018 final private int g_height
;
2019 final private Displayable active
;
2020 final private int c_alphas
;
2021 final private Rectangle clipRect
;
2022 final private int mode
;
2023 final private HashMap
<Color
,Layer
> hm
;
2024 final private ArrayList
<LayerPanel
> blending_list
;
2026 RepaintProperties(final Rectangle clipRect
, final Layer layer
, final int g_width
, final int g_height
, final Displayable active
, final int c_alphas
) {
2027 this.clipRect
= clipRect
;
2029 this.g_width
= g_width
;
2030 this.g_height
= g_height
;
2031 this.active
= active
;
2032 this.c_alphas
= c_alphas
;
2034 // query the display for repainting mode
2035 this.hm
= new HashMap
<Color
,Layer
>();
2036 this.blending_list
= new ArrayList
<LayerPanel
>();
2037 this.mode
= display
.getPaintMode(hm
, blending_list
);
2041 private final class OffscreenThread
extends AbstractOffscreenThread
{
2044 super("T2-Canvas-Offscreen");
2047 public void paint() {
2052 final Displayable active
;
2054 final Rectangle clipRect
;
2055 final Loader loader
;
2056 final HashMap
<Color
,Layer
> hm
;
2057 final ArrayList
<LayerPanel
> blending_list
;
2060 synchronized (this) {
2061 final DisplayCanvas
.RepaintProperties rp
= (DisplayCanvas
.RepaintProperties
) this.rp
;
2063 g_width
= rp
.g_width
;
2064 g_height
= rp
.g_height
;
2066 c_alphas
= rp
.c_alphas
;
2067 clipRect
= rp
.clipRect
;
2068 loader
= layer
.getProject().getLoader();
2071 blending_list
= rp
.blending_list
;
2074 // flag Loader to do massive flushing if needed
2075 loader
.setMassiveMode(true);
2077 // ALMOST, but not always perfect //if (null != clipRect) g.setClip(clipRect);
2079 // prepare the canvas for the srcRect and magnification
2080 final AffineTransform atc
= new AffineTransform();
2081 atc
.scale(magnification
, magnification
);
2082 atc
.translate(-srcRect
.x
, -srcRect
.y
);
2084 // Area to which each Patch will subtract from
2085 //final Area background = new Area(new Rectangle(0, 0, g_width, g_height));
2086 // bring the area to Layer space
2087 //background.transform(atc.createInverse());
2089 // the non-srcRect areas, in offscreen coords
2090 final Rectangle r1
= new Rectangle(srcRect
.x
+ srcRect
.width
, srcRect
.y
, (int)(g_width
/ magnification
) - srcRect
.width
, (int)(g_height
/ magnification
));
2091 final Rectangle r2
= new Rectangle(srcRect
.x
, srcRect
.y
+ srcRect
.height
, srcRect
.width
, (int)(g_height
/ magnification
) - srcRect
.height
);
2094 final ArrayList
<Displayable
> al_top
= new ArrayList
<Displayable
>();
2095 boolean top
= false;
2097 final ArrayList
<Displayable
> al_paint
= new ArrayList
<Displayable
>();
2099 final ArrayList
<Patch
> al_patches
= new ArrayList
<Patch
>();
2102 //final ArrayList al = layer.getDisplayables();
2103 layer
.getParent().checkBuckets();
2104 layer
.checkBuckets();
2105 final Iterator
<Displayable
> ital
= layer
.find(srcRect
, true).iterator();
2106 final Collection
<Displayable
> al_zdispl
= layer
.getParent().findZDisplayables(layer
, srcRect
, true);
2107 final Iterator
<Displayable
> itzd
= al_zdispl
.iterator();
2109 // Assumes the Layer has its objects in order:
2111 // 2 - Profiles, Balls
2112 // 3 - Pipes and ZDisplayables (from the parent LayerSet)
2115 Displayable tmp
= null;
2117 while (ital
.hasNext()) {
2118 final Displayable d
= ital
.next();
2119 final Class c
= d
.getClass();
2120 if (DLabel
.class == c
|| LayerSet
.class == c
) {
2121 tmp
= d
; // since ital.next() has moved forward already
2124 if (Patch
.class == c
) {
2126 al_patches
.add((Patch
)d
);
2128 if (!top
&& d
== active
) top
= true; // no Patch on al_top ever
2129 if (top
) al_top
.add(d
);
2130 else al_paint
.add(d
);
2134 // preload concurrently as many as possible
2135 Loader
.preload(al_patches
, magnification
, false); // must be false; a 'true' would incur in an infinite loop.
2137 // paint the ZDisplayables here, before the labels and LayerSets, if any
2138 while (itzd
.hasNext()) {
2139 final Displayable zd
= itzd
.next();
2140 if (zd
== active
) top
= true;
2141 if (top
) al_top
.add(zd
);
2142 else al_paint
.add(zd
);
2144 // paint LayerSet and DLabel objects!
2146 if (tmp
== active
) top
= true;
2147 if (top
) al_top
.add(tmp
);
2148 else al_paint
.add(tmp
);
2150 while (ital
.hasNext()) {
2151 final Displayable d
= ital
.next();
2152 if (d
== active
) top
= true;
2153 if (top
) al_top
.add(d
);
2154 else al_paint
.add(d
);
2157 // create new graphics
2158 layer
.getProject().getLoader().releaseToFit(g_width
* g_height
* 4 + 1024);
2159 final BufferedImage target
= getGraphicsConfiguration().createCompatibleImage(g_width
, g_height
, Transparency
.TRANSLUCENT
); // creates a BufferedImage.TYPE_INT_ARGB image in my T60p ATI FireGL laptop
2160 final Graphics2D g
= (Graphics2D
)target
.getGraphics();
2162 g
.setTransform(atc
); //at_original);
2164 //setRenderingHints(g);
2165 // always a stroke of 1.0, regardless of magnification; the stroke below corrects for that
2166 g
.setStroke(stroke
);
2170 // Testing: removed Area.subtract, now need to fill int background
2171 g
.setColor(Color
.black
);
2172 g
.fillRect(0, 0, g_width
- r1
.x
, g_height
- r2
.y
);
2177 // 2 - images and anything else not on al_top
2178 // 3 - non-srcRect areas
2180 //Utils.log2("offscreen painting: " + al_paint.size());
2183 // Determine painting mode
2184 if (Display
.REPAINT_SINGLE_LAYER
== mode
) {
2185 // Direct painting mode, with prePaint abilities
2186 for (final Displayable d
: al_paint
) {
2187 d
.prePaint(g
, magnification
, d
== active
, c_alphas
, layer
);
2189 } else if (Display
.REPAINT_MULTI_LAYER
== mode
) {
2190 // paint first the current layer Patches only (to set the background)
2192 for (final Displayable d
: al_patches
) {
2193 d
.paint(g
, magnification
, d
== active
, c_alphas
, layer
);
2196 // then blend on top the Patches of the others, in reverse Z order and using the alpha of the LayerPanel
2197 final Composite original
= g
.getComposite();
2199 g
.setTransform(new AffineTransform());
2200 for (final ListIterator
<LayerPanel
> it
= blending_list
.listIterator(blending_list
.size()); it
.hasPrevious(); ) {
2201 final LayerPanel lp
= it
.previous();
2202 if (lp
.layer
== layer
) continue;
2203 layer
.getProject().getLoader().releaseToFit(g_width
* g_height
* 4 + 1024);
2204 final BufferedImage bi
= getGraphicsConfiguration().createCompatibleImage(g_width
, g_height
, Transparency
.TRANSLUCENT
);
2205 final Graphics2D gb
= bi
.createGraphics();
2206 gb
.setTransform(atc
);
2207 for (final Displayable d
: lp
.layer
.find(srcRect
, true)) {
2208 if (d
.getClass() != Patch
.class) continue; // skip non-images
2209 d
.paint(gb
, magnification
, false, c_alphas
, lp
.layer
);
2211 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, lp
.getAlpha()));
2212 g
.drawImage(bi
, 0, 0, null);
2216 g
.setComposite(original
);
2217 g
.setTransform(atc
);
2219 // then paint the non-Patch objects of the current layer
2220 for (final Displayable d
: al_paint
.subList(al_patches
.size(), al_paint
.size())) {
2221 d
.paint(g
, magnification
, d
== active
, c_alphas
, layer
);
2223 } else { // Display.REPAINT_RGB_LAYER == mode
2224 final HashMap
<Color
,byte[]> channels
= new HashMap
<Color
,byte[]>();
2225 hm
.put(Color
.green
, layer
);
2226 for (final Map
.Entry
<Color
,Layer
> e
: hm
.entrySet()) {
2227 final BufferedImage bi
= new BufferedImage(g_width
, g_height
, BufferedImage
.TYPE_BYTE_GRAY
); //INDEXED, Loader.GRAY_LUT);
2228 final Graphics2D gb
= bi
.createGraphics();
2229 gb
.setTransform(atc
);
2230 final Layer la
= e
.getValue();
2231 if (la
== layer
&& Color
.green
!= e
.getKey()) continue; // don't paint current layer in two channels
2232 for (final Displayable d
: la
.find(srcRect
, true)) {
2233 if (d
.getClass() != Patch
.class) continue; // skip non-images
2234 d
.paint(gb
, magnification
, false, c_alphas
, la
);
2236 channels
.put(e
.getKey(), (byte[])new ByteProcessor(bi
).getPixels());
2238 final byte[] red
, green
, blue
;
2239 green
= channels
.get(Color
.green
);
2240 if (null == channels
.get(Color
.red
)) red
= new byte[green
.length
];
2241 else red
= channels
.get(Color
.red
);
2242 if (null == channels
.get(Color
.blue
)) blue
= new byte[green
.length
];
2243 else blue
= channels
.get(Color
.blue
);
2244 final int[] pix
= new int[green
.length
];
2245 for (int i
=0; i
<green
.length
; i
++) {
2246 pix
[i
] = ((red
[i
] & 0xff) << 16) + ((green
[i
] & 0xff) << 8) + (blue
[i
] & 0xff);
2248 // undo transform, is intended for Displayable objects
2249 g
.setTransform(new AffineTransform());
2250 final Image img
= new ColorProcessor(g_width
, g_height
, pix
).createImage();
2251 g
.drawImage(img
, 0, 0, null);
2254 g
.setTransform(atc
);
2256 // then paint the non-Patch objects of the current layer
2257 for (final Displayable d
: al_paint
.subList(al_patches
.size(), al_paint
.size())) {
2258 d
.paint(g
, magnification
, d
== active
, c_alphas
, layer
);
2262 // finally, paint non-srcRect areas
2263 if (r1
.width
> 0 || r1
.height
> 0 || r2
.width
> 0 || r2
.height
> 0) {
2264 g
.setColor(Color
.gray
);
2271 synchronized (offscreen_lock
) {
2272 offscreen_lock
.lock();
2275 update_graphics
= false;
2276 loader
.setMassiveMode(false);
2277 if (null != offscreen
) offscreen
.flush();
2279 invalidateVolatile();
2280 DisplayCanvas
.this.al_top
= al_top
;
2282 } catch (Exception e
) {
2283 e
.printStackTrace();
2286 offscreen_lock
.unlock();
2289 // Send repaint event, without offscreen graphics
2290 RT
.paint(clipRect
, false);
2292 } catch (OutOfMemoryError oome
) {
2293 // so OutOfMemoryError won't generate locks
2294 IJError
.print(oome
);
2295 } catch (Exception e
) {
2301 // added here to prevent flickering, but doesn't help. All it does is avoid a call to imp.redraw()
2302 protected void scroll(int sx
, int sy
) {
2303 int ox
= xSrcStart
+ (int)(sx
/magnification
); //convert to offscreen coordinates
2304 int oy
= ySrcStart
+ (int)(sy
/magnification
);
2305 int newx
= xSrcStart
+ (xMouseStart
-ox
);
2306 int newy
= ySrcStart
+ (yMouseStart
-oy
);
2307 if (newx
<0) newx
= 0;
2308 if (newy
<0) newy
= 0;
2309 if ((newx
+srcRect
.width
)>imageWidth
) newx
= imageWidth
-srcRect
.width
;
2310 if ((newy
+srcRect
.height
)>imageHeight
) newy
= imageHeight
-srcRect
.height
;
2315 private void handleHide(final KeyEvent ke
) {
2316 if (ke
.isAltDown() && !ke
.isShiftDown()) {
2318 display
.getLayer().getParent().setAllVisible(false);
2319 //Display.repaint(display.getLayer());
2320 Display
.update(display
.getLayer());
2324 if (ke
.isShiftDown()) {
2326 display
.hideDeselected(ke
.isAltDown());
2330 // else, hide selected
2331 display
.getSelection().setVisible(false);
2332 Display
.update(display
.getLayer());
2336 private class ScreenImage
{
2338 Rectangle srcRect
= null;
2341 final boolean equals(final long layer_id
, final Rectangle srcRect
, final double mag
) {
2342 return layer_id
== this.layer_id
&& mag
== this.mag
&& srcRect
.equals(this.srcRect
);
2344 /** Flushes the old awt if any. */
2345 final void set(final Image awt
, final long layer_id
, final Rectangle srcRect
, final double mag
) {
2346 this.layer_id
= layer_id
;
2347 this.srcRect
= (Rectangle
)srcRect
.clone();
2348 if (null != awt
&& awt
!= this.awt
) this.awt
.flush();
2352 final void flush() {
2354 this.srcRect
= null;
2355 if (null != this.awt
) this.awt
.flush();
2359 final boolean isFlushed() { return -1 == layer_id
; }
2362 /*** Stores and manages a listing of max 10 recently made offscreen images. The images are actually stored in the loader's cache; this class simply assigns the layer_id to each. */
2363 private class ShallowCache
{
2364 final ScreenImage
[] sim
= new ScreenImage
[75];
2366 void add(final Image awt
, final Layer layer
, final Rectangle srcRect
, final double mag
) {
2367 final long layer_id
= layer
.getId();
2368 // Only one awt per layer_id
2370 for (;i
<sim
.length
; i
++) {
2371 if (null == sim
[i
]) { sim
[i
] = new ScreenImage(); break; }
2372 if (sim
[i
].isFlushed()) break;
2373 if (sim
[i
].layer_id
== layer_id
) {
2374 sim
[i
].set(awt
, layer_id
, srcRect
, mag
);
2378 // ok so set the given image at 'i'
2380 if (sim
.length
== i
) {
2381 // no space, pop oldest
2382 // So now oldest is next to oldest;
2384 if (oldest
== sim
.length
) k
= oldest
= 0;
2387 sim
[k
].set(awt
, layer_id
, srcRect
, mag
);
2388 layer
.getProject().getLoader().cacheOffscreen(layer
, awt
);
2390 Image
get(final Layer layer
, final Rectangle srcRect
, final double mag
) {
2391 final long layer_id
= layer
.getId();
2392 for (int i
=0; i
<sim
.length
; i
++) {
2393 if (null == sim
[i
]) return null;
2394 if (sim
[i
].equals(layer_id
, srcRect
, mag
)) {
2395 Image awt
= layer
.getProject().getLoader().getCached(layer_id
, 0);
2396 if (null == awt
) sim
[i
].flush(); // got lost
2402 void flush(Layer layer
) {
2403 final long layer_id
= layer
.getId();
2404 for (int i
=0; i
<sim
.length
; i
++) {
2405 if (sim
[i
].layer_id
== layer_id
) {
2406 layer
.getProject().getLoader().decacheAWT(layer_id
);
2408 break; // only one per layer_id