/*
* 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 icy.gui.main.MainFrame;
import icy.gui.viewer.Viewer;
import icy.main.Icy;
import icy.painter.Overlay;
import icy.sequence.DimensionId;
import icy.sequence.Sequence;
import icy.type.rectangle.Rectangle5D;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
/**
* @author Stephane
*/
public abstract class IcyCanvas2D extends IcyCanvas
{
/**
*
*/
private static final long serialVersionUID = 743937493919099495L;
/** mouse position (image coordinate space) */
protected Point2D.Double mouseImagePos;
// image coordinate to canvas coordinate transform
protected final AffineTransform transform;
// canvas coordinate to image coordinate transform
protected AffineTransform inverseTransform;
protected boolean transformChanged;
public IcyCanvas2D(Viewer viewer)
{
super(viewer);
// default for 2D canvas
posX = -1;
posY = -1;
posZ = 0;
posT = 0;
// initial mouse position
mouseImagePos = new Point2D.Double();
transform = new AffineTransform();
inverseTransform = new AffineTransform();
transformChanged = false;
// adjust LUT alpha level for 2D view
lut.setAlphaToOpaque();
}
@Override
public void setPositionZ(int z)
{
// position -1 not supported for Z dimension on this canvas
if (z != -1)
super.setPositionZ(z);
}
@Override
public void setPositionT(int t)
{
// position -1 not supported for T dimension on this canvas
if (t != -1)
super.setPositionT(t);
}
@Override
public double getMouseImagePosX()
{
// can be called before constructor ended
if (mouseImagePos == null)
return 0d;
return mouseImagePos.x;
}
@Override
public double getMouseImagePosY()
{
// can be called before constructor ended
if (mouseImagePos == null)
return 0d;
return mouseImagePos.y;
}
/**
* Return mouse image position
*/
public Point2D.Double getMouseImagePos()
{
return (Point2D.Double) mouseImagePos.clone();
}
public void setMouseImagePos(double x, double y)
{
if ((mouseImagePos.x != x) || (mouseImagePos.y != y))
{
mouseImagePos.x = x;
mouseImagePos.y = y;
// direct update of mouse canvas position
mousePos = imageToCanvas(mouseImagePos);
// notify change
mouseImagePositionChanged(DimensionId.NULL);
}
}
/**
* Set mouse image position
*/
public void setMouseImagePos(Point2D.Double point)
{
setMouseImagePos(point.x, point.y);
}
@Override
public boolean setMousePos(int x, int y)
{
final boolean result = super.setMousePos(x, y);
if (result)
{
if (mouseImagePos == null)
mouseImagePos = new Point2D.Double();
final Point2D newPos = canvasToImage(mousePos);
final double newX = newPos.getX();
final double newY = newPos.getY();
boolean changed = false;
// need to check against NaN is conversion is not supported
if (!Double.isNaN(newX) && (newX != mouseImagePos.x))
{
mouseImagePos.x = newX;
changed = true;
}
// need to check against NaN is conversion is not supported
if (!Double.isNaN(newY) && (newY != mouseImagePos.y))
{
mouseImagePos.y = newY;
changed = true;
}
// notify change
if (changed)
mouseImagePositionChanged(DimensionId.NULL);
}
return result;
}
/**
* @deprecated Use {@link #setMousePos(int, int)} instead
*/
@Deprecated
public void setMouseCanvasPos(int x, int y)
{
setMousePos(x, y);
}
/**
* @deprecated Use {@link #setMousePos(Point)} instead.
*/
@Deprecated
public void setMouseCanvasPos(Point point)
{
setMousePos(point);
}
@Override
protected void setMouseImagePosXInternal(double value)
{
mouseImagePos.x = value;
// direct update of mouse canvas position
mousePos = imageToCanvas(mouseImagePos);
super.setMouseImagePosXInternal(value);
}
@Override
protected void setMouseImagePosYInternal(double value)
{
mouseImagePos.y = value;
// direct update of mouse canvas position
mousePos = imageToCanvas(mouseImagePos);
super.setMouseImagePosYInternal(value);
}
/**
* Convert specified canvas delta to image delta.<br>
*/
protected Point2D.Double canvasToImageDelta(int x, int y, double scaleX, double scaleY, double rot)
{
// get cos and sin
final double cos = Math.cos(-rot);
final double sin = Math.sin(-rot);
// apply rotation
final double resX = (x * cos) - (y * sin);
final double resY = (x * sin) + (y * cos);
// and scale
return new Point2D.Double(resX / scaleX, resY / scaleY);
}
/**
* Convert specified canvas delta point to image delta point
*/
public Point2D.Double canvasToImageDelta(int x, int y)
{
return canvasToImageDelta(x, y, getScaleX(), getScaleY(), getRotationZ());
}
/**
* Convert specified canvas delta point to image delta point
*/
public Point2D.Double canvasToImageDelta(Point point)
{
return canvasToImageDelta(point.x, point.y);
}
/**
* Convert specified canvas delta point to image delta point.
* The conversion is affected by zoom ratio but with the specified logarithm factor.
*/
public Point2D.Double canvasToImageLogDelta(int x, int y, double logFactor)
{
final double sx = getScaleX() / Math.pow(10, Math.log10(getScaleX()) / logFactor);
final double sy = getScaleY() / Math.pow(10, Math.log10(getScaleY()) / logFactor);
return canvasToImageDelta(x, y, sx, sy, getRotationZ());
}
/**
* Convert specified canvas delta point to image delta point.
* The conversion is affected by zoom ratio but with the specified logarithm factor.
*/
public Point2D.Double canvasToImageLogDelta(int x, int y)
{
return canvasToImageLogDelta(x, y, 5d);
}
/**
* Convert specified canvas point to image point.<br>
* By default we consider the rotation applied relatively to canvas center.<br>
* Override this method if you want different transformation type.
*/
protected Point2D.Double canvasToImage(int x, int y, int offsetX, int offsetY, double scaleX, double scaleY,
double rot)
{
// get canvas center
final double canvasCenterX = getCanvasSizeX() / 2;
final double canvasCenterY = getCanvasSizeY() / 2;
// center to canvas for rotation
final double dx = x - canvasCenterX;
final double dy = y - canvasCenterY;
// get cos and sin
final double cos = Math.cos(-rot);
final double sin = Math.sin(-rot);
// apply rotation
double resX = (dx * cos) - (dy * sin);
double resY = (dx * sin) + (dy * cos);
// translate back to position
resX += canvasCenterX;
resY += canvasCenterY;
// basic transform to image coordinates
resX = ((resX - offsetX) / scaleX);
resY = ((resY - offsetY) / scaleY);
return new Point2D.Double(resX, resY);
}
/**
* Convert specified canvas point to image point
*/
public Point2D.Double canvasToImage(int x, int y)
{
final Point2D.Double result = new Point2D.Double(0d, 0d);
// we can directly use the transform object here
getInverseTransform().transform(new Point2D.Double(x, y), result);
return result;
// return canvasToImage(x, y, getOffsetX(), getOffsetY(), getScaleX(), getScaleY(),
// getRotationZ());
}
/**
* Convert specified canvas point to image point
*/
public Point2D.Double canvasToImage(Point point)
{
return canvasToImage(point.x, point.y);
}
/**
* Convert specified canvas rectangle to image rectangle
*/
public Rectangle2D.Double canvasToImage(int x, int y, int w, int h)
{
// convert each rectangle point
final Point2D.Double pt1 = canvasToImage(x, y);
final Point2D.Double pt2 = canvasToImage(x + w, y);
final Point2D.Double pt3 = canvasToImage(x + w, y + h);
final Point2D.Double pt4 = canvasToImage(x, y + h);
// get minimum and maximum X / Y
final double minX = Math.min(pt1.x, Math.min(pt2.x, Math.min(pt3.x, pt4.x)));
final double maxX = Math.max(pt1.x, Math.max(pt2.x, Math.max(pt3.x, pt4.x)));
final double minY = Math.min(pt1.y, Math.min(pt2.y, Math.min(pt3.y, pt4.y)));
final double maxY = Math.max(pt1.y, Math.max(pt2.y, Math.max(pt3.y, pt4.y)));
// return transformed rectangle
return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
}
/**
* Convert specified canvas rectangle to image rectangle
*/
public Rectangle2D.Double canvasToImage(Rectangle rect)
{
return canvasToImage(rect.x, rect.y, rect.width, rect.height);
}
/**
* Convert specified image delta to canvas delta.<br>
*/
protected Point imageToCanvasDelta(double x, double y, double scaleX, double scaleY, double rot)
{
// apply scale
final double dx = x * scaleX;
final double dy = y * scaleY;
// get cos and sin
final double cos = Math.cos(rot);
final double sin = Math.sin(rot);
// apply rotation
final double resX = (dx * cos) - (dy * sin);
final double resY = (dx * sin) + (dy * cos);
return new Point((int) Math.round(resX), (int) Math.round(resY));
}
/**
* Convert specified image delta point to canvas delta point
*/
public Point imageToCanvasDelta(double x, double y)
{
return imageToCanvasDelta(x, y, getScaleX(), getScaleY(), getRotationZ());
}
/**
* Convert specified image delta point to canvas delta point
*/
public Point imageToCanvasDelta(Point2D.Double point)
{
return imageToCanvasDelta(point.x, point.y);
}
/**
* Convert specified image point to canvas point.<br>
* By default we consider the rotation applied relatively to image center.<br>
* Override this method if you want different transformation type.
*/
protected Point imageToCanvas(double x, double y, int offsetX, int offsetY, double scaleX, double scaleY, double rot)
{
// get canvas center
final double canvasCenterX = getCanvasSizeX() / 2;
final double canvasCenterY = getCanvasSizeY() / 2;
// basic transform to canvas coordinates and canvas centering
final double dx = ((x * scaleX) + offsetX) - canvasCenterX;
final double dy = ((y * scaleY) + offsetY) - canvasCenterY;
// get cos and sin
final double cos = Math.cos(rot);
final double sin = Math.sin(rot);
// apply rotation
double resX = (dx * cos) - (dy * sin);
double resY = (dx * sin) + (dy * cos);
// translate back to position
resX += canvasCenterX;
resY += canvasCenterY;
return new Point((int) Math.round(resX), (int) Math.round(resY));
}
/**
* Convert specified image point to canvas point
*/
public Point imageToCanvas(double x, double y)
{
final Point result = new Point();
// we can directly use the transform object here
getTransform().transform(new Point2D.Double(x, y), result);
return result;
// return imageToCanvas(x, y, getOffsetX(), getOffsetY(), getScaleX(), getScaleY(),
// getRotationZ());
}
/**
* Convert specified image point to canvas point
*/
public Point imageToCanvas(Point2D.Double point)
{
return imageToCanvas(point.x, point.y);
}
/**
* Convert specified image rectangle to canvas rectangle
*/
public Rectangle imageToCanvas(double x, double y, double w, double h)
{
// convert each rectangle point
final Point pt1 = imageToCanvas(x, y);
final Point pt2 = imageToCanvas(x + w, y);
final Point pt3 = imageToCanvas(x + w, y + h);
final Point pt4 = imageToCanvas(x, y + h);
// get minimum and maximum X / Y
final int minX = Math.min(pt1.x, Math.min(pt2.x, Math.min(pt3.x, pt4.x)));
final int maxX = Math.max(pt1.x, Math.max(pt2.x, Math.max(pt3.x, pt4.x)));
final int minY = Math.min(pt1.y, Math.min(pt2.y, Math.min(pt3.y, pt4.y)));
final int maxY = Math.max(pt1.y, Math.max(pt2.y, Math.max(pt3.y, pt4.y)));
// return transformed rectangle
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/**
* Convert specified image rectangle to canvas rectangle
*/
public Rectangle imageToCanvas(Rectangle2D.Double rect)
{
return imageToCanvas(rect.x, rect.y, rect.width, rect.height);
}
/**
* Get 2D view size in canvas pixel coordinate
*
* @return a Dimension which represents the visible size.
*/
public Dimension getCanvasSize()
{
return new Dimension(getCanvasSizeX(), getCanvasSizeY());
}
/**
* Get 2D image size
*/
public Dimension getImageSize()
{
return new Dimension(getImageSizeX(), getImageSizeY());
}
/**
* Get 2D image size in canvas pixel coordinate
*/
public Dimension getImageCanvasSize()
{
final double imageSizeX = getImageSizeX();
final double imageSizeY = getImageSizeY();
final double scaleX = getScaleX();
final double scaleY = getScaleY();
final double rot = getRotationZ();
// convert image rectangle
final Point pt1 = imageToCanvas(0d, 0d, 0, 0, scaleX, scaleY, rot);
final Point pt2 = imageToCanvas(imageSizeX, 0d, 0, 0, scaleX, scaleY, rot);
final Point pt3 = imageToCanvas(0d, imageSizeY, 0, 0, scaleX, scaleY, rot);
final Point pt4 = imageToCanvas(imageSizeX, imageSizeY, 0, 0, scaleX, scaleY, rot);
final int minX = Math.min(pt1.x, Math.min(pt2.x, Math.min(pt3.x, pt4.x)));
final int maxX = Math.max(pt1.x, Math.max(pt2.x, Math.max(pt3.x, pt4.x)));
final int minY = Math.min(pt1.y, Math.min(pt2.y, Math.min(pt3.y, pt4.y)));
final int maxY = Math.max(pt1.y, Math.max(pt2.y, Math.max(pt3.y, pt4.y)));
return new Dimension(maxX - minX, maxY - minY);
}
/**
* Get 2D canvas visible rectangle (canvas coordinate).
*/
public Rectangle getCanvasVisibleRect()
{
// try to return view component visible rectangle by default
final Component comp = getViewComponent();
if (comp instanceof JComponent)
return ((JComponent) comp).getVisibleRect();
// just return the canvas component visible rectangle
return getVisibleRect();
}
/**
* Get 2D image visible rectangle (image coordinate).<br>
* Prefer the {@link Graphics#getClipBounds()} method for paint operation as the image visible
* rectangle may return wrong information sometime (when using the {@link #getRenderedImage(int, int, int, boolean)}
* method for instance).
*/
public Rectangle2D getImageVisibleRect()
{
return canvasToImage(getCanvasVisibleRect());
}
/**
* Adjust view position and possibly scaling factor to ensure the specified region become visible.<br>
* It's up to the Canvas implementation to decide how to make the region visible.
*
* @param region
* the region we want to see
*/
public void centerOn(Rectangle region)
{
// override it in Canvas implementation
}
/**
* Center image on specified image position in canvas
*/
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());
setOffsetX(getOffsetX() + (int) Math.round(newTrans.x));
setOffsetY(getOffsetY() + (int) Math.round(newTrans.y));
}
/**
* Center image on specified image position in canvas
*/
public void centerOnImage(Point2D.Double pt)
{
centerOnImage(pt.x, pt.y);
}
/**
* Center image in canvas
*/
public void centerImage()
{
centerOnImage(getImageSizeX() / 2, getImageSizeY() / 2);
}
/**
* get scale X and scale Y so image fit in canvas view dimension
*/
protected Point2D.Double getFitImageToCanvasScale()
{
final double imageSizeX = getImageSizeX();
final double imageSizeY = getImageSizeY();
if ((imageSizeX > 0d) && (imageSizeY > 0d))
{
final double rot = getRotationZ();
// convert image rectangle
final Point pt1 = imageToCanvas(0d, 0d, 0, 0, 1d, 1d, rot);
final Point pt2 = imageToCanvas(imageSizeX, 0d, 0, 0, 1d, 1d, rot);
final Point pt3 = imageToCanvas(0d, imageSizeY, 0, 0, 1d, 1d, rot);
final Point pt4 = imageToCanvas(imageSizeX, imageSizeY, 0, 0, 1d, 1d, rot);
final int minX = Math.min(pt1.x, Math.min(pt2.x, Math.min(pt3.x, pt4.x)));
final int maxX = Math.max(pt1.x, Math.max(pt2.x, Math.max(pt3.x, pt4.x)));
final int minY = Math.min(pt1.y, Math.min(pt2.y, Math.min(pt3.y, pt4.y)));
final int maxY = Math.max(pt1.y, Math.max(pt2.y, Math.max(pt3.y, pt4.y)));
// get image dimension transformed by rotation
final double sx = (double) getCanvasSizeX() / (double) (maxX - minX);
final double sy = (double) getCanvasSizeY() / (double) (maxY - minY);
return new Point2D.Double(sx, sy);
}
return null;
}
/**
* Change scale so image fit in canvas view dimension
*/
public void fitImageToCanvas()
{
final Point2D.Double s = getFitImageToCanvasScale();
if (s != null)
{
final double scale = Math.min(s.x, s.y);
setScaleX(scale);
setScaleY(scale);
}
}
/**
* Change canvas size (so viewer size) to get it fit with image dimension if possible
*/
public void fitCanvasToImage()
{
final MainFrame mainFrame = Icy.getMainInterface().getMainFrame();
final Dimension imageCanvasSize = getImageCanvasSize();
if ((imageCanvasSize.width > 0) && (imageCanvasSize.height > 0) && (mainFrame != null))
{
final Dimension maxDim = mainFrame.getDesktopSize();
final Dimension adjImgCnvSize = canvasToViewer(imageCanvasSize);
// fit in available space --> resize viewer
viewer.setSize(Math.min(adjImgCnvSize.width, maxDim.width), Math.min(adjImgCnvSize.height, maxDim.height));
}
}
/**
* Convert canvas dimension to viewer dimension
*/
public Dimension canvasToViewer(Dimension dim)
{
final Dimension canvasViewSize = getCanvasSize();
final Dimension viewerSize = viewer.getSize();
final Dimension result = new Dimension(dim);
result.width -= canvasViewSize.width;
result.width += viewerSize.width;
result.height -= canvasViewSize.height;
result.height += viewerSize.height;
return result;
}
/**
* Convert viewer dimension to canvas dimension
*/
public Dimension viewerToCanvas(Dimension dim)
{
final Dimension canvasViewSize = getCanvasSize();
final Dimension viewerSize = viewer.getSize();
final Dimension result = new Dimension(dim);
result.width -= viewerSize.width;
result.width += canvasViewSize.width;
result.height -= viewerSize.height;
result.height += canvasViewSize.height;
return result;
}
/**
* Update internal {@link AffineTransform} object.
*/
protected void updateTransform()
{
final int canvasCenterX = getCanvasSizeX() / 2;
final int canvasCenterY = getCanvasSizeY() / 2;
// rotation is centered to canvas
transform.setToTranslation(canvasCenterX, canvasCenterY);
transform.rotate(getRotationZ());
transform.translate(-canvasCenterX, -canvasCenterY);
transform.translate(getOffsetX(), getOffsetY());
transform.scale(getScaleX(), getScaleY());
transformChanged = true;
}
/**
* Return the 2D {@link AffineTransform} object which convert from image coordinate to canvas
* coordinate.<br>
* {@link Overlay} should directly use the transform information from the {@link Graphics2D} object provided in
* their {@link Overlay#paint(Graphics2D, Sequence, IcyCanvas)} method.
*/
public AffineTransform getTransform()
{
return transform;
}
/**
* Return the 2D {@link AffineTransform} object which convert from canvas coordinate to image
* coordinate.<br>
* {@link Overlay} should directly use the transform information from the {@link Graphics2D} object provided in
* their {@link Overlay#paint(Graphics2D, Sequence, IcyCanvas)} method.
*/
public AffineTransform getInverseTransform()
{
if (transformChanged)
{
try
{
inverseTransform = transform.createInverse();
}
catch (NoninvertibleTransformException e)
{
inverseTransform = new AffineTransform();
}
transformChanged = false;
}
return inverseTransform;
}
@Override
public void changed(IcyCanvasEvent event)
{
super.changed(event);
switch (event.getType())
{
case OFFSET_CHANGED:
case ROTATION_CHANGED:
case SCALE_CHANGED:
updateTransform();
break;
}
}
}