blob: dcb78c7789887e3d55cf21883228645238dcca9b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
<<<<<<< HEAD
* Pierre Allard - initial API and implementation
* Regent L'Archeveque
*
=======
* Pierre Allard,
* Regent L'Archeveque - initial API and implementation
*
>>>>>>> refs/heads/eclipse_pa
* SPDX-License-Identifier: EPL-1.0
*
*******************************************************************************/
package org.eclipse.apogy.common.images.ui.composites;
import java.awt.geom.AffineTransform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ScrollBar;
public class SWTImageCanvas extends Canvas {
/* zooming rates in x and y direction are equal. */
final float ZOOMIN_RATE = 1.1f; /* zoomin rate */
final float ZOOMOUT_RATE = 0.9f; /* zoomout rate */
private Image sourceImage; /* original image */
private Image screenImage; /* screen image */
private AffineTransform transform = new AffineTransform();
public SWTImageCanvas(final Composite parent) {
this(parent, SWT.NULL);
}
public SWTImageCanvas(Composite parent, int style) {
super(parent, style | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.NO_BACKGROUND);
addControlListener(new ControlAdapter() { /* resize listener. */
@Override
public void controlResized(ControlEvent event) {
zoomFit();
syncScrollBars();
}
});
addPaintListener(new PaintListener() { /* paint listener. */
@Override
public void paintControl(final PaintEvent event) {
paint(event.gc);
}
});
initScrollBars();
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (SWTImageCanvas.this.sourceImage != null && !SWTImageCanvas.this.sourceImage.isDisposed()) {
SWTImageCanvas.this.sourceImage.dispose();
}
if (SWTImageCanvas.this.screenImage != null && !SWTImageCanvas.this.screenImage.isDisposed()) {
SWTImageCanvas.this.screenImage.dispose();
}
}
});
}
/**
* Paint function
*/
private void paint(GC gc) {
Rectangle clientRect = getClientArea(); /* Canvas' painting area */
if (this.sourceImage != null) {
Rectangle imageRect = SWT2dUtil.inverseTransformRect(this.transform, clientRect);
int gap = 2; /* find a better start point to render */
imageRect.x -= gap;
imageRect.y -= gap;
imageRect.width += 2 * gap;
imageRect.height += 2 * gap;
Rectangle imageBound = this.sourceImage.getBounds();
imageRect = imageRect.intersection(imageBound);
Rectangle destRect = SWT2dUtil.transformRect(this.transform, imageRect);
if (this.screenImage != null)
this.screenImage.dispose();
this.screenImage = new Image(getDisplay(), clientRect.width, clientRect.height);
GC newGC = new GC(this.screenImage);
newGC.setClipping(clientRect);
newGC.drawImage(this.sourceImage, imageRect.x, imageRect.y, imageRect.width, imageRect.height, destRect.x,
destRect.y, destRect.width, destRect.height);
newGC.dispose();
gc.drawImage(this.screenImage, 0, 0);
} else {
gc.setClipping(clientRect);
gc.fillRectangle(clientRect);
initScrollBars();
}
}
/**
* Initializes the scroll-bar and register listeners.
*/
private void initScrollBars() {
ScrollBar horizontal = getHorizontalBar();
horizontal.setEnabled(false);
horizontal.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
scrollHorizontally((ScrollBar) event.widget);
}
});
ScrollBar vertical = getVerticalBar();
vertical.setEnabled(false);
vertical.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
scrollVertically((ScrollBar) event.widget);
}
});
}
/* Scroll horizontally */
private void scrollHorizontally(ScrollBar scrollBar) {
if (this.sourceImage == null)
return;
AffineTransform af = this.transform;
double tx = af.getTranslateX();
double select = -scrollBar.getSelection();
af.preConcatenate(AffineTransform.getTranslateInstance(select - tx, 0));
this.transform = af;
syncScrollBars();
}
/* Scroll vertically */
private void scrollVertically(ScrollBar scrollBar) {
if (this.sourceImage == null)
return;
AffineTransform af = this.transform;
double ty = af.getTranslateY();
double select = -scrollBar.getSelection();
af.preConcatenate(AffineTransform.getTranslateInstance(0, select - ty));
this.transform = af;
syncScrollBars();
}
/**
* Source image getter.
*
* @return sourceImage.
*/
protected Image getSourceImage() {
return this.sourceImage;
}
/**
* Synchronizes the scroll-bar with the image. If the transform is out of range,
* it will correct it. This function considers only following factors :<b>
* transform, image size, client area</b>.
*/
protected void syncScrollBars() {
if (this.sourceImage == null) {
redraw();
return;
}
AffineTransform af = this.transform;
double sx = af.getScaleX(), sy = af.getScaleY();
double tx = af.getTranslateX(), ty = af.getTranslateY();
if (tx > 0)
tx = 0;
if (ty > 0)
ty = 0;
ScrollBar horizontal = getHorizontalBar();
horizontal.setIncrement(getClientArea().width / 100);
horizontal.setPageIncrement(getClientArea().width);
Rectangle imageBound = this.sourceImage.getBounds();
int cw = getClientArea().width, ch = getClientArea().height;
if (imageBound.width * sx > cw) { /* image is wider than client area */
horizontal.setMaximum((int) (imageBound.width * sx));
horizontal.setEnabled(true);
if (((int) -tx) > horizontal.getMaximum() - cw)
tx = -horizontal.getMaximum() + cw;
} else { /* image is narrower than client area */
horizontal.setEnabled(false);
tx = (cw - imageBound.width * sx) / 2; // center if too small.
}
horizontal.setSelection((int) (-tx));
horizontal.setThumb((getClientArea().width));
ScrollBar vertical = getVerticalBar();
vertical.setIncrement(getClientArea().height / 100);
vertical.setPageIncrement((getClientArea().height));
if (imageBound.height * sy > ch) { /* image is higher than client area */
vertical.setMaximum((int) (imageBound.height * sy));
vertical.setEnabled(true);
if (((int) -ty) > vertical.getMaximum() - ch)
ty = -vertical.getMaximum() + ch;
} else { /* image is less higher than client area */
vertical.setEnabled(false);
ty = (ch - imageBound.height * sy) / 2; // center if too small.
}
vertical.setSelection((int) (-ty));
vertical.setThumb((getClientArea().height));
/* update transform. */
af = AffineTransform.getScaleInstance(sx, sy);
af.preConcatenate(AffineTransform.getTranslateInstance(tx, ty));
this.transform = af;
redraw();
}
/**
* Get the image data. (for future use only)
*
* @return image data of canvas
*/
public ImageData getImageData() {
return this.sourceImage.getImageData();
}
/**
* Reset the image data and update the image
*
* @param data image data to be set
*/
public void setImageData(ImageData data) {
if (this.sourceImage != null) {
this.sourceImage.dispose();
}
if (data != null) {
this.sourceImage = new Image(getDisplay(), data);
}
syncScrollBars();
zoomFit();
}
/**
* Fit the image onto the canvas
*/
public void zoomFit() {
if (this.sourceImage == null)
return;
Rectangle imageBound = this.sourceImage.getBounds();
Rectangle destRect = getClientArea();
double sx = (double) destRect.width / (double) imageBound.width;
double sy = (double) destRect.height / (double) imageBound.height;
double s = Math.min(sx, sy);
double dx = 0.5 * destRect.width;
double dy = 0.5 * destRect.height;
centerZoom(dx, dy, s, new AffineTransform());
}
/**
* Show the image with the original size
*/
public void refresh() {
if (this.sourceImage == null)
return;
this.transform = new AffineTransform();
syncScrollBars();
zoomFit();
}
/**
* Perform a zooming operation centered on the given point (dx, dy) and using
* the given scale factor. The given AffineTransform instance is
* pre-concatenated.
*
* @param dx center x
* @param dy center y
* @param scale zoom rate
* @param af original affine transform
*/
protected void centerZoom(double dx, double dy, double scale, AffineTransform af) {
af.preConcatenate(AffineTransform.getTranslateInstance(-dx, -dy));
af.preConcatenate(AffineTransform.getScaleInstance(scale, scale));
af.preConcatenate(AffineTransform.getTranslateInstance(dx, dy));
this.transform = af;
syncScrollBars();
}
/**
* Zoom in around the center of client Area.
*/
public void zoomIn() {
if (this.sourceImage == null)
return;
Rectangle rect = getClientArea();
int w = rect.width, h = rect.height;
double dx = ((double) w) / 2;
double dy = ((double) h) / 2;
centerZoom(dx, dy, this.ZOOMIN_RATE, this.transform);
}
/**
* Zoom out around the center of client Area.
*/
public void zoomOut() {
if (this.sourceImage == null)
return;
Rectangle rect = getClientArea();
int w = rect.width, h = rect.height;
double dx = ((double) w) / 2;
double dy = ((double) h) / 2;
centerZoom(dx, dy, this.ZOOMOUT_RATE, this.transform);
}
public void rotate90degCounterClockwise() {
/* rotate image anti-clockwise */
ImageData src = getImageData();
if (src == null)
return;
PaletteData srcPal = src.palette;
PaletteData destPal;
ImageData dest;
/* construct a new ImageData */
if (srcPal.isDirect) {
destPal = new PaletteData(srcPal.redMask, srcPal.greenMask, srcPal.blueMask);
} else {
destPal = new PaletteData(srcPal.getRGBs());
}
dest = new ImageData(src.height, src.width, src.depth, destPal);
/* rotate by rearranging the pixels */
for (int i = 0; i < src.width; i++) {
for (int j = 0; j < src.height; j++) {
int pixel = src.getPixel(i, j);
dest.setPixel(j, src.width - 1 - i, pixel);
}
}
setImageData(dest);
}
public void rotate90degClockwise() {
/* rotate image anti-clockwise */
ImageData src = getImageData();
if (src == null)
return;
PaletteData srcPal = src.palette;
PaletteData destPal;
ImageData dest;
/* construct a new ImageData */
if (srcPal.isDirect) {
destPal = new PaletteData(srcPal.redMask, srcPal.greenMask, srcPal.blueMask);
} else {
destPal = new PaletteData(srcPal.getRGBs());
}
dest = new ImageData(src.height, src.width, src.depth, destPal);
/* rotate by rearranging the pixels */
for (int i = 0; i < src.width; i++) {
for (int j = 0; j < src.height; j++) {
int pixel = src.getPixel(i, j);
dest.setPixel(src.height - 1 - j, i, pixel);
}
}
setImageData(dest);
}
}