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 if (null != active
&& active
.getClass() != Patch
.class && !active
.isOutOfRepaintingClip(magnification
, srcRect
, clipRect
)) active
.paint(g
, magnification
, true, c_alphas
, active_layer
);
195 final Rectangle tmp
= null != clipRect ?
new Rectangle() : null;
196 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;
197 for (int i
=0; i
<top
.length
; i
++) {
198 if (null != clipRect
&& !top
[i
].getBoundingBox(tmp
).intersects(clip
)) continue;
199 top
[i
].paint(g
, magnification
, false, c_alphas
, active_layer
);
202 //Utils.log2("painted new volatile with active " + active);
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 if (null != offscreen
) {
367 //g.drawImage(offscreen, 0, 0, null);
368 if (dragging
) invalidateVolatile(); // to update the active at least
369 render(g
, active
, di
, active_layer
, c_alphas
, at_original
, clipRect
);
372 g2d
.setTransform(at_original
);
374 } catch (Exception e
) {
377 offscreen_lock
.unlock();
380 g2d
.setStroke(this.stroke
);
382 // paint a pink frame around selected objects, and a white frame around the active object
383 final Selection selection
= display
.getSelection();
384 if (null != selection
&& ProjectToolbar
.getToolId() < ProjectToolbar
.PENCIL
) { // i.e. PENCIL, PEN and ALIGN
385 selection
.paint(g2d
, srcRect
, magnification
);
389 //if (null != display.getLayer().root) display.getLayer().root.paint(g2d, srcRect, magnification, Color.red);
390 //if (null != display.getLayer().getParent().root) display.getLayer().getParent().root.paint(g2d, srcRect, magnification, Color.blue);
394 g2d
.setTransform(new AffineTransform());
395 // reset to 1.0 thickness
396 g2d
.setStroke(new BasicStroke(1.0f
, BasicStroke
.CAP_BUTT
, BasicStroke
.JOIN_MITER
));
398 final Align align
= null != active_layer ? active_layer
.getParent().getAlign() : null;
400 align
.paint(active_layer
, g2d
, srcRect
, magnification
);
403 // paint brush outline for AreaList
404 if (mouse_in
&& null != active
&& ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
&& active
.getClass() == AreaList
.class) {
405 int brushSize
= ProjectToolbar
.getBrushSize();
406 g
.setColor(active
.getColor());
407 g
.drawOval((int)((xMouse
-srcRect
.x
-brushSize
/2)*magnification
), (int)((yMouse
- srcRect
.y
-brushSize
/2)*magnification
), (int)(brushSize
* magnification
), (int)(brushSize
* magnification
));
410 final Roi roi
= imp
.getRoi();
416 if (null != freehandProfile
) {
417 freehandProfile
.paint(g
, magnification
, srcRect
, true);
419 noCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(new BufferedImage(1,1,BufferedImage
.TYPE_BYTE_BINARY
), new Point(0,0), "noCursor");
422 final long now
= System
.currentTimeMillis();
423 //p("interval: " + (now - last_paint));
426 } catch (Exception e
) {
427 Utils
.log2("DisplayCanvas.paint(Graphics) Error: " + e
);
430 synchronized (lock_paint
) {
436 public void waitForRepaint() {
437 // wait for all offscreen methods to finish painting
439 // wait for the paint method to finish painting
440 synchronized (lock_paint
) {
441 // wait until painting is done
445 /** Paints a handle on the screen coords. Adapted from ij.gui.Roi class. */
446 static public void drawHandle(final Graphics g
, final int x
, final int y
, final double magnification
) {
447 final int width5
= (int)Math
.round(5 / magnification
);
448 final int width3
= (int)Math
.round(3 / magnification
);
449 final int corr2
= (int)Math
.round(2 / magnification
);
450 final int corr1
= (int)Math
.ceil(1 / magnification
);
451 g
.setColor(Color
.white
);
452 g
.drawRect(x
- corr2
, y
- corr2
, width5
, width5
);
453 g
.setColor(Color
.black
);
454 g
.drawRect(x
- corr1
, y
- corr1
, width3
, width3
);
455 g
.setColor(Color
.white
);
456 g
.fillRect(x
, y
, corr1
, corr1
);
459 /** Paints a handle on the offscreen x,y. Adapted from ij.gui.Roi class. */
460 private void drawHandle(Graphics g
, double x
, double y
) {
461 g
.setColor(Color
.black
);
462 g
.fillRect((int) ((x
- srcRect
.x
) * magnification
) - 1, (int) ((y
- srcRect
.y
) * magnification
) - 1, 3, 3);
463 g
.setColor(Color
.white
);
464 g
.drawRect((int) ((x
- srcRect
.x
) * magnification
) - 2, (int) ((y
- srcRect
.y
) * magnification
) - 2, 5, 5);
467 protected void setDrawingColor(int ox
, int oy
, boolean setBackground
) {
468 super.setDrawingColor(ox
, oy
, setBackground
);
472 private int x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
;
474 private boolean popup
= false;
476 private boolean locked
= false; // TODO temporary!
478 private int tmp_tool
= -1;
480 public void mousePressed(MouseEvent me
) {
482 this.flags
= me
.getModifiers();
484 x_p
= x_d
= srcRect
.x
+ (int) (me
.getX() / magnification
); // offScreenX(me.getX());
485 y_p
= y_d
= srcRect
.y
+ (int) (me
.getY() / magnification
); // offScreenY(me.getY());
490 // ban if beyond bounds:
491 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
496 popup
= false; // not reset properly in macosx
497 if (Utils
.isPopupTrigger(me
)) {
499 display
.getPopupMenu().show(this, me
.getX(), me
.getY());
506 int tool
= ProjectToolbar
.getToolId();
508 // pan with middle mouse like in inkscape
509 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
510 if (0 != (flags & InputEvent.BUTTON2_MASK))
512 if (me
.getButton() == MouseEvent
.BUTTON2
) {
514 ProjectToolbar
.setTool(Toolbar
.HAND
);
517 //Utils.log2("button: " + me.getButton() + " BUTTON2: " + MouseEvent.BUTTON2);
520 case Toolbar
.MAGNIFIER
:
521 if (me
.isAltDown()) zoomOut(me
.getX(), me
.getY());
522 else zoomIn(me
.getX(), me
.getY());
525 super.setupScroll(x_p
, y_p
); // offscreen coords.
526 //display.repaintAll();
530 if (input_disabled
) {
531 input_disabled2
= true;
532 Utils
.showMessage("Please wait while completing the task.\nOnly the glass and hand tool are enabled.");
533 return; // only zoom and pan are allowed
536 Displayable active
= display
.getActive();
541 if (null != active
&& active
instanceof Patch
) {
542 me
.translatePoint(-(int) active
.getX(), -(int) active
.getY());
543 super.mousePressed(me
);
545 // TODO should use LayerStack virtualization ... then scale back the ROI
548 case ProjectToolbar
.PENCIL
:
549 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
550 Profile prof
= (Profile
) active
;
551 this.freehandProfile
= new FreeHandProfile(prof
);
552 freehandProfile
.mousePressed(x_p
, y_p
);
556 case Toolbar
.RECTANGLE
:
558 case Toolbar
.POLYGON
:
559 case Toolbar
.FREEROI
:
561 case Toolbar
.POLYLINE
:
562 case Toolbar
.FREELINE
:
565 // pass the mouse event to superclass ImageCanvas.
566 super.mousePressed(me
);
569 case Toolbar
.DROPPER
:
571 setDrawingColor(x_p
, y_p
, me
.isAltDown());
576 if (display
.isReadOnly()) return;
580 // edit a label, or add a new one
581 if (null == active
|| !active
.contains(x_p
, y_p
)) {
582 // find a Displayable to activate, if any
583 display
.choose(me
.getX(), me
.getY(), x_p
, y_p
, DLabel
.class);
584 active
= display
.getActive();
586 if (null != active
&& active
.isVisible() && active
instanceof DLabel
) {
588 ((DLabel
) active
).edit();
591 DLabel label
= new DLabel(display
.getProject(), " ", x_p
, y_p
);
592 display
.getLayer().add(label
);
598 // SPECIFIC for SELECT and above tools
600 // no ROIs allowed past this point
601 if (tool
>= ProjectToolbar
.SELECT
) imp
.killRoi();
604 Selection selection
= display
.getSelection();
605 if (selection
.isTransforming()) {
606 box
= selection
.getLinkedBox();
607 selection
.mousePressed(me
, x_p
, y_p
, magnification
);
610 // select or deselect another active Displayable, or add it to the selection group:
611 if (ProjectToolbar
.SELECT
== tool
) {
612 display
.choose(me
.getX(), me
.getY(), x_p
, y_p
, me
.isShiftDown(), null);
614 active
= display
.getActive();
615 selection
= display
.getSelection();
617 if (ProjectToolbar
.ALIGN
== tool
) {
618 LayerSet set
= display
.getLayer().getParent();
619 if (!set
.isAligning()) {
620 set
.startAlign(display
);
622 set
.getAlign().mousePressed(display
.getLayer(), me
, x_p
, y_p
, magnification
);
626 if (null == active
|| !active
.isVisible()) return;
629 case ProjectToolbar
.SELECT
:
630 // check if the active is usable:
631 // check if the selection contains locked objects
632 if (selection
.isLocked()) {
636 if (selection
.isEmpty()) {
640 // gather initial box (for repainting purposes)
641 box
= selection
.getLinkedBox();
642 selection
.mousePressed(me
, x_p
, y_p
, magnification
);
644 default: // the PEN and PENCIL tools, and any other custom tool
645 display
.getLayerSet().addPreDataEditStep(active
);
646 box
= active
.getBoundingBox();
647 active
.mousePressed(me
, x_p
, y_p
, magnification
);
648 invalidateVolatile();
651 //Utils.log("locked: " + locked + " popup: " + popup + " input_disabled2: " + input_disabled2);
654 public void mouseDragged(MouseEvent me
) {
655 // ban if beyond bounds:
656 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
660 Selection selection
= display
.getSelection();
661 if (locked
&& !selection
.isEmpty()) {
662 Utils
.log("Selection is locked.");
670 this.flags
= me
.getModifiers();
675 x_d
= srcRect
.x
+ (int) (me
.getX() / magnification
); // offscreen
676 y_d
= srcRect
.y
+ (int) (me
.getY() / magnification
);
682 int me_x
= me
.getX();
683 int me_y
= me
.getY();
684 if (me_x
< 0 || me_x
> this.getWidth() || me_y
< 0 || me_y
> this.getHeight()) {
690 int tool
= ProjectToolbar
.getToolId();
693 // pan with middle mouse like in inkscape
694 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
695 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
698 */ // so the above has been implemented as a temporary switch to the HAND tool at the mousePressed function.
701 case Toolbar
.MAGNIFIER
: // TODO : create a zooms-area tool
706 scroll(me
.getX(), me
.getY());
707 if (0 != srx
- srcRect
.x
|| 0 != sry
- srcRect
.y
) {
708 update_graphics
= true; // update the offscreen images.
709 display
.getNavigator().repaint(false);
715 if (input_disabled2
) return;
717 if (null != display
.getLayer().getParent().getAlign()) return;
721 //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));
723 // Code for Matthias' FreehandProfile (TODO this should be done on mousePressed, not on mouseDragged)
725 Displayable active
= display
.getActive();
726 if (null != active
&& active
.getClass() == Profile
.class) {
729 r
= new Robot(this.getGraphicsConfiguration().getDevice());
731 } catch (AWTException e
) {
737 case ProjectToolbar
.PENCIL
:
738 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
739 if (freehandProfile
== null)
740 return; // starting painting out of the DisplayCanvas border
741 double dx
= x_d
- x_d_old
;
742 double dy
= y_d
- y_d_old
;
743 freehandProfile
.mouseDragged(me
, x_d
, y_d
, dx
, dy
);
745 // Point screenLocation = getLocationOnScreen();
746 // mousePos[0] += screenLocation.x;
747 // mousePos[1] += screenLocation.y;
748 // r.mouseMove( mousePos[0], mousePos[1]);
752 case Toolbar
.RECTANGLE
:
754 case Toolbar
.POLYGON
:
755 case Toolbar
.FREEROI
:
757 case Toolbar
.POLYLINE
:
758 case Toolbar
.FREELINE
:
761 // pass the mouse event to superclass ImageCanvas.
762 super.mouseDragged(me
);
766 // no ROIs beyond this point
767 if (tool
>= ProjectToolbar
.SELECT
) imp
.killRoi();
771 if (display
.isReadOnly()) return;
773 if (null != active
&& active
.isVisible()) {
774 // prevent dragging beyond the layer limits
775 if (display
.getLayer().contains(x_d
, y_d
, 1)) {
778 case ProjectToolbar
.SELECT
:
779 selection
.mouseDragged(me
, x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
780 box2
= selection
.getLinkedBox();
782 // repaint all Displays (where it was and where it is now, hence the sum of both boxes):
783 //TODO//Utils.log2("md: " + box.toString());
784 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, false, active
.isLinked() || active
.getClass() == Patch
.class);
785 // box for next mouse dragged iteration
788 case ProjectToolbar
.ALIGN
:
791 active
.mouseDragged(me
, x_p
, y_p
, x_d
, y_d
, x_d_old
, y_d_old
);
792 // the line above must repaint on its own
796 locked
= true; // TODO temporary until the snapTo and mouseEntered issues are fixed
797 Utils
.log("DisplayCanvas.mouseDragged: preventing drag beyond layer limits.");
802 public void mouseReleased(MouseEvent me
) {
803 boolean dragging2
= dragging
;
810 // ban if beyond bounds:
811 if (x_p
< srcRect
.x
|| y_p
< srcRect
.y
|| x_p
> srcRect
.x
+ srcRect
.width
|| y_p
> srcRect
.y
+ srcRect
.height
) {
815 int tool
= ProjectToolbar
.getToolId();
817 // pan with middle mouse like in inkscape
818 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
819 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
825 case Toolbar
.MAGNIFIER
:
826 // display.updateInDatabase("srcRect"); // TODO if the display.frame
827 // is shrinked, the pack() in the zoom methods will also call the
828 // updateInDatabase("srcRect") (so it's going to be done twice)
829 display
.updateTitle();
832 display
.updateInDatabase("srcRect");
833 if (-1 != tmp_tool
) {
834 ProjectToolbar
.setTool(tmp_tool
);
837 if (!dragging2
) repaint(true); // TEMPORARY just to allow fixing bad screen when simply cliking with the hand
841 if (input_disabled2
) {
842 input_disabled2
= false; // reset
849 String msg
= "\nRight-click and select\"";
850 if (null != display
.getActive()) {
851 msg
+= display
.getActive().getClass() == Patch
.class ?
"Unlock" : "Unlink";
854 Utils
.showMessage("Selection is locked or contains links to a locked object." + msg
);
859 if (display
.getLayer().getParent().isAligning()) {
863 // pan with middle mouse like in inkscape
864 /* // works, but can't use it: the alt button then is useless (so no erasing with areas, and so on)
865 if (0 != (flags & InputEvent.BUTTON2_MASK)) {
870 this.flags
= me
.getModifiers();
871 flags
&= ~InputEvent
.BUTTON1_MASK
; // make sure button 1 bit is not set (FOR AreaList brush-like)
872 flags
&= ~InputEvent
.BUTTON2_MASK
; // make sure button 2 bit is not set
873 flags
&= ~InputEvent
.BUTTON3_MASK
; // make sure button 3 bit is not set
875 int x_r
= srcRect
.x
+ (int)(me
.getX() / magnification
);
876 int y_r
= srcRect
.y
+ (int)(me
.getY() / magnification
);
881 Displayable active
= display
.getActive();
884 case ProjectToolbar
.PENCIL
:
885 if (null != active
&& active
.isVisible() && active
.getClass() == Profile
.class) {
886 if (freehandProfile
== null)
887 return; // starting painting out of the DisplayCanvas boarder
888 freehandProfile
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
889 freehandProfile
= null;
891 Selection selection
= display
.getSelection();
892 selection
.updateTransform(display
.getActive());
893 Display
.repaint(display
.getLayer(), selection
.getBox(), Selection
.PADDING
); // repaints the navigator as well
897 case Toolbar
.RECTANGLE
:
899 case Toolbar
.POLYGON
:
900 case Toolbar
.FREEROI
:
902 case Toolbar
.POLYLINE
:
903 case Toolbar
.FREELINE
:
906 // pass the mouse event to superclass ImageCanvas.
907 super.mouseReleased(me
);
909 // return; // replaced by #SET_ROI
912 final Roi roi
= imp
.getRoi();
915 if (display
.isReadOnly()) return;
917 if (tool
>= ProjectToolbar
.SELECT
) {
918 if (null != roi
) imp
.killRoi();
919 } else return; // #SET_ROI
921 Selection selection
= display
.getSelection();
925 selection
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
926 box
.add(selection
.getLinkedBox());
927 Display
.repaint(display
.getLayer(), box
, Selection
.PADDING
); // repaints the navigator as well
928 StitchingTEM
.snap(active
, display
); // will repaint whatever is appropriate (the visible linked group snapped along)
934 if (null != active
&& active
.isVisible()) {
936 case ProjectToolbar
.SELECT
:
937 selection
.mouseReleased(me
, x_p
, y_p
, x_d
, y_d
, x_r
, y_r
);
938 box
.add(selection
.getLinkedBox());
939 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, !selection
.isTransforming(), active
.isLinked() || active
.getClass() == Patch
.class); // does not repaint the navigator
941 case ProjectToolbar
.PENCIL
:
942 case ProjectToolbar
.PEN
:
943 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)
944 // update active's bounding box
945 selection
.updateTransform(active
);
946 box
.add(selection
.getBox());
947 Display
.repaint(display
.getLayer(), Selection
.PADDING
, box
, !selection
.isTransforming(), active
.isLinked() || active
.getClass() == Patch
.class); // does not repaint the navigator
948 //if (!active.getClass().equals(AreaList.class)) Display.repaint(display.getLayer(), box, Selection.PADDING); // repaints the navigator as well
949 // TODO: this last repaint call is unnecessary, if the box was properly repainted on mouse drag for Profile etc.
951 if (null != old_brush_box
) {
952 repaint(old_brush_box
, 0, false);
953 old_brush_box
= null; // from mouseMoved
955 // The current state:
956 display
.getLayerSet().addDataEditStep(active
);
962 // private to the package
963 boolean isDragging() {
964 if (null == display
.getSelection()) {
965 Utils
.log2("WARNING DisplayCanvas.isDragging thinks the display.getSelection() gives a null object ?!?");
968 return display
.getSelection().isDragging();
971 private boolean mouse_in
= false;
973 public void mouseEntered(MouseEvent me
) {
975 // try to catch focus if the JFrame is front most
976 if (display
.isActiveWindow() && !this.hasFocus()) {
979 // bring dragged point to mouse pointer
980 // TODO doesn't work as expected.
982 Displayable active = display.getActive();
983 int x = offScreenX(me.getX());
984 int y = offScreenY(me.getY());
985 if (null != active) {
986 active.snapTo(x, y, x_p, y_p);
987 x_p = x_d = x_d_old = x;
988 y_p = y_d = y_d_old = y;
991 //Utils.log2("mouseEntered x,y: " + offScreenX(me.getX()) + "," + offScreenY(me.getY()));
994 public void mouseExited(MouseEvent me
) {
996 // paint away the circular brush if any
997 if (ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
) {
998 Displayable active
= display
.getActive();
999 if (null != active
&& active
.isVisible() && AreaList
.class == active
.getClass()) {
1000 if (null != old_brush_box
) {
1001 this.repaint(old_brush_box
, 0);
1002 old_brush_box
= null;
1008 /** Sets the cursor based on the current tool and cursor location. */
1009 public void setCursor(int sx
, int sy
, int ox
, int oy
) {
1010 // copy of ImageCanvas.setCursor without the win==null
1013 Roi roi
= imp
.getRoi();
1015 * ImageWindow win = imp.getWindow(); if (win==null) return;
1017 if (IJ
.spaceBarDown()) {
1018 setCursor(handCursor
);
1021 switch (Toolbar
.getToolId()) {
1022 case Toolbar
.MAGNIFIER
:
1023 if (IJ
.isMacintosh())
1024 setCursor(defaultCursor
);
1026 setCursor(moveCursor
);
1029 setCursor(handCursor
);
1031 case ProjectToolbar
.SELECT
:
1032 case ProjectToolbar
.PENCIL
:
1033 case ProjectToolbar
.ALIGN
:
1034 setCursor(defaultCursor
);
1036 default: // selection tool
1038 if (roi
!= null && roi
.getState() != roi
.CONSTRUCTING
&& roi
.isHandle(sx
, sy
) >= 0)
1039 setCursor(handCursor
);
1040 else if (Prefs
.usePointerCursor
|| (roi
!= null && roi
.getState() != roi
.CONSTRUCTING
&& roi
.contains(ox
, oy
)))
1041 setCursor(defaultCursor
);
1043 setCursor(crosshairCursor
);
1047 /** Set the srcRect - used by the DisplayNavigator. */
1048 protected void setSrcRect(int x
, int y
, int width
, int height
) {
1049 this.srcRect
.setRect(x
, y
, width
, height
);
1050 display
.updateInDatabase("srcRect");
1053 public void setDrawingSize(int new_width
, int new_height
,
1054 boolean adjust_srcRect
) {
1056 if (adjust_srcRect
) {
1057 double mag
= super.getMagnification();
1058 // This method is very important! Make it fit perfectly.
1059 if (srcRect
.width
* mag
< new_width
) {
1061 if (new_width
> imageWidth
* mag
) {
1064 srcRect
.width
= imageWidth
;
1066 srcRect
.width
= (int) Math
.ceil(new_width
/ mag
);
1067 if (srcRect
.x
+ srcRect
.width
> imageWidth
) {
1068 srcRect
.x
= imageWidth
- srcRect
.width
;
1073 srcRect
.width
= (int) Math
.ceil(new_width
/ mag
);
1075 if (srcRect
.height
* mag
< new_height
) {
1077 if (new_height
> imageHeight
* mag
) {
1080 srcRect
.height
= imageHeight
;
1082 srcRect
.height
= (int) Math
.ceil(new_height
/ mag
);
1083 if (srcRect
.y
+ srcRect
.height
> imageHeight
) {
1084 srcRect
.y
= imageHeight
- srcRect
.height
;
1089 srcRect
.height
= (int) Math
.ceil(new_height
/ mag
);
1092 super.setDrawingSize(new_width
, new_height
);
1095 private void zoomIn2(int x
, int y
) {
1096 // copy of ImageCanvas.zoomIn except for the canEnlarge is different and
1097 // there's no call to the non-existing ImageWindow
1098 if (magnification
>= 32)
1100 double newMag
= getHigherZoomLevel2(magnification
);
1102 // zoom at point: correct mag drift
1103 int cx
= getWidth() / 2;
1104 int cy
= getHeight() / 2;
1105 int dx
= (int)(((x
- cx
) * magnification
) / newMag
);
1106 int dy
= (int)(((y
- cy
) * magnification
) / newMag
);
1110 // Adjust the srcRect to the new dimensions
1111 int w
= (int) Math
.round(dstWidth
/ newMag
);
1112 if (w
* newMag
< dstWidth
)
1116 int h
= (int) Math
.round(dstHeight
/ newMag
);
1117 if (h
* newMag
< dstHeight
)
1119 if (h
> imageHeight
)
1123 final Rectangle r
= new Rectangle(x
- w
/ 2, y
- h
/ 2, w
, h
);
1128 if (r
.x
+ w
> imageWidth
)
1129 r
.x
= imageWidth
- w
;
1130 if (r
.y
+ h
> imageHeight
)
1131 r
.y
= imageHeight
- h
;
1136 setMagnification(newMag
);
1137 display
.updateInDatabase("srcRect");
1138 display
.repaintAll2(); // this repaint includes this canvas's repaint as well, but also the navigator, etc. // repaint();
1141 private void zoomOut2(int x
, int y
) {
1142 //if (magnification <= 0.03125)
1144 double newMag
= getLowerZoomLevel2(magnification
);
1146 // zoom at point: correct mag drift
1147 int cx
= getWidth() / 2;
1148 int cy
= getHeight() / 2;
1149 int dx
= (int)(((x
- cx
) * magnification
) / newMag
);
1150 int dy
= (int)(((y
- cy
) * magnification
) / newMag
);
1154 if (imageWidth
* newMag
> dstWidth
|| imageHeight
* newMag
> dstHeight
) {
1155 int w
= (int) Math
.round(dstWidth
/ newMag
);
1156 if (w
* newMag
< dstWidth
)
1158 int h
= (int) Math
.round(dstHeight
/ newMag
);
1159 if (h
* newMag
< dstHeight
)
1163 Rectangle r
= new Rectangle(x
- w
/ 2, y
- h
/ 2, w
, h
);
1168 if (r
.x
+ w
> imageWidth
)
1169 r
.x
= imageWidth
- w
;
1170 if (r
.y
+ h
> imageHeight
)
1171 r
.y
= imageHeight
- h
;
1174 // Shrink srcRect, but NOT the dstWidth,dstHeight of the canvas, which remain the same:
1175 srcRect
= new Rectangle(0, 0, imageWidth
, imageHeight
);
1178 setMagnification(newMag
);
1179 display
.repaintAll2(); // this repaint includes this canvas's repaint
1180 // as well, but also the navigator, etc.
1182 display
.updateInDatabase("srcRect");
1185 /** The minimum amout of pixels allowed for width or height when zooming out. */
1186 static private final int MIN_DIMENSION
= 10; // pixels
1188 /** Enable zooming out up to the point where the display becomes 10 pixels in width or height. */
1189 protected double getLowerZoomLevel2(final double currentMag
) {
1190 // if it is 1/72 or lower, then:
1191 if (Math
.abs(currentMag
- 1/72.0) < 0.00000001 || currentMag
< 1/72.0) { // lowest zoomLevel in ImageCanvas is 1/72.0
1192 // find nearest power of two under currentMag
1193 // start at level 7, which is 1/128
1195 double scale
= currentMag
;
1196 while (scale
* srcRect
.width
> MIN_DIMENSION
&& scale
* srcRect
.height
> MIN_DIMENSION
) {
1197 scale
= 1 / Math
.pow(2, level
);
1198 // if not equal and actually smaller, break:
1199 if (Math
.abs(scale
- currentMag
) != 0.00000001 && scale
< currentMag
) break;
1204 return ImageCanvas
.getLowerZoomLevel(currentMag
);
1207 protected double getHigherZoomLevel2(final double currentMag
) {
1208 // if it is not 1/72 and its lower, then:
1209 if (Math
.abs(currentMag
- 1/72.0) > 0.00000001 && currentMag
< 1/72.0) { // lowest zoomLevel in ImageCanvas is 1/72.0
1210 // find nearest power of two above currentMag
1211 // start at level 14, which is 0.00006103515625 (0.006 %)
1212 int level
= 14; // this value may be increased in the future
1213 double scale
= currentMag
;
1214 while (level
>= 0) {
1215 scale
= 1 / Math
.pow(2, level
);
1216 if (scale
> currentMag
) break;
1221 return ImageCanvas
.getHigherZoomLevel(currentMag
);
1227 * // OBSOLETE: modified ij.gui.ImageCanvas directly
1228 public void mouseMoved(MouseEvent e) { if (IJ.getInstance()==null) return; int sx =
1229 * e.getX(); int sy = e.getY(); int ox = offScreenX(sx); int oy =
1230 * offScreenY(sy); flags = e.getModifiers(); setCursor(sx, sy, ox, oy);
1231 * IJ.setInputEvent(e); Roi roi = imp.getRoi(); if (roi!=null &&
1232 * (roi.getType()==Roi.POLYGON || roi.getType()==Roi.POLYLINE ||
1233 * roi.getType()==Roi.ANGLE) && roi.getState()==roi.CONSTRUCTING) {
1234 * PolygonRoi pRoi = (PolygonRoi)roi; pRoi.handleMouseMove(ox, oy); } else {
1235 * if (ox<imageWidth && oy<imageHeight) { //ImageWindow win =
1236 * imp.getWindow(); //if (win!=null) win.mouseMoved(ox, oy);
1237 * imp.mouseMoved(ox, oy); } else IJ.showStatus(""); } }
1240 private Rectangle old_brush_box
= null;
1242 private MouseMovedThread mouse_moved
= new MouseMovedThread();
1244 private class MouseMovedThread
extends Thread
{
1245 private MouseEvent me
= null;
1246 private boolean go
= true;
1247 MouseMovedThread() {
1248 super("T2-mouseMoved");
1250 setPriority(Thread
.NORM_PRIORITY
);
1253 void dispatch(MouseEvent me
) {
1254 //Utils.log2("before");
1255 synchronized (this) {
1263 synchronized (this) { notify(); }
1267 MouseEvent me
= null;
1268 synchronized (this) {
1269 try { this.wait(); } catch (Exception e
) {}
1273 try { mouseMoved(me
); } catch (Exception e
) { IJError
.print(e
); }
1276 private void mouseMoved(MouseEvent me
) {
1277 if (null == me
) return;
1278 if (input_disabled
|| display
.getSelection().isDragging()) return;
1280 final Displayable active
= display
.getActive();
1282 // only when no mouse buttons are down
1283 final int flags
= me
.getModifiers(); // override, the super fails for some reason
1284 if (0 == (flags
& InputEvent
.BUTTON1_MASK
)
1285 /* && 0 == (flags & InputEvent.BUTTON2_MASK) */ // this is the alt key down ..
1286 && 0 == (flags
& InputEvent
.BUTTON3_MASK
)
1287 //if (me.getButton() == MouseEvent.NOBUTTON
1288 && ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
&& null != active
&& active
.isVisible() && AreaList
.class == active
.getClass()) {
1289 // repaint area where the brush circle is
1290 int brushSize
= ProjectToolbar
.getBrushSize() +2; // +2 padding
1291 Rectangle r
= new Rectangle( xMouse
- brushSize
/2,
1292 yMouse
- brushSize
/2,
1295 Rectangle copy
= (Rectangle
)r
.clone();
1296 if (null != old_brush_box
) r
.add(old_brush_box
);
1297 old_brush_box
= copy
;
1298 repaint(r
, 1); // padding because of painting rounding which would live dirty trails
1301 if (me
.isShiftDown()) {
1302 // Print a comma-separated list of objects under the mouse pointer
1303 final Layer layer
= DisplayCanvas
.this.display
.getLayer();
1304 final int x_p
= offScreenX(me
.getX()),
1305 y_p
= offScreenY(me
.getY());
1306 final ArrayList
<Displayable
> al
= new ArrayList(layer
.getParent().findZDisplayables(layer
, x_p
, y_p
, true));
1307 final ArrayList al2
= new ArrayList(layer
.find(x_p
, y_p
, true));
1308 Collections
.reverse(al2
); // text labels first
1310 if (0 == al
.size()) {
1311 Utils
.showStatus("", false);
1314 final StringBuilder sb
= new StringBuilder();
1315 final Project pr
= layer
.getProject();
1316 for (Displayable d
: al
) sb
.append(pr
.getShortMeaningfulTitle(d
)).append(", ");
1317 sb
.setLength(sb
.length()-2);
1318 Utils
.showStatus(sb
.toString(), false);
1320 // set xMouse, yMouse, and print pixel value
1321 DisplayCanvas
.super.mouseMoved(me
);
1326 public void mouseMoved(final MouseEvent me
) {
1327 mouse_moved
.dispatch(me
);
1330 /** Zoom in using the current mouse position, or the center if the mouse is out. */
1331 public void zoomIn() {
1332 if (xMouse
< 0 || screenX(xMouse
) > dstWidth
|| yMouse
< 0 || screenY(yMouse
) > dstHeight
) {
1333 zoomIn(dstWidth
/2, dstHeight
/2);
1335 zoomIn(screenX(xMouse
), screenY(yMouse
));
1339 /** Overriding to repaint the DisplayNavigator as well. */
1340 public void zoomIn(int x
, int y
) {
1341 update_graphics
= true; // update the offscreen images.
1345 /** Zoom out using the current mouse position, or the center if the mouse is out. */
1346 public void zoomOut() {
1347 if (xMouse
< 0 || screenX(xMouse
) > dstWidth
|| yMouse
< 0 || screenY(yMouse
) > dstHeight
) {
1348 zoomOut(dstWidth
/2, dstHeight
/2);
1349 } else zoomOut(screenX(xMouse
), screenY(yMouse
));
1352 /** Overriding to repaint the DisplayNavigator as well. */
1353 public void zoomOut(int x
, int y
) {
1354 update_graphics
= true; // update the offscreen images.
1358 /** Center the srcRect around the given object(s) bounding box, zooming if necessary. */
1359 public void showCentered(Rectangle r
) {
1360 // multiply bounding box dimensions by two
1362 r
.y
-= r
.height
/ 2;
1364 r
.height
+= r
.height
;
1365 // compute target magnification
1366 double magn
= getWidth() / (double)r
.width
;
1367 // bring bounds within limits of the layer and the canvas' drawing size
1368 double lw
= display
.getLayer().getLayerWidth();
1369 double lh
= display
.getLayer().getLayerHeight();
1370 int cw
= (int) (getWidth() / magn
); // canvas dimensions in offscreen coords
1371 int ch
= (int) (getHeight() / magn
);
1374 // fit to canvas drawing size:
1375 r
.y
+= (r
.height
- ch
) / 2;
1378 // place within layer bounds
1379 if (r
.x
< 0) r
.x
= 0;
1380 if (r
.y
< 0) r
.y
= 0;
1385 if (r
.height
> lh
) {
1389 if (r
.x
+ r
.width
> lw
) r
.x
= (int)(lw
- cw
);
1390 if (r
.y
+ r
.height
> lh
) r
.y
= (int)(lh
- ch
);
1391 // compute magn again, since the desired width may have changed:
1392 magn
= getWidth() / (double)r
.width
;
1394 // set magnification and srcRect
1396 try { Thread
.sleep(200); } catch (Exception e
) {} // swing ... waiting for the display.pack()
1397 update_graphics
= true;
1398 RT
.paint(null, update_graphics
);
1399 display
.updateInDatabase("srcRect");
1400 display
.updateTitle();
1401 display
.getNavigator().repaint(false);
1404 /** 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. */
1405 public void repaint(Displayable d
) {
1410 * Repaint as much as the bounding box around the given Displayable plus the
1411 * extra padding. If the Displayable is null, the entire canvas is
1412 * repainted, remaking the offscreen images.
1414 public void repaint(Displayable displ
, int extra
) {
1415 if (null != displ
) {
1416 Rectangle r
= displ
.getBoundingBox();
1417 r
.x
= (int) ((r
.x
- srcRect
.x
) * magnification
) - extra
;
1418 r
.y
= (int) ((r
.y
- srcRect
.y
) * magnification
) - extra
;
1419 r
.width
= (int) Math
.ceil(r
.width
* magnification
) + extra
+ extra
;
1420 r
.height
= (int) Math
.ceil(r
.height
* magnification
) + extra
+ extra
;
1421 RT
.paint(r
, update_graphics
);
1429 * Repaint the clip corresponding to the sum of all boundingboxes of
1430 * Displayable objects in the hashset.
1432 // it is assumed that the linked objects are close to each other, otherwise
1433 // the clip rectangle grows enormously.
1434 public void repaint(final HashSet hs
) {
1435 if (null == hs
) return;
1436 final Iterator it
= hs
.iterator();
1438 Rectangle r
= new Rectangle();
1439 final Layer dl
= display
.getLayer();
1440 while (it
.hasNext()) {
1441 final Displayable d
= (Displayable
) it
.next();
1442 if (d
.getLayer() == dl
) {
1444 r
.add(d
.getBoundingBox());
1448 //repaint(r.x, r.y, r.width, r.height);
1449 RT
.paint(r
, update_graphics
);
1454 * Repaint the given offscreen Rectangle after transforming its data on the fly to the
1455 * srcRect and magnification of this DisplayCanvas. The Rectangle is not
1458 public void repaint(final Rectangle r
, final int extra
) {
1460 //Utils.log2("DisplayCanvas.repaint(Rectangle, int) warning: null r");
1461 RT
.paint(null, update_graphics
);
1464 // 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);
1465 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
);
1469 * Repaint the given Rectangle after transforming its data on the fly to the
1470 * srcRect and magnification of this DisplayCanvas. The Rectangle is not
1472 * @param box The rectangle to repaint
1473 * @param extra The extra outbound padding to add to the rectangle
1474 * @param update_graphics Whether to recreate the offscreen images or not
1476 public void repaint(Rectangle box
, int extra
, boolean update_graphics
) {
1477 this.update_graphics
= update_graphics
;
1478 repaint(box
, extra
);
1481 /** Repaint everything, updating offscreen graphics if so specified. */
1482 public void repaint(final boolean update_graphics
) {
1483 this.update_graphics
= update_graphics
| this.update_graphics
;
1484 RT
.paint(null, update_graphics
);
1487 /** Overridden to multithread. This method is here basically to enable calls to the FakeImagePlus.draw from the HAND and other tools to repaint properly.*/
1488 public void repaint() {
1489 //Utils.log2("issuing thread");
1490 RT
.paint(null, update_graphics
);
1493 /** Overridden to multithread. */
1494 /* // saved as unoveridden to make sure there are no infinite thread loops when calling super in buggy JVMs
1495 public void repaint(long ms, int x, int y, int width, int height) {
1496 RT.paint(new Rectangle(x, y, width, height), update_graphics);
1500 /** Overridden to multithread. */
1501 public void repaint(int x
, int y
, int width
, int height
) {
1502 RT
.paint(new Rectangle(x
, y
, width
, height
), update_graphics
);
1505 public void setUpdateGraphics(boolean b
) {
1506 update_graphics
= b
;
1509 /** Release offscreen images and stop threads. */
1510 public void flush() {
1511 // cleanup update graphics thread if any
1513 synchronized (offscreen_lock
) {
1514 offscreen_lock
.lock();
1517 // reset for remaking if necessary TODO doesn't work in at least java 1.6 ?
1518 update_graphics
= true;
1520 offscreen_lock
.unlock();
1524 public void destroy() {
1526 WindowManager
.setTempCurrentImage(imp
); // the FakeImagePlus
1527 WindowManager
.removeWindow(fake_win
); // the FakeImageWindow
1530 public boolean isTransforming() { // TODO: this can fail if the Display is closed quickly after creation
1531 return display
.getSelection().isTransforming();
1534 public void setTransforming(boolean b
) {
1535 if (ProjectToolbar
.getToolId() != ProjectToolbar
.SELECT
&& b
) {
1536 ProjectToolbar
.setTool(ProjectToolbar
.SELECT
);
1538 display
.getSelection().setTransforming(b
);
1539 //repaint other Displays as well!
1540 Display
.repaint(display
.getLayerSet());
1543 public void cancelTransform() {
1544 Selection selection
= display
.getSelection();
1545 Rectangle box
= selection
.getLinkedBox();
1546 selection
.cancelTransform();
1547 box
.add(selection
.getLinkedBox()); // the restored box now.
1548 if (!(selection
.getNSelected() == 1 && !display
.getActive().isLinked())) update_graphics
= true;
1553 public void keyReleased(KeyEvent ke) {
1554 int key_code = ke.getKeyCode();
1556 case KeyEvent.VK_UP:
1557 case KeyEvent.VK_DOWN:
1558 case KeyEvent.VK_LEFT:
1559 case KeyEvent.VK_RIGHT:
1560 Selection selection = display.getSelection();
1561 Rectangle b = selection.getLinkedBox();
1562 selection.resetBox();
1563 b.add(selection.getLinkedBox());
1571 public void keyPressed(KeyEvent ke
) {
1575 if (ke.getKeyCode() == KeyEvent.VK_D && ke.isShiftDown() && ke.isAltDown() && ke.isControlDown()) {
1577 java.lang.reflect.Field f = Display.class.getDeclaredField("hs_panels");
1578 f.setAccessible(true);
1579 Utils.log("Display n_panels:" + ((java.util.HashMap)f.get(display)).size());
1580 Utils.log("Display displ.: " + display.getLayer().getDisplayables().size());
1582 } catch (Exception e) {
1589 Displayable active
= display
.getActive();
1591 if (null != freehandProfile
1592 && ProjectToolbar
.getToolId() == ProjectToolbar
.PENCIL
1593 && ke
.getKeyCode() == KeyEvent
.VK_ESCAPE
1594 && null != freehandProfile
)
1596 freehandProfile
.abort();
1602 * TODO screen editor ... TEMPORARY if (active instanceof DLabel) {
1603 * active.keyPressed(ke); ke.consume(); return; }
1606 int keyCode
= ke
.getKeyCode();
1607 int keyChar
= ke
.getKeyChar();
1609 boolean used
= false;
1628 ke
.consume(); // otherwise ImageJ would use it!
1632 if (input_disabled
) {
1633 if (KeyEvent
.VK_ESCAPE
== keyCode
) {
1634 // cancel last job if any
1635 if (Utils
.checkYN("Really cancel job?")) {
1636 display
.getProject().getLoader().quitJob(null);
1637 display
.repairGUI();
1641 return; // only zoom is enabled, above
1644 if (KeyEvent
.VK_W
== keyCode
) {
1645 display
.remove(false); // will call back the canvas.flush()
1648 } else if (KeyEvent
.VK_S
== keyCode
&& 0 == ke
.getModifiers() && display
.getProject().getLoader().isAsynchronous()) {
1649 display
.getProject().getLoader().save(display
.getProject());
1654 // if display is not read-only, check for other keys:
1657 case ',': // select next Layer up
1658 display
.previousLayer(ke
.getModifiers()); // repaints as well
1662 case '.': // select next Layer down
1663 display
.nextLayer(ke
.getModifiers());
1668 if (null == active
&& null != imp
.getRoi()) {
1669 IJ
.getInstance().keyPressed(ke
);
1673 // end here if display is read-only
1674 if (display
.isReadOnly()) {
1676 display
.repaintAll();
1680 if (KeyEvent
.VK_ENTER
== keyCode
) {
1681 if (display
.getSelection().isTransforming()) {
1682 setTransforming(false); // will apply transforms and repaint
1685 } else if (display
.getLayer().getParent().isAligning()) {
1686 display
.getLayer().getParent().applyAlign(false);
1690 IJ
.getInstance().toFront();
1696 // check preconditions (or the keys are meaningless). Allow 'enter' to
1697 // bring forward the ImageJ window, and 'v' to paste a patch.
1698 /*if (null == active && KeyEvent.VK_ENTER != keyCode && KeyEvent.VK_V != keyCode && KeyEvent) {
1702 Layer layer
= display
.getLayer();
1704 final int mod
= ke
.getModifiers();
1707 case KeyEvent
.VK_COMMA
:
1708 case 0xbc: // select next Layer up
1709 display
.nextLayer(ke
.getModifiers());
1711 case KeyEvent
.VK_PERIOD
:
1712 case 0xbe: // select next Layer down
1713 display
.previousLayer(ke
.getModifiers());
1716 // UNDO: shift+z or ctrl+z
1717 if (0 == (mod ^ Event
.SHIFT_MASK
) || 0 == (mod ^ Utils
.getControlModifier())) {
1718 // If it's the last step and the last action was not Z_KEY undo action, then store current:
1719 Bureaucrat
.createAndStart(new Worker
.Task("Undo") { public void exec() {
1720 if (isTransforming()) display
.getSelection().undoOneStep();
1721 else display
.getLayerSet().undoOneStep();
1722 Display
.repaint(display
.getLayerSet());
1723 }}, display
.getProject());
1725 // REDO: alt+z or ctrl+shift+z
1726 } else if (0 == (mod ^ Event
.ALT_MASK
) || 0 == (mod ^
(Event
.SHIFT_MASK
| Utils
.getControlModifier())) ) {
1727 Bureaucrat
.createAndStart(new Worker
.Task("Redo") { public void exec() {
1728 if (isTransforming()) display
.getSelection().redoOneStep();
1729 else display
.getLayerSet().redoOneStep();
1730 Display
.repaint(display
.getLayerSet());
1731 }}, display
.getProject());
1734 // else, the 'z' command restores the image using ImageJ internal undo
1737 if (null != active
&& 0 == ke
.getModifiers() && !isTransforming()) {
1738 setTransforming(true);
1741 // else, let ImageJ grab the ROI into the Manager, if any
1744 if (0 == (ke
.getModifiers() ^ Utils
.getControlModifier())) {
1745 display
.getSelection().selectAll();
1746 Display
.repaint(display
.getLayer(), display
.getSelection().getBox(), 0);
1748 break; // INSIDE the 'if' block, so that it can bleed to the default block which forwards to active!
1749 } else if (null != active
) {
1750 active
.keyPressed(ke
);
1751 if (ke
.isConsumed()) break;
1752 // TODO this is just a hack really. Should just fall back to default switch option.
1753 // The whole keyPressed method needs revision: should not break from it when not using the key.
1755 case KeyEvent
.VK_ESCAPE
: // cancel transformation
1756 if (display
.getLayer().getParent().isAligning()) {
1757 display
.getLayer().getParent().cancelAlign();
1759 } else if (null != active
) {
1760 if (display
.getSelection().isTransforming()) cancelTransform();
1762 display
.select(null); // deselect
1763 // repaint out the brush if present
1764 if (ProjectToolbar
.PEN
== ProjectToolbar
.getToolId()) {
1765 repaint(old_brush_box
, 0);
1771 case KeyEvent
.VK_SPACE
:
1772 if (0 == ke
.getModifiers()) {
1773 if (null != active
) {
1774 invalidateVolatile();
1775 if (Math
.abs(active
.getAlpha() - 0.5f
) > 0.001f
) active
.setAlpha(0.5f
);
1776 else active
.setAlpha(1.0f
);
1777 display
.setTransparencySlider(active
.getAlpha());
1782 int kem
= ke
.getModifiers();
1783 if (0 != (kem
& KeyEvent
.SHIFT_MASK
)
1784 && 0 != (kem
& KeyEvent
.ALT_MASK
)
1785 && 0 != (kem
& KeyEvent
.CTRL_MASK
)) {
1786 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.");
1792 if (ke
.isAltDown()) {
1795 } else if (dragging
) {
1796 // 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)
1804 if (ke
.isAltDown()) {
1805 if (ke
.isShiftDown()) display
.importImage();
1806 else display
.importNextImage();
1810 case KeyEvent
.VK_PAGE_UP
: // as in Inkscape
1811 if (null != active
) {
1812 update_graphics
= true;
1813 layer
.getParent().move(LayerSet
.UP
, active
);
1814 Display
.repaint(layer
, active
, 5);
1815 Display
.updatePanelIndex(layer
, active
);
1819 case KeyEvent
.VK_PAGE_DOWN
: // as in Inkscape
1820 if (null != active
) {
1821 update_graphics
= true;
1822 layer
.getParent().move(LayerSet
.DOWN
, active
);
1823 Display
.repaint(layer
, active
, 5);
1824 Display
.updatePanelIndex(layer
, active
);
1828 case KeyEvent
.VK_HOME
: // as in Inkscape
1829 if (null != active
) {
1830 update_graphics
= true;
1831 layer
.getParent().move(LayerSet
.TOP
, active
);
1832 Display
.repaint(layer
, active
, 5);
1833 Display
.updatePanelIndex(layer
, active
);
1837 case KeyEvent
.VK_END
: // as in Inkscape
1838 if (null != active
) {
1839 update_graphics
= true;
1840 layer
.getParent().move(LayerSet
.BOTTOM
, active
);
1841 Display
.repaint(layer
, active
, 5);
1842 Display
.updatePanelIndex(layer
, active
);
1847 if (0 == ke
.getModifiers()) {
1848 if (null == active
|| active
.getClass() == Patch
.class) {
1849 // paste a new image
1850 ImagePlus clipboard
= ImagePlus
.getClipboard();
1851 if (null != clipboard
) {
1852 ImagePlus imp
= new ImagePlus(clipboard
.getTitle() + "_" + System
.currentTimeMillis(), clipboard
.getProcessor().crop());
1853 Object info
= clipboard
.getProperty("Info");
1854 if (null != info
) imp
.setProperty("Info", (String
)info
);
1855 double x
= srcRect
.x
+ srcRect
.width
/2 - imp
.getWidth()/2;
1856 double y
= srcRect
.y
+ srcRect
.height
/2 - imp
.getHeight()/2;
1857 // save the image somewhere:
1858 Patch pa
= display
.getProject().getLoader().addNewImage(imp
, x
, y
);
1859 display
.getLayer().add(pa
);
1861 } // TODO there isn't much ImageJ integration in the pasting. Can't paste to a selected image, for example.
1863 // Each type may know how to paste data from the copy buffer into itself:
1864 active
.keyPressed(ke
);
1870 if (null != active
) {
1871 active
.keyPressed(ke
);
1875 if (0 == ke
.getModifiers()) {
1876 final Project pro
= display
.getProject();
1877 if ("true".equals(pro
.getProperty("no_color_cues"))) {
1879 pro
.setProperty("no_color_cues", null);
1881 pro
.setProperty("no_color_cues", "true");
1883 Display
.repaint(display
.getLayer().getParent());
1887 case KeyEvent
.VK_DELETE
:
1888 if (0 == ke
.getModifiers()) {
1889 display
.getSelection().deleteAll();
1893 if (0 == ke
.getModifiers() && null != active
&& active
.getClass() == Profile
.class) {
1894 display
.duplicateLinkAndSendTo(active
, 0, active
.getLayer().getParent().previous(layer
));
1899 if (0 == ke
.getModifiers() && null != active
&& active
.getClass() == Profile
.class) {
1900 display
.duplicateLinkAndSendTo(active
, 1, active
.getLayer().getParent().next(layer
));
1904 case KeyEvent
.VK_F1
:
1905 case KeyEvent
.VK_F2
:
1906 case KeyEvent
.VK_F3
:
1907 case KeyEvent
.VK_F4
:
1908 case KeyEvent
.VK_F5
:
1909 case KeyEvent
.VK_F6
:
1910 case KeyEvent
.VK_F7
:
1911 case KeyEvent
.VK_F8
:
1912 case KeyEvent
.VK_F9
:
1913 case KeyEvent
.VK_F10
:
1914 case KeyEvent
.VK_F11
:
1915 case KeyEvent
.VK_F12
:
1916 ProjectToolbar
.keyPressed(ke
);
1918 case KeyEvent
.VK_UP
:
1919 case KeyEvent
.VK_DOWN
:
1920 case KeyEvent
.VK_LEFT
:
1921 case KeyEvent
.VK_RIGHT
:
1924 // forward event to active
1925 if (null != active
) {
1926 active
.keyPressed(ke
);
1927 if (ke
.isConsumed()) {
1928 Selection selection
= display
.getSelection();
1929 repaint(selection
.getLinkedBox(), Selection
.PADDING
+ 2); // optimization
1934 if ( !(keyCode
== KeyEvent
.VK_UNDEFINED
|| keyChar
== KeyEvent
.CHAR_UNDEFINED
) && !ke
.isConsumed() && null != active
&& active
instanceof Patch
) {
1935 // forward to ImageJ for a final try
1936 IJ
.getInstance().keyPressed(ke
);
1940 //Utils.log2("keyCode, keyChar: " + keyCode + ", " + keyChar + " ref: " + KeyEvent.VK_UNDEFINED + ", " + KeyEvent.CHAR_UNDEFINED);
1943 public void keyTyped(KeyEvent ke
) {}
1945 public void keyReleased(KeyEvent ke
) {}
1947 public void zoomToFit() {
1948 double magw
= (double) getWidth() / imageWidth
;
1949 double magh
= (double) getHeight() / imageHeight
;
1950 this.magnification
= magw
< magh ? magw
: magh
;
1951 this.srcRect
.setRect(0, 0, imageWidth
, imageHeight
);
1952 setMagnification(magnification
);
1953 display
.updateInDatabase("srcRect"); // includes magnification
1957 public void setReceivesInput(boolean b
) {
1958 this.input_disabled
= !b
;
1961 public boolean isInputEnabled() {
1962 return !input_disabled
;
1965 public void exportXML(StringBuffer sb_body
, String indent
, Object any
) {
1966 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");
1969 /** 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.*/
1970 public ImagePlus
getFakeImagePlus() {
1974 /** Key/Mouse bindings like:
1975 * - ij.gui.StackWindow: wheel to scroll slices (in this case Layers)
1976 * - Inkscape: control+wheel to zoom (apple+wheel in macosx, since control+wheel zooms desktop)
1978 public void mouseWheelMoved(MouseWheelEvent mwe
) {
1979 if (dragging
) return; // prevent unexpected mouse wheel movements
1980 final int modifiers
= mwe
.getModifiers();
1981 final int rotation
= mwe
.getWheelRotation();
1982 if (0 == (modifiers ^ Utils
.getControlModifier())) {
1983 // scroll zooom under pointer
1986 if (x
< 0 || y
< 0 || x
>= getWidth() || y
>= getHeight()) {
1995 } else if (0 == (modifiers ^ InputEvent
.SHIFT_MASK
) && ProjectToolbar
.getToolId() == ProjectToolbar
.PEN
) {
1996 int brushSize_old
= ProjectToolbar
.getBrushSize();
1997 // resize brush for AreaList painting
1998 final int sign
= rotation
> 0 ?
1 : -1;
1999 int brushSize
= ProjectToolbar
.setBrushSize((int)(5 * sign
/ magnification
)); // the getWheelRotation provides the sign
2000 if (brushSize_old
> brushSize
) brushSize
= brushSize_old
; // for repainting purposes alnne
2001 int extra
= (int)(5 / magnification
);
2002 if (extra
< 2) extra
= 2;
2004 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
);
2006 } else if (0 == modifiers
) {
2008 if (rotation
> 0) display
.nextLayer(modifiers
);
2009 else display
.previousLayer(modifiers
);
2013 protected class RepaintProperties
implements AbstractOffscreenThread
.RepaintProperties
{
2014 final private Layer layer
;
2015 final private int g_width
;
2016 final private int g_height
;
2017 final private Displayable active
;
2018 final private int c_alphas
;
2019 final private Rectangle clipRect
;
2020 final private int mode
;
2021 final private HashMap
<Color
,Layer
> hm
;
2022 final private ArrayList
<LayerPanel
> blending_list
;
2024 RepaintProperties(final Rectangle clipRect
, final Layer layer
, final int g_width
, final int g_height
, final Displayable active
, final int c_alphas
) {
2025 this.clipRect
= clipRect
;
2027 this.g_width
= g_width
;
2028 this.g_height
= g_height
;
2029 this.active
= active
;
2030 this.c_alphas
= c_alphas
;
2032 // query the display for repainting mode
2033 this.hm
= new HashMap
<Color
,Layer
>();
2034 this.blending_list
= new ArrayList
<LayerPanel
>();
2035 this.mode
= display
.getPaintMode(hm
, blending_list
);
2039 private final class OffscreenThread
extends AbstractOffscreenThread
{
2042 super("T2-Canvas-Offscreen");
2045 public void paint() {
2050 final Displayable active
;
2052 final Rectangle clipRect
;
2053 final Loader loader
;
2054 final HashMap
<Color
,Layer
> hm
;
2055 final ArrayList
<LayerPanel
> blending_list
;
2058 synchronized (this) {
2059 final DisplayCanvas
.RepaintProperties rp
= (DisplayCanvas
.RepaintProperties
) this.rp
;
2061 g_width
= rp
.g_width
;
2062 g_height
= rp
.g_height
;
2064 c_alphas
= rp
.c_alphas
;
2065 clipRect
= rp
.clipRect
;
2066 loader
= layer
.getProject().getLoader();
2069 blending_list
= rp
.blending_list
;
2072 // flag Loader to do massive flushing if needed
2073 loader
.setMassiveMode(true);
2075 // ALMOST, but not always perfect //if (null != clipRect) g.setClip(clipRect);
2077 // prepare the canvas for the srcRect and magnification
2078 final AffineTransform atc
= new AffineTransform();
2079 atc
.scale(magnification
, magnification
);
2080 atc
.translate(-srcRect
.x
, -srcRect
.y
);
2082 // Area to which each Patch will subtract from
2083 //final Area background = new Area(new Rectangle(0, 0, g_width, g_height));
2084 // bring the area to Layer space
2085 //background.transform(atc.createInverse());
2087 // the non-srcRect areas, in offscreen coords
2088 final Rectangle r1
= new Rectangle(srcRect
.x
+ srcRect
.width
, srcRect
.y
, (int)(g_width
/ magnification
) - srcRect
.width
, (int)(g_height
/ magnification
));
2089 final Rectangle r2
= new Rectangle(srcRect
.x
, srcRect
.y
+ srcRect
.height
, srcRect
.width
, (int)(g_height
/ magnification
) - srcRect
.height
);
2092 final ArrayList
<Displayable
> al_top
= new ArrayList
<Displayable
>();
2093 boolean top
= false;
2095 final ArrayList
<Displayable
> al_paint
= new ArrayList
<Displayable
>();
2097 final ArrayList
<Patch
> al_patches
= new ArrayList
<Patch
>();
2100 //final ArrayList al = layer.getDisplayables();
2101 layer
.getParent().checkBuckets();
2102 layer
.checkBuckets();
2103 final Iterator
<Displayable
> ital
= layer
.find(srcRect
, true).iterator();
2104 final Collection
<Displayable
> al_zdispl
= layer
.getParent().findZDisplayables(layer
, srcRect
, true);
2105 final Iterator
<Displayable
> itzd
= al_zdispl
.iterator();
2107 // Assumes the Layer has its objects in order:
2109 // 2 - Profiles, Balls
2110 // 3 - Pipes and ZDisplayables (from the parent LayerSet)
2113 Displayable tmp
= null;
2115 while (ital
.hasNext()) {
2116 final Displayable d
= ital
.next();
2117 final Class c
= d
.getClass();
2118 if (DLabel
.class == c
|| LayerSet
.class == c
) {
2119 tmp
= d
; // since ital.next() has moved forward already
2122 if (Patch
.class == c
) {
2124 al_patches
.add((Patch
)d
);
2126 if (!top
&& d
== active
) top
= true; // no Patch on al_top ever
2127 if (top
) al_top
.add(d
);
2128 else al_paint
.add(d
);
2132 // preload concurrently as many as possible
2133 Loader
.preload(al_patches
, magnification
, false); // must be false; a 'true' would incur in an infinite loop.
2135 // paint the ZDisplayables here, before the labels and LayerSets, if any
2136 while (itzd
.hasNext()) {
2137 final Displayable zd
= itzd
.next();
2138 if (zd
== active
) top
= true;
2139 else if (top
) al_top
.add(zd
);
2140 else al_paint
.add(zd
);
2142 // paint LayerSet and DLabel objects!
2144 if (tmp
== active
) top
= true;
2145 else if (top
) al_top
.add(tmp
);
2146 else al_paint
.add(tmp
);
2148 while (ital
.hasNext()) {
2149 final Displayable d
= ital
.next();
2150 if (d
== active
) top
= true;
2151 else if (top
) al_top
.add(d
);
2152 else al_paint
.add(d
);
2155 // create new graphics
2156 layer
.getProject().getLoader().releaseToFit(g_width
* g_height
* 4 + 1024);
2157 final BufferedImage target
= getGraphicsConfiguration().createCompatibleImage(g_width
, g_height
, Transparency
.TRANSLUCENT
); // creates a BufferedImage.TYPE_INT_ARGB image in my T60p ATI FireGL laptop
2158 final Graphics2D g
= (Graphics2D
)target
.getGraphics();
2160 g
.setTransform(atc
); //at_original);
2162 //setRenderingHints(g);
2163 // always a stroke of 1.0, regardless of magnification; the stroke below corrects for that
2164 g
.setStroke(stroke
);
2168 // Testing: removed Area.subtract, now need to fill int background
2169 g
.setColor(Color
.black
);
2170 g
.fillRect(0, 0, g_width
- r1
.x
, g_height
- r2
.y
);
2175 // 2 - images and anything else not on al_top
2176 // 3 - non-srcRect areas
2178 //Utils.log2("offscreen painting: " + al_paint.size());
2181 // Determine painting mode
2182 if (Display
.REPAINT_SINGLE_LAYER
== mode
) {
2183 // Direct painting mode, with prePaint abilities
2184 for (final Displayable d
: al_paint
) {
2185 d
.prePaint(g
, magnification
, d
== active
, c_alphas
, layer
);
2187 } else if (Display
.REPAINT_MULTI_LAYER
== mode
) {
2188 // paint first the current layer Patches only (to set the background)
2190 for (final Displayable d
: al_patches
) {
2191 d
.paint(g
, magnification
, d
== active
, c_alphas
, layer
);
2194 // then blend on top the Patches of the others, in reverse Z order and using the alpha of the LayerPanel
2195 final Composite original
= g
.getComposite();
2197 g
.setTransform(new AffineTransform());
2198 for (final ListIterator
<LayerPanel
> it
= blending_list
.listIterator(blending_list
.size()); it
.hasPrevious(); ) {
2199 final LayerPanel lp
= it
.previous();
2200 if (lp
.layer
== layer
) continue;
2201 layer
.getProject().getLoader().releaseToFit(g_width
* g_height
* 4 + 1024);
2202 final BufferedImage bi
= getGraphicsConfiguration().createCompatibleImage(g_width
, g_height
, Transparency
.TRANSLUCENT
);
2203 final Graphics2D gb
= bi
.createGraphics();
2204 gb
.setTransform(atc
);
2205 for (final Displayable d
: lp
.layer
.find(srcRect
, true)) {
2206 if (d
.getClass() != Patch
.class) continue; // skip non-images
2207 d
.paint(gb
, magnification
, false, c_alphas
, lp
.layer
);
2209 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, lp
.getAlpha()));
2210 g
.drawImage(bi
, 0, 0, null);
2214 g
.setComposite(original
);
2215 g
.setTransform(atc
);
2217 // then paint the non-Patch objects of the current layer
2218 for (final Displayable d
: al_paint
.subList(al_patches
.size(), al_paint
.size())) {
2219 d
.paint(g
, magnification
, d
== active
, c_alphas
, layer
);
2221 } else { // Display.REPAINT_RGB_LAYER == mode
2222 final HashMap
<Color
,byte[]> channels
= new HashMap
<Color
,byte[]>();
2223 hm
.put(Color
.green
, layer
);
2224 for (final Map
.Entry
<Color
,Layer
> e
: hm
.entrySet()) {
2225 final BufferedImage bi
= new BufferedImage(g_width
, g_height
, BufferedImage
.TYPE_BYTE_GRAY
); //INDEXED, Loader.GRAY_LUT);
2226 final Graphics2D gb
= bi
.createGraphics();
2227 gb
.setTransform(atc
);
2228 final Layer la
= e
.getValue();
2229 if (la
== layer
&& Color
.green
!= e
.getKey()) continue; // don't paint current layer in two channels
2230 for (final Displayable d
: la
.find(srcRect
, true)) {
2231 if (d
.getClass() != Patch
.class) continue; // skip non-images
2232 d
.paint(gb
, magnification
, false, c_alphas
, la
);
2234 channels
.put(e
.getKey(), (byte[])new ByteProcessor(bi
).getPixels());
2236 final byte[] red
, green
, blue
;
2237 green
= channels
.get(Color
.green
);
2238 if (null == channels
.get(Color
.red
)) red
= new byte[green
.length
];
2239 else red
= channels
.get(Color
.red
);
2240 if (null == channels
.get(Color
.blue
)) blue
= new byte[green
.length
];
2241 else blue
= channels
.get(Color
.blue
);
2242 final int[] pix
= new int[green
.length
];
2243 for (int i
=0; i
<green
.length
; i
++) {
2244 pix
[i
] = ((red
[i
] & 0xff) << 16) + ((green
[i
] & 0xff) << 8) + (blue
[i
] & 0xff);
2246 // undo transform, is intended for Displayable objects
2247 g
.setTransform(new AffineTransform());
2248 final Image img
= new ColorProcessor(g_width
, g_height
, pix
).createImage();
2249 g
.drawImage(img
, 0, 0, null);
2252 g
.setTransform(atc
);
2254 // then paint the non-Patch objects of the current layer
2255 for (final Displayable d
: al_paint
.subList(al_patches
.size(), al_paint
.size())) {
2256 d
.paint(g
, magnification
, d
== active
, c_alphas
, layer
);
2260 // finally, paint non-srcRect areas
2261 if (r1
.width
> 0 || r1
.height
> 0 || r2
.width
> 0 || r2
.height
> 0) {
2262 g
.setColor(Color
.gray
);
2269 synchronized (offscreen_lock
) {
2270 offscreen_lock
.lock();
2273 update_graphics
= false;
2274 loader
.setMassiveMode(false);
2275 if (null != offscreen
) offscreen
.flush();
2277 invalidateVolatile();
2278 DisplayCanvas
.this.al_top
= al_top
;
2280 } catch (Exception e
) {
2281 e
.printStackTrace();
2284 offscreen_lock
.unlock();
2287 // Send repaint event, without offscreen graphics
2288 RT
.paint(clipRect
, false);
2290 } catch (OutOfMemoryError oome
) {
2291 // so OutOfMemoryError won't generate locks
2292 IJError
.print(oome
);
2293 } catch (Exception e
) {
2299 // added here to prevent flickering, but doesn't help. All it does is avoid a call to imp.redraw()
2300 protected void scroll(int sx
, int sy
) {
2301 int ox
= xSrcStart
+ (int)(sx
/magnification
); //convert to offscreen coordinates
2302 int oy
= ySrcStart
+ (int)(sy
/magnification
);
2303 int newx
= xSrcStart
+ (xMouseStart
-ox
);
2304 int newy
= ySrcStart
+ (yMouseStart
-oy
);
2305 if (newx
<0) newx
= 0;
2306 if (newy
<0) newy
= 0;
2307 if ((newx
+srcRect
.width
)>imageWidth
) newx
= imageWidth
-srcRect
.width
;
2308 if ((newy
+srcRect
.height
)>imageHeight
) newy
= imageHeight
-srcRect
.height
;
2313 private void handleHide(final KeyEvent ke
) {
2314 if (ke
.isAltDown() && !ke
.isShiftDown()) {
2316 display
.getLayer().getParent().setAllVisible(false);
2317 //Display.repaint(display.getLayer());
2318 Display
.update(display
.getLayer());
2322 if (ke
.isShiftDown()) {
2324 display
.hideDeselected(ke
.isAltDown());
2328 // else, hide selected
2329 display
.getSelection().setVisible(false);
2330 Display
.update(display
.getLayer());
2334 private class ScreenImage
{
2336 Rectangle srcRect
= null;
2339 final boolean equals(final long layer_id
, final Rectangle srcRect
, final double mag
) {
2340 return layer_id
== this.layer_id
&& mag
== this.mag
&& srcRect
.equals(this.srcRect
);
2342 /** Flushes the old awt if any. */
2343 final void set(final Image awt
, final long layer_id
, final Rectangle srcRect
, final double mag
) {
2344 this.layer_id
= layer_id
;
2345 this.srcRect
= (Rectangle
)srcRect
.clone();
2346 if (null != awt
&& awt
!= this.awt
) this.awt
.flush();
2350 final void flush() {
2352 this.srcRect
= null;
2353 if (null != this.awt
) this.awt
.flush();
2357 final boolean isFlushed() { return -1 == layer_id
; }
2360 /*** 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. */
2361 private class ShallowCache
{
2362 final ScreenImage
[] sim
= new ScreenImage
[75];
2364 void add(final Image awt
, final Layer layer
, final Rectangle srcRect
, final double mag
) {
2365 final long layer_id
= layer
.getId();
2366 // Only one awt per layer_id
2368 for (;i
<sim
.length
; i
++) {
2369 if (null == sim
[i
]) { sim
[i
] = new ScreenImage(); break; }
2370 if (sim
[i
].isFlushed()) break;
2371 if (sim
[i
].layer_id
== layer_id
) {
2372 sim
[i
].set(awt
, layer_id
, srcRect
, mag
);
2376 // ok so set the given image at 'i'
2378 if (sim
.length
== i
) {
2379 // no space, pop oldest
2380 // So now oldest is next to oldest;
2382 if (oldest
== sim
.length
) k
= oldest
= 0;
2385 sim
[k
].set(awt
, layer_id
, srcRect
, mag
);
2386 layer
.getProject().getLoader().cacheOffscreen(layer
, awt
);
2388 Image
get(final Layer layer
, final Rectangle srcRect
, final double mag
) {
2389 final long layer_id
= layer
.getId();
2390 for (int i
=0; i
<sim
.length
; i
++) {
2391 if (null == sim
[i
]) return null;
2392 if (sim
[i
].equals(layer_id
, srcRect
, mag
)) {
2393 Image awt
= layer
.getProject().getLoader().getCached(layer_id
, 0);
2394 if (null == awt
) sim
[i
].flush(); // got lost
2400 void flush(Layer layer
) {
2401 final long layer_id
= layer
.getId();
2402 for (int i
=0; i
<sim
.length
; i
++) {
2403 if (sim
[i
].layer_id
== layer_id
) {
2404 layer
.getProject().getLoader().decacheAWT(layer_id
);
2406 break; // only one per layer_id