/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.canvas; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.List; import java.util.concurrent.TimeUnit; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.Timer; import icy.canvas.CanvasLayerEvent.LayersEventType; import icy.canvas.IcyCanvasEvent.IcyCanvasEventType; import icy.gui.component.button.IcyToggleButton; import icy.gui.menu.ToolRibbonTask; import icy.gui.menu.ToolRibbonTask.ToolRibbonTaskListener; import icy.gui.util.GuiUtil; import icy.gui.viewer.Viewer; import icy.image.IcyBufferedImage; import icy.image.IcyBufferedImageUtil; import icy.image.ImageUtil; import icy.main.Icy; import icy.math.Interpolator; import icy.math.MathUtil; import icy.math.MultiSmoothMover; import icy.math.MultiSmoothMover.MultiSmoothMoverAdapter; import icy.math.SmoothMover; import icy.math.SmoothMover.SmoothMoveType; import icy.math.SmoothMover.SmoothMoverAdapter; import icy.painter.ImageOverlay; import icy.painter.Overlay; import icy.preferences.CanvasPreferences; import icy.preferences.XMLPreferences; import icy.resource.ResourceUtil; import icy.resource.icon.IcyIcon; import icy.roi.ROI; import icy.sequence.DimensionId; import icy.sequence.Sequence; import icy.sequence.SequenceEvent.SequenceEventType; import icy.system.thread.SingleProcessor; import icy.system.thread.ThreadUtil; import icy.type.rectangle.Rectangle2DUtil; import icy.type.rectangle.Rectangle5D; import icy.util.EventUtil; import icy.util.GraphicsUtil; import icy.util.ShapeUtil; import icy.util.StringUtil; import plugins.kernel.roi.tool.plugin.ROILineCutterPlugin; /** * New Canvas 2D : default ICY 2D viewer.<br> * Support translation / scale and rotation transformation.<br> * * @author Stephane */ public class Canvas2D extends IcyCanvas2D implements ToolRibbonTaskListener { /** * */ private static final long serialVersionUID = 8850168605044063031L; static final int ICON_SIZE = 20; static final int ICON_TARGET_SIZE = 20; static final Image ICON_CENTER_IMAGE = ResourceUtil.ICON_CENTER_IMAGE; static final Image ICON_FIT_IMAGE = ResourceUtil.ICON_FIT_IMAGE; static final Image ICON_FIT_CANVAS = ResourceUtil.ICON_FIT_CANVAS; // static final Image ICON_CENTER_IMAGE = ImageUtil.scale(ResourceUtil.ICON_CENTER_IMAGE, // ICON_SIZE, ICON_SIZE); // static final Image ICON_FIT_IMAGE = ImageUtil.scale(ResourceUtil.ICON_FIT_IMAGE, ICON_SIZE, // ICON_SIZE); // static final Image ICON_FIT_CANVAS = ImageUtil.scale(ResourceUtil.ICON_FIT_CANVAS, ICON_SIZE, // ICON_SIZE); static final Image ICON_TARGET = ImageUtil.scale(ResourceUtil.ICON_TARGET, ICON_SIZE, ICON_SIZE); static final Image ICON_TARGET_BLACK = ImageUtil.getColorImageFromAlphaImage(ICON_TARGET, Color.black); static final Image ICON_TARGET_LIGHT = ImageUtil.getColorImageFromAlphaImage(ICON_TARGET, Color.lightGray); /** * Possible rounded zoom factor : 0.01 --> 100 */ final static double[] zoomRoundedFactors = new double[] {0.01d, 0.02d, 0.0333d, 0.05d, 0.075d, 0.1d, 0.15d, 0.2d, 0.25d, 0.333d, 0.5d, 0.66d, 0.75d, 1d, 1.25d, 1.5d, 1.75d, 2d, 2.5d, 3d, 4d, 5d, 6.6d, 7.5d, 10d, 15d, 20d, 30d, 50d, 66d, 75d, 100d}; /** * Image overlay to encapsulate image display in a canvas layer */ protected class Canvas2DImageOverlay extends IcyCanvasImageOverlay { @Override public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) { if (g == null) return; final BufferedImage img = canvasView.imageCache.getImage(); if (img != null) g.drawImage(img, null, 0, 0); else { final Graphics2D g2 = (Graphics2D) g.create(); // set back canvas coordinate g2.transform(getInverseTransform()); g2.setFont(canvasView.font); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (canvasView.imageCache.isProcessing()) // cache not yet built canvasView.drawTextCenter(g2, "Loading...", 0.8f); else if (canvasView.imageCache.getNotEnoughMemory()) // not enough memory to render image canvasView.drawTextCenter(g2, "Not enough memory to display image", 0.8f); else // no image canvasView.drawTextCenter(g2, " No image ", 0.8f); g2.dispose(); } } } public class CanvasMap extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener { /** * */ private static final long serialVersionUID = -7305605644605013768L; private Point mouseMapPos; private Point mapStartDragPos; private double mapStartRotationZ; private boolean mapMoving; private boolean mapRotating; public CanvasMap() { super(); mouseMapPos = new Point(0, 0); mapStartDragPos = null; mapStartRotationZ = 0; mapMoving = false; mapRotating = false; setBorder(BorderFactory.createRaisedBevelBorder()); // height will then be fixed to 160 setPreferredSize(new Dimension(160, 160)); addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(this); } /** * Return AffineTransform object which transform an image coordinate to map coordinate. */ public AffineTransform getImageTransform() { final int w = getWidth(); final int h = getHeight(); final int imgW = getImageSizeX(); final int imgH = getImageSizeY(); if ((imgW == 0) || (imgH == 0)) return null; final double sx = (double) w / (double) imgW; final double sy = (double) h / (double) imgH; final double tx, ty; final double s; // scale to viewport if (sx < sy) { s = sx; tx = 0; ty = (h - (imgH * s)) / 2; } else if (sx > sy) { s = sy; ty = 0; tx = (w - (imgW * s)) / 2; } else { s = sx; tx = 0; ty = 0; } final AffineTransform result = new AffineTransform(); // get transformation to fit image in minimap result.translate(tx, ty); result.scale(s, s); return result; } /** * Transform a CanvasMap point in CanvasView point */ public Point getCanvasPosition(Point p) { // transform map coordinate to canvas coordinate return imageToCanvas(getImagePosition(p)); } /** * Transforms a Image point in CanvasView point. */ public Point getCanvasPosition(Point2D.Double p) { // transform image coordinate to canvas coordinate return imageToCanvas(p); } /** * Transforms a CanvasMap point in Image point. */ public Point2D.Double getImagePosition(Point p) { final AffineTransform trans = getImageTransform(); try { // get image coordinates return (Point2D.Double) trans.inverseTransform(p, new Point2D.Double()); } catch (Exception ecx) { return new Point2D.Double(0, 0); } } public boolean isDragging() { return mapStartDragPos != null; } protected void updateDrag(InputEvent e) { // not moving --> exit if (!mapMoving) return; final Point2D.Double startDragImagePoint = getImagePosition(mapStartDragPos); final Point2D.Double imagePoint = getImagePosition(mouseMapPos); // shift action --> limit to one direction if (EventUtil.isShiftDown(e)) { // X drag if (Math.abs(mouseMapPos.x - mapStartDragPos.x) > Math.abs(mouseMapPos.y - mapStartDragPos.y)) imagePoint.y = startDragImagePoint.y; // Y drag else imagePoint.x = startDragImagePoint.x; } // center view on this point (this update mouse canvas position) centerOnImage(imagePoint); // no need to update mouse canvas position here as it stays at center } protected void updateRot(InputEvent e) { // not rotating --> exit if (!mapRotating) return; final Point2D.Double imagePoint = getImagePosition(mouseMapPos); // update mouse canvas position from image position setMousePos(imageToCanvas(imagePoint)); // get map center final int mapCenterX = getWidth() / 2; final int mapCenterY = getHeight() / 2; // get last and current mouse position delta with center final int lastMouseDeltaPosX = mapStartDragPos.x - mapCenterX; final int lastMouseDeltaPosY = mapStartDragPos.y - mapCenterY; final int newMouseDeltaPosX = mouseMapPos.x - mapCenterX; final int newMouseDeltaPosY = mouseMapPos.y - mapCenterY; // get angle in radian between last and current mouse position // relative to image center double newAngle = Math.atan2(newMouseDeltaPosY, newMouseDeltaPosX); double lastAngle = Math.atan2(lastMouseDeltaPosY, lastMouseDeltaPosX); // inverse rotation double angle = lastAngle - newAngle; // control button down --> rotation is enforced if (EventUtil.isControlDown(e)) angle *= 3; final double destAngle; // shift action --> limit to 45� rotation if (EventUtil.isShiftDown(e)) destAngle = Math.rint((mapStartRotationZ + angle) * (8d / (2 * Math.PI))) * ((2 * Math.PI) / 8d); else destAngle = mapStartRotationZ + angle; // modify rotation with smooth mover setRotation(destAngle, true); } @Override public void mouseDragged(MouseEvent e) { canvasView.handlingMouseMoveEvent = true; try { mouseMapPos = new Point(e.getPoint()); // get the drag event ? if (isDragging()) { // left button action --> center view on mouse point if (EventUtil.isLeftMouseButton(e)) { mapMoving = true; if (mapRotating) { mapRotating = false; // force repaint so the cross is no more visible canvasView.repaint(); } updateDrag(e); } else if (EventUtil.isRightMouseButton(e)) { mapMoving = false; if (!mapRotating) { mapRotating = true; // force repaint so the cross is visible canvasView.repaint(); } updateRot(e); } // consume event e.consume(); } } finally { canvasView.handlingMouseMoveEvent = false; } } @Override public void mouseMoved(MouseEvent e) { mouseMapPos = new Point(e.getPoint()); // send to canvas view with converted canvas position canvasView.onMousePositionChanged(getCanvasPosition(e.getPoint())); } @Override public void mouseClicked(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { // start drag mouse position mapStartDragPos = (Point) e.getPoint().clone(); // store canvas parameters mapStartRotationZ = getRotationZ(); // left click action --> center view on mouse point if (EventUtil.isLeftMouseButton(e)) { final AffineTransform trans = getImageTransform(); if (trans != null) { try { // get image coordinates final Point2D imagePoint = trans.inverseTransform(e.getPoint(), null); // center view on this point centerOnImage(imagePoint.getX(), imagePoint.getY()); // update new canvas position setMousePos(imageToCanvas(imagePoint.getX(), imagePoint.getY())); // consume event e.consume(); } catch (Exception ecx) { // ignore } } } } @Override public void mouseReleased(MouseEvent e) { // assume end dragging mapStartDragPos = null; mapRotating = false; mapMoving = false; // repaint repaint(); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseWheelMoved(MouseWheelEvent e) { // we first center image to mouse position final AffineTransform trans = getImageTransform(); if (trans != null) { try { // get image coordinates final Point2D imagePoint = trans.inverseTransform(e.getPoint(), null); // center view on this point centerOnImage(imagePoint.getX(), imagePoint.getY()); // update new canvas position setMousePos(imageToCanvas(imagePoint.getX(), imagePoint.getY())); } catch (Exception ecx) { // ignore } } // send to canvas view if (canvasView.onMouseWheelMoved(e.isConsumed(), e.getWheelRotation(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e), EventUtil.isShiftDown(e))) e.consume(); } public void keyPressed(KeyEvent e) { // just for the shift key state change updateDrag(e); updateRot(e); } public void keyReleased(KeyEvent e) { // just for the shift key state change updateDrag(e); updateRot(e); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); final AffineTransform trans = getImageTransform(); if (trans != null) { final Graphics2D g2 = (Graphics2D) g.create(); final BufferedImage img = canvasView.imageCache.getImage(); // draw image if (img != null) g2.drawImage(img, trans, null); // then apply canvas inverse transformation trans.scale(1 / getScaleX(), 1 / getScaleY()); trans.translate(-getOffsetX(), -getOffsetY()); final int canvasSizeX = getCanvasSizeX(); final int canvasSizeY = getCanvasSizeY(); final int canvasCenterX = canvasSizeX / 2; final int canvasCenterY = canvasSizeY / 2; trans.translate(canvasCenterX, canvasCenterY); trans.rotate(-getRotationZ()); trans.translate(-canvasCenterX, -canvasCenterY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // get transformed rectangle final Shape shape = trans.createTransformedShape(new Rectangle(canvasSizeX, canvasSizeY)); // and draw canvas view rect of the image // TODO : the g2.draw(shape) cost sometime ! g2.setStroke(new BasicStroke(3)); g2.setColor(Color.black); g2.draw(shape); g2.setStroke(new BasicStroke(2)); g2.setColor(Color.white); g2.draw(shape); // rotation helper if (mapRotating) { final Point2D center = trans.transform(new Point(canvasCenterX, canvasCenterY), null); final int centerX = (int) Math.round(center.getX()); final int centerY = (int) Math.round(center.getY()); final BasicStroke blackStr = new BasicStroke(4); final BasicStroke greenStr = new BasicStroke(2); g2.setStroke(blackStr); g2.setColor(Color.black); g2.drawLine(centerX - 4, centerY - 4, centerX + 4, centerY + 4); g2.drawLine(centerX - 4, centerY + 4, centerX + 4, centerY - 4); g2.setStroke(greenStr); g2.setColor(Color.green); g2.drawLine(centerX - 4, centerY - 4, centerX + 4, centerY + 4); g2.drawLine(centerX - 4, centerY + 4, centerX + 4, centerY - 4); } g2.dispose(); } } } public class CanvasView extends JPanel implements ActionListener, MouseWheelListener, MouseListener, MouseMotionListener { /** * */ private static final long serialVersionUID = 4041355608444378172L; public class ImageCache implements Runnable { /** * image cache */ private BufferedImage image; /** * processor */ private final SingleProcessor processor; /** * internals */ private boolean needRebuild; private boolean notEnoughMemory; public ImageCache() { super(); processor = new SingleProcessor(true, "Canvas2D renderer"); // we want the processor to stay alive for sometime processor.setKeepAliveTime(3, TimeUnit.SECONDS); image = null; needRebuild = true; notEnoughMemory = false; // build cache processor.submit(this); } public void invalidCache() { needRebuild = true; } public boolean isValid() { return !needRebuild; } public boolean isProcessing() { return processor.isProcessing(); } public void refresh() { // rebuild cache if (needRebuild) processor.submit(this); // just repaint in the meantime getViewComponent().repaint(); } public BufferedImage getImage() { return image; } public boolean getNotEnoughMemory() { return notEnoughMemory; } @Override public void run() { // important to set it to false at beginning needRebuild = false; try { // build image image = Canvas2D.this.getARGBImage(getPositionT(), getPositionZ(), getPositionC(), image); notEnoughMemory = false; } catch (OutOfMemoryError e) { notEnoughMemory = true; } // repaint now getViewComponent().repaint(); } } /** * Image cache */ final ImageCache imageCache; /** * internals */ final Font font; private final Timer refreshTimer; private final Timer zoomInfoTimer; private final Timer rotationInfoTimer; private final SmoothMover zoomInfoAlphaMover; private final SmoothMover rotationInfoAlphaMover; private String zoomMessage; private String rotationMessage; Dimension lastSize; boolean actived; boolean handlingMouseMoveEvent; private Point startDragPosition; private Point startOffset; double curScaleX; double curScaleY; private double startRotationZ; // private Cursor previousCursor; boolean moving; boolean rotating; boolean hasMouseFocus; boolean areaSelection; public CanvasView() { super(); imageCache = new ImageCache(); actived = false; handlingMouseMoveEvent = false; startDragPosition = null; startOffset = null; curScaleX = -1; curScaleY = -1; // previousCursor = getCursor(); moving = false; rotating = false; hasMouseFocus = false; areaSelection = false; lastSize = getSize(); font = new Font("Arial", Font.BOLD, 16); zoomInfoAlphaMover = new SmoothMover(0); zoomInfoAlphaMover.setMoveTime(500); zoomInfoAlphaMover.setUpdateDelay(20); zoomInfoAlphaMover.addListener(new SmoothMoverAdapter() { @Override public void valueChanged(SmoothMover source, double newValue, int pourcent) { // just repaint repaint(); } }); rotationInfoAlphaMover = new SmoothMover(0); rotationInfoAlphaMover.setMoveTime(500); rotationInfoAlphaMover.setUpdateDelay(20); rotationInfoAlphaMover.addListener(new SmoothMoverAdapter() { @Override public void valueChanged(SmoothMover source, double newValue, int pourcent) { // just repaint repaint(); } }); refreshTimer = new Timer(100, this); refreshTimer.setRepeats(false); zoomInfoTimer = new Timer(1000, this); zoomInfoTimer.setRepeats(false); rotationInfoTimer = new Timer(1000, this); rotationInfoTimer.setRepeats(false); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { final Dimension newSize = getSize(); int extX = 0; int extY = 0; // first time component is displayed ? if (!actived) { // by default we adapt image to canvas size fitImageToCanvas(false); // center image (if cannot fit to canvas size) centerImage(); actived = true; } else { // auto FIT enabled if (zoomFitCanvasButton.isSelected()) fitImageToCanvas(true); else { // re-center final int dx = newSize.width - lastSize.width; final int dy = newSize.height - lastSize.height; final int dx2 = dx / 2; final int dy2 = dy / 2; // keep trace of lost bit extX = (2 * dx2) - dx; extY = (2 * dy2) - dy; setOffset((int) smoothTransform.getDestValue(TRANS_X) + dx2, (int) smoothTransform.getDestValue(TRANS_Y) + dy2, true); } } // keep trace of size plus lost part lastSize.width = newSize.width + extX; lastSize.height = newSize.height + extY; } }); addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(this); } /** * Release some stuff */ void shutDown() { // stop timer and movers refreshTimer.stop(); zoomInfoTimer.stop(); rotationInfoTimer.stop(); refreshTimer.removeActionListener(this); zoomInfoTimer.removeActionListener(this); rotationInfoTimer.removeActionListener(this); zoomInfoAlphaMover.shutDown(); rotationInfoAlphaMover.shutDown(); } /** * Returns the internal {@link ImageCache} object. */ public ImageCache getImageCache() { return imageCache; } protected void updateDrag(boolean control, boolean shift) { if (!moving) return; final Point mousePos = getMousePos(); final Point delta = new Point(mousePos.x - startDragPosition.x, mousePos.y - startDragPosition.y); // shift action --> limit to one direction if (shift) { // X drag if (Math.abs(delta.x) > Math.abs(delta.y)) delta.y = 0; // Y drag else delta.x = 0; } translate(startOffset, delta, control); } protected void translate(Point startPos, Point delta, boolean control) { final Point2D.Double deltaD; // control button down if (control) // drag is scaled by current scales factor // deltaD = canvasToImageDelta(delta.x, delta.y, 1d / getScaleX(), 1d / getScaleY(), // getRotationZ()); deltaD = canvasToImageDelta(delta.x * 3, delta.y * 3, 1d, 1d, getRotationZ()); else // just get rid of rotation factor deltaD = canvasToImageDelta(delta.x, delta.y, 1d, 1d, getRotationZ()); // modify offset with smooth mover setOffset((int) Math.round(startPos.x + deltaD.x), (int) Math.round(startPos.y + deltaD.y), true); } protected void updateRot(boolean control, boolean shift) { if (!rotating) return; final Point mousePos = getMousePos(); // get canvas center final int canvasCenterX = getCanvasSizeX() / 2; final int canvasCenterY = getCanvasSizeY() / 2; // get last and current mouse position delta with center final int lastMouseDeltaPosX = startDragPosition.x - canvasCenterX; final int lastMouseDeltaPosY = startDragPosition.y - canvasCenterY; final int newMouseDeltaPosX = mousePos.x - canvasCenterX; final int newMouseDeltaPosY = mousePos.y - canvasCenterY; // get angle in radian between last and current mouse position // relative to image center double newAngle = Math.atan2(newMouseDeltaPosY, newMouseDeltaPosX); double lastAngle = Math.atan2(lastMouseDeltaPosY, lastMouseDeltaPosX); double angle = newAngle - lastAngle; // control button down --> rotation is enforced if (control) angle *= 3; final double destAngle; // shift action --> limit to 45� rotation if (shift) destAngle = Math.rint((startRotationZ + angle) * (8d / (2 * Math.PI))) * ((2 * Math.PI) / 8d); else destAngle = startRotationZ + angle; // modify rotation with smooth mover setRotation(destAngle, true); } /** * Internal canvas process on mouseClicked event.<br> * Return true if event should be consumed. */ boolean onMouseClicked(boolean consumed, int clickCount, boolean left, boolean right, boolean control) { if (!consumed) { // nothing yet } return false; } /** * Internal canvas process on mousePressed event.<br> * Return true if event should be consumed. */ boolean onMousePressed(boolean consumed, boolean left, boolean right, boolean control) { // not yet consumed if (!consumed) { final ToolRibbonTask toolTask = Icy.getMainInterface().getToolRibbon(); final Sequence seq = getSequence(); // left button press ? if (left) { // ROI tool selected --> ROI creation if ((toolTask != null) && toolTask.isROITool()) { // get the ROI plugin class name final String roiClassName = toolTask.getSelected(); // unselect tool before ROI creation unless // control modifier is used for multiple ROI creation if (!control) Icy.getMainInterface().setSelectedTool(null); // only if sequence still live if (seq != null) { // try to create ROI from current selected ROI tool final ROI roi = ROI.create(roiClassName, getMouseImagePos5D()); // roi created ? --> it becomes the selected ROI if (roi != null) { roi.setCreating(true); // attach to sequence (hacky method to avoid undoing ROI cutting) seq.addROI(roi, !roiClassName.equals(ROILineCutterPlugin.class.getName())); // then do exclusive selection seq.setSelectedROI(roi); } // consume event return true; } } // start area selection if (control) areaSelection = true; } // start drag mouse position startDragPosition = getMousePos(); // store canvas parameters startOffset = new Point(getOffsetX(), getOffsetY()); startRotationZ = getRotationZ(); // repaint refresh(); updateCursor(); // consume event to activate drag return true; } return false; } /** * Internal canvas process on mouseReleased event.<br> * Return true if event should be consumed. */ boolean onMouseReleased(boolean consumed, boolean left, boolean right, boolean control) { // area selection ? if (areaSelection) { final Sequence seq = getSequence(); if (seq != null) { final List<ROI> rois = seq.getROIs(); // we have some rois ? if (rois.size() > 0) { final Rectangle2D area = canvasToImage(getAreaSelection()); // 5D area final Rectangle5D area5d = new Rectangle5D.Double(area.getX(), area.getY(), getPositionZ(), getPositionT(), Double.NEGATIVE_INFINITY, area.getWidth(), area.getHeight(), 1d, 1d, Double.POSITIVE_INFINITY); seq.beginUpdate(); try { for (ROI roi : rois) roi.setSelected(roi.intersects(area5d)); } finally { seq.endUpdate(); } } } } // assume end dragging startDragPosition = null; moving = false; rotating = false; areaSelection = false; // repaint refresh(); updateCursor(); // consume event return true; } /** * Internal canvas process on mouseMove event.<br> * Always processed, no consume here. */ void onMousePositionChanged(Point pos) { handlingMouseMoveEvent = true; try { // update mouse position setMousePos(pos); } finally { handlingMouseMoveEvent = false; } } /** * Internal canvas process on mouseDragged event.<br> * Return true if event should be consumed. */ boolean onMouseDragged(boolean consumed, Point pos, boolean left, boolean right, boolean control, boolean shift) { if (!consumed) { // canvas get the drag event ? if (isDragging()) { // left mouse button action : translation if (left) { moving = true; if (rotating) { rotating = false; // force repaint so the cross is no more visible canvasView.repaint(); } updateDrag(control, shift); } // right mouse button action : rotation else if (right) { moving = false; if (!rotating) { rotating = true; // force repaint so the cross is visible canvasView.repaint(); } updateRot(control, shift); } // dragging --> consume event return true; } // repaint area selection else if (areaSelection) repaint(); // no dragging --> no consume return false; } return false; } /** * Internal canvas process on mouseWheelMoved event.<br> * Return true if event should be consumed. */ boolean onMouseWheelMoved(boolean consumed, int wheelRotation, boolean left, boolean right, boolean control, boolean shift) { if (!consumed) { if (!isDragging()) { // as soon we manipulate the image with mouse, we want to be focused if (!viewer.hasFocus()) viewer.requestFocus(); double sx, sy; // adjust mouse wheel depending preference double wr = wheelRotation * CanvasPreferences.getMouseWheelSensitivity(); if (CanvasPreferences.getInvertMouseWheelAxis()) wr = -wr; sx = 1d + (wr / 100d); sy = 1d + (wr / 100d); // if (wr > 0d) // { // sx = 20d / 19d; // sy = 20d / 19d; // } // else // { // sx = 19d / 20d; // sy = 19d / 20d; // } // control button down --> fast zoom if (control) { sx *= sx; sy *= sy; } // reload current value if (curScaleX == -1) curScaleX = smoothTransform.getDestValue(SCALE_X); if (curScaleY == -1) curScaleY = smoothTransform.getDestValue(SCALE_Y); curScaleX = curScaleX * sx; curScaleY = curScaleY * sy; double newScaleX = curScaleX; double newScaleY = curScaleY; // shift key down --> adjust to closest "round" number if (shift) { newScaleX = MathUtil.closest(newScaleX, zoomRoundedFactors); newScaleY = MathUtil.closest(newScaleY, zoomRoundedFactors); } setScale(newScaleX, newScaleY, false, true); // consume event return true; } } // don't consume this event return false; } @Override public void mouseClicked(MouseEvent e) { // send mouse event to overlays first Canvas2D.this.mouseClick(e); // process if (onMouseClicked(e.isConsumed(), e.getClickCount(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e))) e.consume(); } @Override public void mousePressed(MouseEvent e) { // send mouse event to overlays first Canvas2D.this.mousePressed(e); // process if (onMousePressed(e.isConsumed(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e))) e.consume(); } @Override public void mouseReleased(MouseEvent e) { // send mouse event to overlays first Canvas2D.this.mouseReleased(e); // process if (onMouseReleased(e.isConsumed(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e))) e.consume(); } @Override public void mouseEntered(MouseEvent e) { hasMouseFocus = true; // send mouse event to overlays Canvas2D.this.mouseEntered(e); // and refresh refresh(); } @Override public void mouseExited(MouseEvent e) { hasMouseFocus = false; // send mouse event to overlays Canvas2D.this.mouseExited(e); // and refresh refresh(); } @Override public void mouseMoved(MouseEvent e) { // process first without consume (update mouse canvas position) onMousePositionChanged(e.getPoint()); // send mouse event to overlays after so mouse canvas position is ok Canvas2D.this.mouseMove(e); } @Override public void mouseDragged(MouseEvent e) { // process first without consume (update mouse canvas position) onMousePositionChanged(e.getPoint()); // send mouse event to overlays after so mouse canvas position is ok Canvas2D.this.mouseDrag(e); // process if (onMouseDragged(e.isConsumed(), e.getPoint(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e), EventUtil.isShiftDown(e))) e.consume(); } @Override public void mouseWheelMoved(MouseWheelEvent e) { // send mouse event to overlays Canvas2D.this.mouseWheelMoved(e); // process if (onMouseWheelMoved(e.isConsumed(), e.getWheelRotation(), EventUtil.isLeftMouseButton(e), EventUtil.isRightMouseButton(e), EventUtil.isControlDown(e), EventUtil.isShiftDown(e))) e.consume(); } public void keyPressed(KeyEvent e) { final boolean control = EventUtil.isControlDown(e); final boolean shift = EventUtil.isShiftDown(e); // just for modifiers key state change updateDrag(control, shift); updateRot(control, shift); } public void keyReleased(KeyEvent e) { final boolean control = EventUtil.isControlDown(e); final boolean shift = EventUtil.isShiftDown(e); // just for modifiers key state change updateDrag(control, shift); updateRot(control, shift); } /** * Draw specified image layer and others layers on specified {@link Graphics2D} object. */ void drawLayer(Graphics2D g, Sequence seq, Layer layer) { if (layer.isVisible()) { final float opacity = layer.getOpacity(); if (opacity != 1f) g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); else g.setComposite(AlphaComposite.SrcOver); layer.getOverlay().paint(g, seq, Canvas2D.this); } } /** * Draw specified image layer and others layers on specified {@link Graphics2D} object. */ void drawImageAndLayers(Graphics2D g, Layer imageLayer) { final Sequence seq = getSequence(); final Layer defaultImageLayer = getImageLayer(); // global layer visible switch for canvas if (isLayersVisible()) { final List<Layer> layers = getLayers(true); // draw them in inverse order to have first painter event at top for (int i = layers.size() - 1; i >= 0; i--) { final Layer layer = layers.get(i); // replace the default image layer by the specified one if (layer == defaultImageLayer) drawLayer(g, seq, imageLayer); else drawLayer(g, seq, layer); } } else // display image layer only drawLayer(g, seq, imageLayer); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); final int w = getCanvasSizeX(); final int h = getCanvasSizeY(); final int canvasCenterX = w / 2; final int canvasCenterY = h / 2; // background and layers { final Graphics2D g2 = (Graphics2D) g.create(); // background if (isBackgroundColorEnabled()) { g2.setBackground(getBackgroundColor()); g2.clearRect(0, 0, w, h); } // apply filtering if (CanvasPreferences.getFiltering() && ((getScaleX() < 4d) && (getScaleY() < 4d))) g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); else g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // apply transformation g2.transform(getTransform()); // draw image and layers drawImageAndLayers(g2, getImageLayer()); g2.dispose(); } // area selection if (areaSelection) { final Rectangle area = getAreaSelection(); final Graphics2D g2 = (Graphics2D) g.create(); g2.setStroke(new BasicStroke(1)); g2.setColor(Color.darkGray); g2.drawRect(area.x + 1, area.y + 1, area.width, area.height); g2.setColor(Color.lightGray); g2.drawRect(area.x, area.y, area.width, area.height); g2.dispose(); } // synchronized canvas ? display external cursor if (!hasMouseFocus) { final Graphics2D g2 = (Graphics2D) g.create(); final Point mousePos = getMousePos(); final int x = mousePos.x - (ICON_TARGET_SIZE / 2); final int y = mousePos.y - (ICON_TARGET_SIZE / 2); // display cursor at mouse pos g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); g2.drawImage(ICON_TARGET_LIGHT, x + 1, y + 1, null); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); g2.drawImage(ICON_TARGET_BLACK, x, y, null); g2.dispose(); } // display zoom info if (zoomInfoAlphaMover.getValue() > 0) { final Graphics2D g2 = (Graphics2D) g.create(); g2.setFont(font); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); drawTextBottomRight(g2, zoomMessage, (float) zoomInfoAlphaMover.getValue()); g2.dispose(); } // display rotation info if (rotationInfoAlphaMover.getValue() > 0) { final Graphics2D g2 = (Graphics2D) g.create(); g2.setFont(font); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); drawTextTopRight(g2, rotationMessage, (float) rotationInfoAlphaMover.getValue()); g2.dispose(); } // rotation helper if (rotating) { final Graphics2D g2 = (Graphics2D) g.create(); final BasicStroke blackStr = new BasicStroke(5); final BasicStroke greenStr = new BasicStroke(3); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setStroke(blackStr); g2.setColor(Color.black); g2.drawLine(canvasCenterX - 5, canvasCenterY - 5, canvasCenterX + 5, canvasCenterY + 5); g2.drawLine(canvasCenterX - 5, canvasCenterY + 5, canvasCenterX + 5, canvasCenterY - 5); g2.setStroke(greenStr); g2.setColor(Color.green); g2.drawLine(canvasCenterX - 5, canvasCenterY - 5, canvasCenterX + 5, canvasCenterY + 5); g2.drawLine(canvasCenterX - 5, canvasCenterY + 5, canvasCenterX + 5, canvasCenterY - 5); g2.dispose(); } // image or layers changed during repaint --> refresh again if (!isCacheValid()) refresh(); // cache is being rebuild --> refresh to show progression else if (imageCache.isProcessing()) refreshLater(100); // repaint minimap to reflect change (simplest way to refresh minimap) canvasMap.repaint(); } public void drawTextBottomRight(Graphics2D g, String text, float alpha) { final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); final int w = (int) rect.getWidth(); final int h = (int) rect.getHeight(); final int x = getWidth() - (w + 8 + 2); final int y = getHeight() - (h + 8 + 2); g.setColor(Color.gray); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); g.setColor(Color.white); g.drawString(text, x + 4, y + 2 + h); } public void drawTextTopRight(Graphics2D g, String text, float alpha) { final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); final int w = (int) rect.getWidth(); final int h = (int) rect.getHeight(); final int x = getWidth() - (w + 8 + 2); final int y = 2; g.setColor(Color.gray); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); g.setColor(Color.white); g.drawString(text, x + 4, y + 2 + h); } public void drawTextCenter(Graphics2D g, String text, float alpha) { final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text); final int w = (int) rect.getWidth(); final int h = (int) rect.getHeight(); final int x = (getWidth() - (w + 8 + 2)) / 2; final int y = (getHeight() - (h + 8 + 2)) / 2; g.setColor(Color.gray); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); g.fillRoundRect(x, y, w + 8, h + 8, 8, 8); g.setColor(Color.white); g.drawString(text, x + 4, y + 2 + h); } /** * Update mouse cursor */ protected void updateCursor() { // final Cursor cursor = getCursor(); // // // save previous cursor if different from HAND // if (cursor.getType() != Cursor.HAND_CURSOR) // previousCursor = cursor; // if (isDragging()) { GuiUtil.setCursor(this, Cursor.HAND_CURSOR); return; } if (areaSelection) { GuiUtil.setCursor(this, Cursor.CROSSHAIR_CURSOR); return; } final Sequence seq = getSequence(); if (seq != null) { final ROI overlappedRoi = seq.getFocusedROI(); // overlapping an ROI ? if (overlappedRoi != null) { final Layer layer = getLayer(overlappedRoi); if ((layer != null) && layer.isVisible()) { GuiUtil.setCursor(this, Cursor.HAND_CURSOR); return; } } final List<ROI> selectedRois = seq.getSelectedROIs(); // search if we are overriding ROI control points for (ROI selectedRoi : selectedRois) { final Layer layer = getLayer(selectedRoi); if ((layer != null) && layer.isVisible() && selectedRoi.hasSelectedPoint()) { GuiUtil.setCursor(this, Cursor.HAND_CURSOR); return; } } } // setCursor(previousCursor); GuiUtil.setCursor(this, Cursor.DEFAULT_CURSOR); } public void refresh() { imageCache.refresh(); } /** * Refresh in sometime */ public void refreshLater(int milli) { refreshTimer.setInitialDelay(milli); refreshTimer.start(); } /** * Display zoom message for the specified amount of time (in ms) */ public void setZoomMessage(String value, int delay) { zoomMessage = value; if (StringUtil.isEmpty(value)) { zoomInfoTimer.stop(); zoomInfoAlphaMover.setValue(0d); } else { zoomInfoAlphaMover.setValue(0.8d); zoomInfoTimer.setInitialDelay(delay); zoomInfoTimer.restart(); } } /** * Display rotation message for the specified amount of time (in ms) */ public void setRotationMessage(String value, int delay) { rotationMessage = value; if (StringUtil.isEmpty(value)) { rotationInfoTimer.stop(); rotationInfoAlphaMover.setValue(0d); } else { rotationInfoAlphaMover.setValue(0.8d); rotationInfoTimer.setInitialDelay(delay); rotationInfoTimer.restart(); } } public void imageChanged() { imageCache.invalidCache(); } public void layersChanged() { } public boolean isDragging() { return !areaSelection && (startDragPosition != null); } public boolean isCacheValid() { return imageCache.isValid(); } /** * Returns the current Rectangle region of the area selection.<br> * It returns <code>null</code> if we are not in area selection mode */ public Rectangle getAreaSelection() { if (!areaSelection) return null; final int x, y; final int w, h; final Point mp = getMousePos(); if (mp.x > startDragPosition.x) { x = startDragPosition.x; w = mp.x - x; } else { x = mp.x; w = startDragPosition.x - x; } if (mp.y > startDragPosition.y) { y = startDragPosition.y; h = mp.y - y; } else { y = mp.y; h = startDragPosition.y - y; } return new Rectangle(x, y, w, h); } @Override public void actionPerformed(ActionEvent e) { final Object source = e.getSource(); if (source == refreshTimer) refresh(); else if (source == zoomInfoTimer) zoomInfoAlphaMover.moveTo(0); else if (source == rotationInfoTimer) rotationInfoAlphaMover.moveTo(0); } } /** * * index 0 : translation X (int) index 1 : translation Y (int) index 2 : * scale X (double) index 3 : scale Y (double) index 4 : rotation angle * (double) * * @author Stephane */ static class Canvas2DSmoothMover extends MultiSmoothMover { public Canvas2DSmoothMover(int size, SmoothMoveType type) { super(size, type); } public Canvas2DSmoothMover(int size) { super(size); } @Override public void moveTo(int index, double value) { final double v; // format value for radian 0..2PI range if (index == ROT) v = MathUtil.formatRadianAngle(value); else v = value; if (destValues[index] != v) { destValues[index] = v; // start movement start(index, System.currentTimeMillis()); } } @Override public void moveTo(double[] values) { final int maxInd = Math.min(values.length, destValues.length); // first we check we have at least one value which had changed boolean changed = false; for (int index = 0; index < maxInd; index++) { final double value; // format value for radian 0..2PI range if (index == ROT) value = MathUtil.formatRadianAngle(values[index]); else value = values[index]; if (destValues[index] != value) { changed = true; break; } } // value changed ? if (changed) { // better synchronization for multiple changes final long time = System.currentTimeMillis(); for (int index = 0; index < maxInd; index++) { final double value; // format value for radian 0..2PI range if (index == ROT) value = MathUtil.formatRadianAngle(values[index]); else value = values[index]; destValues[index] = value; // start movement start(index, time); } } } @Override public void setValue(int index, double value) { final double v; // format value for radian 0..2PI range if (index == ROT) v = MathUtil.formatRadianAngle(value); else v = value; // stop current movement stop(index); // directly set value destValues[index] = v; setCurrentValue(index, v, 100); } @Override public void setValues(double[] values) { final int maxInd = Math.min(values.length, destValues.length); for (int index = 0; index < maxInd; index++) { final double value; // format value for radian 0..2PI range if (index == ROT) value = MathUtil.formatRadianAngle(values[index]); else value = values[index]; // stop current movement stop(index); // directly set value destValues[index] = value; setCurrentValue(index, value, 100); } } @Override protected void setCurrentValue(int index, double value, int pourcent) { final double v; // format value for radian 0..2PI range if (index == ROT) v = MathUtil.formatRadianAngle(value); else v = value; if (currentValues[index] != v) { currentValues[index] = v; // notify value changed changed(index, v, pourcent); } } @Override protected void start(int index, long time) { final double current = currentValues[index]; final double dest; if (index == ROT) { double d = destValues[index]; // choose shorter path if (Math.abs(d - current) > Math.PI) { if (d > Math.PI) dest = d - (Math.PI * 2); else dest = d + (Math.PI * 2); } else dest = d; } else dest = destValues[index]; // number of step to reach final value final int size = Math.max(moveTime / getUpdateDelay(), 1); // calculate interpolation switch (type) { case NONE: stepValues[index] = new double[2]; stepValues[index][0] = current; stepValues[index][1] = dest; break; case LINEAR: stepValues[index] = Interpolator.doLinearInterpolation(current, dest, size); break; case LOG: stepValues[index] = Interpolator.doLogInterpolation(current, dest, size); break; case EXP: stepValues[index] = Interpolator.doExpInterpolation(current, dest, size); break; } // notify and start if (!isMoving(index)) { moveStarted(index, time); moving[index] = true; } else moveModified(index, time); } } /** * pref ID */ static final String PREF_CANVAS2D_ID = "Canvas2D"; static final String ID_FIT_CANVAS = "fitCanvas"; static final String ID_BG_COLOR_ENABLED = "bgColorEnabled"; static final String ID_BG_COLOR = "bgColor"; final static int TRANS_X = 0; final static int TRANS_Y = 1; final static int SCALE_X = 2; final static int SCALE_Y = 3; final static int ROT = 4; /** * view where we draw */ final CanvasView canvasView; /** * minimap in canvas panel */ final CanvasMap canvasMap; /** * GUI & setting */ IcyToggleButton zoomFitCanvasButton; Color bgColor; /** * preferences */ final XMLPreferences preferences; /** * The smoothTransform object contains all transform informations<br> */ final Canvas2DSmoothMover smoothTransform; // internal String textInfos; Dimension previousImageSize; boolean modifyingZoom; boolean modifyingRotation; public Canvas2D(Viewer viewer) { super(viewer); // all channel visible at once posC = -1; // view panel canvasView = new CanvasView(); // mini map canvasMap = new CanvasMap(); // variables initialization preferences = CanvasPreferences.getPreferences().node(PREF_CANVAS2D_ID); // init transform (5 values, log transition type) smoothTransform = new Canvas2DSmoothMover(5, SmoothMoveType.LOG); // initials transform values smoothTransform.setValues(new double[] {0d, 0d, 1d, 1d, 0d}); textInfos = null; modifyingZoom = false; modifyingRotation = false; previousImageSize = new Dimension(getImageSizeX(), getImageSizeY()); smoothTransform.addListener(new MultiSmoothMoverAdapter() { @Override public void valueChanged(MultiSmoothMover source, int index, double newValue, int pourcent) { // notify canvas transformation has changed switch (index) { case TRANS_X: offsetChanged(DimensionId.X); break; case TRANS_Y: offsetChanged(DimensionId.Y); break; case SCALE_X: scaleChanged(DimensionId.X); break; case SCALE_Y: scaleChanged(DimensionId.Y); break; case ROT: rotationChanged(DimensionId.Z); break; } } @Override public void moveEnded(MultiSmoothMover source, int index, double value) { // scale move ended, we can fix notify canvas transformation has changed switch (index) { case SCALE_X: canvasView.curScaleX = -1; break; case SCALE_Y: canvasView.curScaleY = -1; } } }); // want fast transition smoothTransform.setMoveTime(400); // and very smooth refresh if possible smoothTransform.setUpdateDelay(20); // build inspector canvas panel & GUI stuff buildSettingGUI(); // set view in center add(canvasView, BorderLayout.CENTER); // mouse infos panel setting: we want to see values for X/Y only (2D view) mouseInfPanel.setInfoXVisible(true); mouseInfPanel.setInfoYVisible(true); // Z and T values are already visible in Z/T navigator bar mouseInfPanel.setInfoZVisible(false); mouseInfPanel.setInfoTVisible(false); // no C navigation with this canvas (all channels visible) mouseInfPanel.setInfoCVisible(false); // data and color information visible mouseInfPanel.setInfoDataVisible(true); mouseInfPanel.setInfoColorVisible(true); updateZNav(); updateTNav(); final ToolRibbonTask trt = Icy.getMainInterface().getToolRibbon(); if (trt != null) trt.addListener(this); } @Override public void shutDown() { super.shutDown(); canvasView.shutDown(); // shutdown mover object (else internal timer keep a reference to Canvas2D) smoothTransform.shutDown(); final ToolRibbonTask trt = Icy.getMainInterface().getToolRibbon(); if (trt != null) trt.removeListener(this); } @Override protected Overlay createImageOverlay() { return new Canvas2DImageOverlay(); } public Canvas2DSettingPanel getCanvasSettingPanel() { return (Canvas2DSettingPanel) panel; } /** * Build canvas panel for inspector */ private void buildSettingGUI() { // canvas setting panel (for inspector) panel = new Canvas2DSettingPanel(this); // add the map to it panel.add(canvasMap, BorderLayout.CENTER); // fit canvas toggle zoomFitCanvasButton = new IcyToggleButton(new IcyIcon(ICON_FIT_CANVAS)); zoomFitCanvasButton.setSelected(preferences.getBoolean(ID_FIT_CANVAS, false)); zoomFitCanvasButton.setFocusable(false); zoomFitCanvasButton.setToolTipText("Keep image fitting to window size"); zoomFitCanvasButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final boolean selected = zoomFitCanvasButton.isSelected(); preferences.putBoolean(ID_FIT_CANVAS, selected); // fit if enabled if (selected) fitImageToCanvas(true); } }); } @Override public Component getViewComponent() { return canvasView; } /** * Return the {@link CanvasView} component of Canvas2D. */ public CanvasView getCanvasView() { return canvasView; } /** * Return the {@link CanvasMap} component of Canvas2D. */ public CanvasMap getCanvasMap() { return canvasMap; } @Override public void customizeToolbar(JToolBar toolBar) { toolBar.addSeparator(); toolBar.add(zoomFitCanvasButton); // toolBar.addSeparator(); // toolBar.add(zoomFitImageButton); // toolBar.add(centerImageButton); } @Override public void fitImageToCanvas() { fitImageToCanvas(false); } /** * Change zoom so image fit in canvas view dimension */ public void fitImageToCanvas(boolean smooth) { // search best ratio final Point2D.Double s = getFitImageToCanvasScale(); if (s != null) { final double scale = Math.min(s.x, s.y); // set mouse position on image center centerMouseOnImage(); // apply scale setScale(scale, scale, true, smooth); } } @Override public void fitCanvasToImage() { // center image first centerImage(); super.fitCanvasToImage(); } @Override public void centerOnImage(double x, double y) { // get point on canvas final Point pt = imageToCanvas(x, y); final int canvasCenterX = getCanvasSizeX() / 2; final int canvasCenterY = getCanvasSizeY() / 2; final Point2D.Double newTrans = canvasToImageDelta(canvasCenterX - pt.x, canvasCenterY - pt.y, 1d, 1d, getRotationZ()); setOffset((int) (smoothTransform.getDestValue(TRANS_X) + Math.round(newTrans.x)), (int) (smoothTransform.getDestValue(TRANS_Y) + Math.round(newTrans.y)), false); } /** * Set mouse position on image center */ protected void centerMouseOnImage() { setMouseImagePos(getImageSizeX() / 2, getImageSizeY() / 2); } /** * Set mouse position on current view center */ protected void centerMouseOnView() { setMousePos(getCanvasSizeX() >> 1, getCanvasSizeY() >> 1); } @Override public void centerOn(Rectangle region) { final Rectangle2D imageRectMax = Rectangle2DUtil .getScaledRectangle(new Rectangle(getImageSizeX(), getImageSizeY()), 1.5d, true); Rectangle2D adjusted = Rectangle2DUtil.getScaledRectangle(region, 2d, true); // get undersize double wu = Math.max(0, 100d - adjusted.getWidth()); double hu = Math.max(0, 100d - adjusted.getHeight()); // enlarge a bit to have at least a 100x100 rectangle if ((wu > 0) || (hu > 0)) ShapeUtil.enlarge(adjusted, wu, hu, true); // get overflow on original image size double wo = Math.max(0, adjusted.getWidth() - imageRectMax.getWidth()); double ho = Math.max(0, adjusted.getHeight() - imageRectMax.getHeight()); // reduce a bit to clip on max image size if ((wo > 0) || (ho > 0)) ShapeUtil.enlarge(adjusted, -wo, -ho, true); final Rectangle viewRect = new Rectangle(getViewComponent().getSize()); // calculate new scale factors final double scaleX = viewRect.width / adjusted.getWidth(); final double scaleY = viewRect.height / adjusted.getHeight(); // get point on canvas final int offX; final int offY; final double newScale; if (scaleX < scaleY) { newScale = scaleX; // use scale X, adapt offset Y offX = (int) (adjusted.getX() * newScale); offY = (int) ((adjusted.getY() * newScale) - ((viewRect.height - (adjusted.getHeight() * newScale)) / 2d)); } else { newScale = scaleY; // use scale Y, adapt offset X offX = (int) ((adjusted.getX() * newScale) - ((viewRect.width - (adjusted.getWidth() * newScale)) / 2d)); offY = (int) (adjusted.getY() * newScale); } // apply new position and scaling setTransform(-offX, -offY, newScale, newScale, smoothTransform.getDestValue(ROT), true); } /** * Set transform */ protected void setTransform(int tx, int ty, double sx, double sy, double rot, boolean smooth) { final double[] values = new double[] {tx, ty, sx, sy, rot}; // modify all at once for synchronized change events if (smooth) smoothTransform.moveTo(values); else smoothTransform.setValues(values); } /** * Set offset X and Y.<br> * * @param smooth * use smooth transition */ public void setOffset(int x, int y, boolean smooth) { final int adjX = Math.min(getMaxOffsetX(), Math.max(getMinOffsetX(), x)); final int adjY = Math.min(getMaxOffsetY(), Math.max(getMinOffsetY(), y)); setTransform(adjX, adjY, smoothTransform.getDestValue(SCALE_X), smoothTransform.getDestValue(SCALE_Y), smoothTransform.getDestValue(ROT), smooth); } /** * Set zoom factor (this use the smart zoom position and smooth transition). * * @param center * if true then zoom is centered to current view else zoom is * centered using current mouse position * @param smooth * use smooth transition */ public void setScale(double factor, boolean center, boolean smooth) { // first we center mouse position if requested if (center) centerMouseOnImage(); setScale(factor, factor, true, smooth); } /** * Set zoom X and Y factor.<br> * This use the smart zoom position and smooth transition. * * @param mouseCentered * if true the current mouse image position will becomes the * center of viewport else the current mouse image position will * keep its place. * @param smooth * use smooth transition */ public void setScale(double x, double y, boolean mouseCentered, boolean smooth) { final Sequence seq = getSequence(); // there is no way of changing scale if no sequence if (seq == null) return; // get destination rot final double rot = smoothTransform.getDestValue(ROT); // limit min and max zoom ratio final double newScaleX = Math.max(0.01d, Math.min(100d, x)); final double newScaleY = Math.max(0.01d, Math.min(100d, y)); // get new mouse position on canvas pixel final Point newMouseCanvasPos = imageToCanvas(mouseImagePos.x, mouseImagePos.y, 0, 0, newScaleX, newScaleY, rot); // new image size final int newImgSizeX = (int) Math.ceil(getImageSizeX() * newScaleX); final int newImgSizeY = (int) Math.ceil(getImageSizeY() * newScaleY); // canvas center final int canvasCenterX = getCanvasSizeX() / 2; final int canvasCenterY = getCanvasSizeY() / 2; final Point2D.Double newTrans; if (mouseCentered) { // we want the mouse image point to becomes the canvas center (take rotation in account) newTrans = canvasToImageDelta(canvasCenterX - newMouseCanvasPos.x, canvasCenterY - newMouseCanvasPos.y, 1d, 1d, rot); } else { final Point mousePos = getMousePos(); // we want the mouse image point to keep its place (take rotation in account) newTrans = canvasToImageDelta(mousePos.x - newMouseCanvasPos.x, mousePos.y - newMouseCanvasPos.y, 1d, 1d, rot); } // limit translation to min / max offset final int newTransX = Math.min(canvasCenterX, Math.max(canvasCenterX - newImgSizeX, (int) Math.round(newTrans.x))); final int newTransY = Math.min(canvasCenterY, Math.max(canvasCenterY - newImgSizeY, (int) Math.round(newTrans.y))); setTransform(newTransX, newTransY, newScaleX, newScaleY, rot, smooth); } /** * Set zoom X and Y factor.<br> * This is direct affectation method without position modification. * * @param smooth * use smooth transition */ public void setScale(double x, double y, boolean smooth) { setTransform((int) smoothTransform.getDestValue(TRANS_X), (int) smoothTransform.getDestValue(TRANS_Y), x, y, smoothTransform.getDestValue(ROT), smooth); } /** * Set zoom factor.<br> * Only here for backward compatibility with ICY4IJ.<br> * Zoom is center on image. * * @deprecated use setScale(...) instead */ @Deprecated public void setZoom(float zoom) { // set mouse position on image center centerMouseOnImage(); // then apply zoom setScale(zoom, zoom, true, false); } /** * Get destination image size X in canvas pixel coordinate */ public int getDestImageCanvasSizeX() { return (int) Math.ceil(getImageSizeX() * smoothTransform.getDestValue(SCALE_X)); } /** * Get destination image size Y in canvas pixel coordinate */ public int getDestImageCanvasSizeY() { return (int) Math.ceil(getImageSizeY() * smoothTransform.getDestValue(SCALE_Y)); } void backgroundColorEnabledChanged() { // save to preference preferences.putBoolean(ID_BG_COLOR_ENABLED, isBackgroundColorEnabled()); // and refresh view canvasView.refresh(); } void backgroundColorChanged() { // save to preference preferences.putInt(ID_BG_COLOR, getBackgroundColor().getRGB()); // and refresh view canvasView.refresh(); } /** * Returns the background color enabled state */ public boolean isBackgroundColorEnabled() { return getCanvasSettingPanel().isBackgroundColorEnabled(); } /** * Sets the background color enabled state */ public void setBackgroundColorEnabled(boolean value) { getCanvasSettingPanel().setBackgroundColorEnabled(value); } /** * Returns the background color */ public Color getBackgroundColor() { return getCanvasSettingPanel().getBackgroundColor(); } /** * Sets the background color */ public void setBackgroundColor(Color color) { getCanvasSettingPanel().setBackgroundColor(color); } /** * @return the automatic 'fit to canvas' state */ public boolean getFitToCanvas() { return zoomFitCanvasButton.isSelected(); } /** * Sets the automatic 'fit to canvas' state */ public void setFitToCanvas(boolean value) { zoomFitCanvasButton.setSelected(value); } @Override public boolean isSynchronizationSupported() { return true; } protected int getMinOffsetX() { return (getCanvasSizeX() / 2) - getDestImageCanvasSizeX(); } protected int getMaxOffsetX() { return (getCanvasSizeX() / 2); } protected int getMinOffsetY() { return (getCanvasSizeY() / 2) - getDestImageCanvasSizeY(); } protected int getMaxOffsetY() { return (getCanvasSizeY() / 2); } @Override public int getOffsetX() { // can be called before constructor ended if (smoothTransform == null) return 0; return (int) smoothTransform.getValue(TRANS_X); } @Override public int getOffsetY() { // can be called before constructor ended if (smoothTransform == null) return 0; return (int) smoothTransform.getValue(TRANS_Y); } @Override public double getScaleX() { // can be called before constructor ended if (smoothTransform == null) return 0d; return smoothTransform.getValue(SCALE_X); } @Override public double getScaleY() { // can be called before constructor ended if (smoothTransform == null) return 0d; return smoothTransform.getValue(SCALE_Y); } @Override public double getRotationZ() { // can be called before constructor ended if (smoothTransform == null) return 0d; return smoothTransform.getValue(ROT); } /** * Only here for backward compatibility with ICY4IJ plugin. * * @deprecated use getScaleX() or getScaleY() instead */ @Deprecated public double getZoomFactor() { return getScaleX(); } /** * We want angle to be in [0..2*PI] */ public double getRotation() { return MathUtil.formatRadianAngle(getRotationZ()); } @Override protected void setPositionCInternal(int c) { // not supported in this canvas, C should stay at -1 } @Override protected void setOffsetXInternal(int value) { // this will automatically call the offsetChanged() event smoothTransform.setValue(TRANS_X, Math.min(getMaxOffsetX(), Math.max(getMinOffsetX(), value))); } @Override protected void setOffsetYInternal(int value) { // this will automatically call the offsetChanged() event smoothTransform.setValue(TRANS_Y, Math.min(getMaxOffsetY(), Math.max(getMinOffsetY(), value))); } @Override protected void setScaleXInternal(double value) { // this will automatically call the scaledChanged() event smoothTransform.setValue(SCALE_X, value); canvasView.curScaleX = value; } @Override protected void setScaleYInternal(double value) { // this will automatically call the scaledChanged() event smoothTransform.setValue(SCALE_Y, value); canvasView.curScaleY = value; } @Override protected void setRotationZInternal(double value) { // this will automatically call the rotationChanged() event smoothTransform.setValue(ROT, value); } /** * Set rotation angle (radian).<br> * * @param smooth * use smooth transition */ public void setRotation(double value, boolean smooth) { setTransform((int) smoothTransform.getDestValue(TRANS_X), (int) smoothTransform.getDestValue(TRANS_Y), smoothTransform.getDestValue(SCALE_X), smoothTransform.getDestValue(SCALE_Y), value, smooth); } @Override public void keyPressed(KeyEvent e) { // send to overlays super.keyPressed(e); if (!e.isConsumed()) { switch (e.getKeyCode()) { case KeyEvent.VK_R: // reset zoom and rotation setRotation(0, false); fitImageToCanvas(true); // also reset LUT if (EventUtil.isShiftDown(e, true)) { final Sequence sequence = getSequence(); final Viewer viewer = getViewer(); if ((viewer != null) && (sequence != null)) viewer.setLut(sequence.createCompatibleLUT()); } e.consume(); break; case KeyEvent.VK_LEFT: if (EventUtil.isMenuControlDown(e, true)) setPositionT(Math.max(getPositionT() - 5, 0)); else setPositionT(Math.max(getPositionT() - 1, 0)); e.consume(); break; case KeyEvent.VK_RIGHT: if (EventUtil.isMenuControlDown(e, true)) setPositionT(getPositionT() + 5); else setPositionT(getPositionT() + 1); e.consume(); break; case KeyEvent.VK_UP: if (EventUtil.isMenuControlDown(e, true)) setPositionZ(getPositionZ() + 5); else setPositionZ(getPositionZ() + 1); e.consume(); break; case KeyEvent.VK_DOWN: if (EventUtil.isMenuControlDown(e, true)) setPositionZ(Math.max(getPositionZ() - 5, 0)); else setPositionZ(Math.max(getPositionZ() - 1, 0)); e.consume(); break; case KeyEvent.VK_NUMPAD2: if (!canvasView.moving) { final Point startPos = new Point(getOffsetX(), getOffsetY()); final Point delta = new Point(0, -getCanvasSizeY() / 4); canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); e.consume(); } break; case KeyEvent.VK_NUMPAD4: if (!canvasView.moving) { final Point startPos = new Point(getOffsetX(), getOffsetY()); final Point delta = new Point(getCanvasSizeX() / 4, 0); canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); e.consume(); } break; case KeyEvent.VK_NUMPAD6: if (!canvasView.moving) { final Point startPos = new Point(getOffsetX(), getOffsetY()); final Point delta = new Point(-getCanvasSizeX() / 4, 0); canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); e.consume(); } break; case KeyEvent.VK_NUMPAD8: if (!canvasView.moving) { final Point startPos = new Point(getOffsetX(), getOffsetY()); final Point delta = new Point(0, getCanvasSizeY() / 4); canvasView.translate(startPos, delta, EventUtil.isControlDown(e)); e.consume(); } break; } } // forward to view canvasView.keyPressed(e); // forward to map canvasMap.keyPressed(e); } @Override public void keyReleased(KeyEvent e) { // send to overlays super.keyReleased(e); // forward to view canvasView.keyReleased(e); // forward to map canvasMap.keyReleased(e); } @Override public void refresh() { canvasView.imageChanged(); canvasView.layersChanged(); canvasView.refresh(); } /** * Return an ARGB BufferedImage form of the image located at position [T, Z, C].<br> * If the 'out' image is not compatible with wanted image, a new image is returned. */ public BufferedImage getARGBImage(int t, int z, int c, BufferedImage out) { final IcyBufferedImage img = Canvas2D.this.getImage(t, z, c); if (img != null) { final BufferedImage result; if ((out != null) && ImageUtil.sameSize(img, out)) result = out; else result = new BufferedImage(img.getSizeX(), img.getSizeY(), BufferedImage.TYPE_INT_ARGB); return IcyBufferedImageUtil.toBufferedImage(img, result, getLut()); } return null; } public BufferedImage getARGBImage(int t, int z, int c, BufferedImage out, boolean adaptSize) { final IcyBufferedImage img = Canvas2D.this.getImage(t, z, c); if (img != null) return IcyBufferedImageUtil.toBufferedImage(img, out, getLut()); return null; } @Override public BufferedImage getRenderedImage(int t, int z, int c, boolean cv) { final Sequence seq = getSequence(); if (seq == null) return null; // save position final int prevT = getPositionT(); final int prevZ = getPositionZ(); final boolean dl = isLayersVisible(); if (dl) { // set wanted position (needed for correct overlay drawing) // we have to fire events else some stuff can miss the change setPositionT(t); setPositionZ(z); } try { final Dimension size; if (cv) size = getCanvasSize(); else size = seq.getDimension2D(); // get result image and graphics object final BufferedImage result = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); final Graphics2D g = result.createGraphics(); // set default clip region g.setClip(0, 0, size.width, size.height); if (cv) { // apply filtering if (CanvasPreferences.getFiltering() && ((getScaleX() < 4d) && (getScaleY() < 4d))) g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); else g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // apply transformation g.transform(getTransform()); } else { // apply filtering if (CanvasPreferences.getFiltering()) g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); else g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } // create temporary image, overlay and layer so we can choose the correct image // (not optimal for memory and performance) final BufferedImage img = getARGBImage(t, z, c, null); final Overlay imgOverlay = new ImageOverlay("Image", img); final Layer imgLayer = new Layer(imgOverlay); // keep visibility and priority information imgLayer.setVisible(getImageLayer().isVisible()); imgLayer.setPriority(getImageLayer().getPriority()); // draw image and layers canvasView.drawImageAndLayers(g, imgLayer); g.dispose(); return result; } finally { if (dl) { // restore position setPositionT(prevT); setPositionZ(prevZ); } } } /** * @deprecated Use <code>getRenderedImage(t, z, -1, true)</code> instead. */ @Deprecated public BufferedImage getRenderedImage(int t, int z) { return getRenderedImage(t, z, -1, true); } /** * @deprecated Use <code>getRenderedImage(t, z, -1, canvasView)</code> instead. */ @Deprecated public BufferedImage getRenderedImage(int t, int z, boolean canvasView) { return getRenderedImage(t, z, -1, canvasView); } /** * Synchronize views of specified list of canvas */ @Override protected void synchronizeCanvas(List<IcyCanvas> canvasList, IcyCanvasEvent event, boolean processAll) { final IcyCanvasEventType type = event.getType(); final DimensionId dim = event.getDim(); // position synchronization if (isSynchOnSlice()) { if (processAll || (type == IcyCanvasEventType.POSITION_CHANGED)) { // no information about dimension --> set all if (processAll || (dim == DimensionId.NULL)) { // only support T and Z positioning final int z = getPositionZ(); final int t = getPositionT(); for (IcyCanvas cnv : canvasList) { if (z != -1) cnv.setPositionZ(z); if (t != -1) cnv.setPositionT(t); } } else { for (IcyCanvas cnv : canvasList) { final int pos = getPosition(dim); if (pos != -1) cnv.setPosition(dim, pos); } } } } // view synchronization if (isSynchOnView()) { if (processAll || (type == IcyCanvasEventType.SCALE_CHANGED)) { // no information about dimension --> set all if (processAll || (dim == DimensionId.NULL)) { final double sX = getScaleX(); final double sY = getScaleY(); for (IcyCanvas cnv : canvasList) ((Canvas2D) cnv).setScale(sX, sY, false); } else { for (IcyCanvas cnv : canvasList) cnv.setScale(dim, getScale(dim)); } } if (processAll || (type == IcyCanvasEventType.ROTATION_CHANGED)) { // no information about dimension --> set all if (processAll || (dim == DimensionId.NULL)) { final double rot = getRotationZ(); for (IcyCanvas cnv : canvasList) ((Canvas2D) cnv).setRotation(rot, false); } else { for (IcyCanvas cnv : canvasList) cnv.setRotation(dim, getRotation(dim)); } } // process offset in last as it can be limited depending destination scale value if (processAll || (type == IcyCanvasEventType.OFFSET_CHANGED)) { // no information about dimension --> set all if (processAll || (dim == DimensionId.NULL)) { final int offX = getOffsetX(); final int offY = getOffsetY(); for (IcyCanvas cnv : canvasList) ((Canvas2D) cnv).setOffset(offX, offY, false); } else { for (IcyCanvas cnv : canvasList) cnv.setOffset(dim, getOffset(dim)); } } } // cursor synchronization if (isSynchOnCursor()) { // mouse synchronization if (processAll || (type == IcyCanvasEventType.MOUSE_IMAGE_POSITION_CHANGED)) { // no information about dimension --> set all if (processAll || (dim == DimensionId.NULL)) { final double mouseImagePosX = getMouseImagePosX(); final double mouseImagePosY = getMouseImagePosY(); for (IcyCanvas cnv : canvasList) ((Canvas2D) cnv).setMouseImagePos(mouseImagePosX, mouseImagePosY); } else { for (IcyCanvas cnv : canvasList) cnv.setMouseImagePos(dim, getMouseImagePos(dim)); } } } } @Override public void changed(IcyCanvasEvent event) { super.changed(event); // not yet initialized if (canvasView == null) return; final IcyCanvasEventType type = event.getType(); switch (type) { case POSITION_CHANGED: // image has changed canvasView.imageChanged(); case OFFSET_CHANGED: case SCALE_CHANGED: case ROTATION_CHANGED: // update mouse image position from mouse canvas position setMouseImagePos(canvasToImage(getMousePos())); // display info message if (type == IcyCanvasEventType.SCALE_CHANGED) { final String zoomInfo = Integer.toString((int) (getScaleX() * 100)); ThreadUtil.invokeLater(new Runnable() { @Override public void run() { // in panel modifyingZoom = true; try { getCanvasSettingPanel().updateZoomState(zoomInfo); } finally { modifyingZoom = false; } } }); // and in canvas canvasView.setZoomMessage("Zoom : " + zoomInfo + " %", 500); } else if (type == IcyCanvasEventType.ROTATION_CHANGED) { final String rotInfo = Integer.toString((int) Math.round(getRotation() * 180d / Math.PI)); ThreadUtil.invokeLater(new Runnable() { @Override public void run() { // in panel modifyingRotation = true; try { getCanvasSettingPanel().updateRotationState(rotInfo); } finally { modifyingRotation = false; } } }); // and in canvas canvasView.setRotationMessage("Rotation : " + rotInfo + " �", 500); } // refresh canvas canvasView.refresh(); break; case MOUSE_IMAGE_POSITION_CHANGED: // mouse position changed outside mouse move event ? if (!canvasView.handlingMouseMoveEvent && !canvasView.isDragging() && !isSynchSlave()) { // mouse position in canvas final Point mousePos = getMousePos(); final Point mouseAbsolutePos = getMousePos(); // absolute mouse position SwingUtilities.convertPointToScreen(mouseAbsolutePos, canvasView); // simulate a mouse move event so overlays can handle position change final MouseEvent mouseEvent = new MouseEvent(this, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, mousePos.x, mousePos.y, mouseAbsolutePos.x, mouseAbsolutePos.y, 0, false, 0); // send mouse move event to overlays mouseMove(mouseEvent, getMouseImagePos5D()); } // update mouse cursor canvasView.updateCursor(); // needed to refresh custom cursor if (!canvasView.hasMouseFocus) canvasView.refresh(); break; } } @Override protected void lutChanged(int component) { super.lutChanged(component); // refresh image if (canvasView != null) { canvasView.imageChanged(); canvasView.refresh(); } } @Override protected void layerChanged(CanvasLayerEvent event) { super.layerChanged(event); // layer visibility property modified ? if ((event.getType() == LayersEventType.CHANGED) && Layer.isPaintProperty(event.getProperty())) { // layer refresh if (canvasView != null) { canvasView.layersChanged(); canvasView.refresh(); } } } @Override protected void sequenceOverlayChanged(Overlay overlay, SequenceEventType type) { super.sequenceOverlayChanged(overlay, type); // layer refresh if (canvasView != null) { canvasView.layersChanged(); canvasView.refresh(); } } @Override protected void sequenceDataChanged(IcyBufferedImage image, SequenceEventType type) { super.sequenceDataChanged(image, type); // refresh image if (canvasView != null) { canvasView.imageChanged(); canvasView.refresh(); } } @Override protected void sequenceTypeChanged() { super.sequenceTypeChanged(); // sequence XY dimension changed ? if ((previousImageSize.width != getImageSizeX()) || (previousImageSize.height != getImageSizeY())) { // fit to canvas enabled ? --> adapt zoom to new sequence XY dimension if (getFitToCanvas()) fitImageToCanvas(true); } } @Override public void toolChanged(String command) { final Sequence seq = getSequence(); final ToolRibbonTask toolTask = Icy.getMainInterface().getToolRibbon(); if (toolTask != null) { // if we selected a ROI tool we force layers to be visible if (toolTask.isROITool()) setLayersVisible(true); } // unselected all ROI if (seq != null) seq.setSelectedROI(null); } }