/*
* 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);
}
}