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