/******************************************************************************* * Copyright (c) 2012 IT Just working. * 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: * IT Just working - initial API and implementation *******************************************************************************/ package com.itjw.txviewer.graph.ui.swt; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; 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.Point; 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 */ protected Image sourceImage; /* original image */ private Image screenImage; /* screen image */ private AffineTransform transform = new AffineTransform(); protected boolean center; /** * Constructor for ScrollableCanvas. * @param parent the parent of this control. * @param style the style of this control. */ public SWTImageCanvas(final Composite parent, int style) { super( parent, style ); center=true; addControlListener(new ControlAdapter() { /* resize listener. */ public void controlResized(ControlEvent event) { syncScrollBars(); } }); addPaintListener(new PaintListener() { /* paint listener. */ public void paintControl(final PaintEvent event) { paint(event.gc); } }); initScrollBars(); } /** * Dispose the garbage here */ public void dispose() { if (sourceImage != null && !sourceImage.isDisposed()) { sourceImage.dispose(); } if (screenImage != null && !screenImage.isDisposed()) { screenImage.dispose(); } } /* Paint function */ private void paint(GC gc) { Rectangle clientRect = getClientArea(); /* Canvas' painting area */ if (sourceImage != null) { Rectangle imageRect = inverseTransformRect(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 = sourceImage.getBounds(); imageRect = imageRect.intersection(imageBound); Rectangle destRect = transformRect(transform, imageRect); if (screenImage != null) screenImage.dispose(); screenImage = new Image(getDisplay(), clientRect.width, clientRect.height); GC newGC = new GC(screenImage); newGC.setClipping(clientRect); newGC.drawImage(sourceImage, imageRect.x, imageRect.y, imageRect.width, imageRect.height, destRect.x, destRect.y, destRect.width, destRect.height); newGC.dispose(); gc.drawImage(screenImage, 0, 0); postImagePaint(gc); } else { gc.setClipping(clientRect); gc.fillRectangle(clientRect); initScrollBars(); } } protected void postImagePaint(GC gc){ // do nothing, to be overridden by childs } /* Initalize the scrollbar and register listeners. */ private void initScrollBars() { ScrollBar horizontal = getHorizontalBar(); horizontal.setEnabled(false); horizontal.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { scrollHorizontally((ScrollBar) event.widget); } }); ScrollBar vertical = getVerticalBar(); vertical.setEnabled(false); vertical.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { scrollVertically((ScrollBar) event.widget); } }); } /* Scroll horizontally */ private void scrollHorizontally(ScrollBar scrollBar) { if (sourceImage == null) return; AffineTransform af = transform; double tx = af.getTranslateX(); double select = -scrollBar.getSelection(); af.preConcatenate(AffineTransform.getTranslateInstance(select - tx, 0)); transform = af; syncScrollBars(); } /* Scroll vertically */ private void scrollVertically(ScrollBar scrollBar) { if (sourceImage == null) return; AffineTransform af = transform; double ty = af.getTranslateY(); double select = -scrollBar.getSelection(); af.preConcatenate(AffineTransform.getTranslateInstance(0, select - ty)); transform = af; syncScrollBars(); } public void scrollToY(int y){ ScrollBar vBar = getVerticalBar(); vBar.setSelection(y); scrollVertically(vBar); } public void scrollToX(int x){ ScrollBar hBar = getHorizontalBar(); hBar.setSelection(x); scrollHorizontally(hBar); } /** * Source image getter. * @return sourceImage. */ public Image getSourceImage() { return sourceImage; } /** * Source image setter. * @return sourceImage. */ public void setSourceImage(Image sourceImage) { if(this.sourceImage!=null) this.sourceImage.dispose(); this.sourceImage=sourceImage; } public AffineTransform getTransform() { return transform; } /** * Synchronize the scrollbar with the image. If the transform is out * of range, it will correct it. This function considers only following * factors : transform, image size, client area. */ public void syncScrollBars() { if (sourceImage == null) { redraw(); return; } AffineTransform af = 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((int) (getClientArea().width / 100)); horizontal.setPageIncrement(getClientArea().width); Rectangle imageBound = 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 = center?((cw - imageBound.width * sx) / 2):0; //center or put left if too small. } horizontal.setSelection((int) (-tx)); horizontal.setThumb((int) (getClientArea().width)); ScrollBar vertical = getVerticalBar(); vertical.setIncrement((int) (getClientArea().height / 100)); vertical.setPageIncrement((int) (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 = center?((ch - imageBound.height * sy) / 2):0; //center or put top if too small. } vertical.setSelection((int) (-ty)); vertical.setThumb((int) (getClientArea().height)); /* update transform. */ af = AffineTransform.getScaleInstance(sx, sy); af.preConcatenate(AffineTransform.getTranslateInstance(tx, ty)); transform = af; redraw(); } /** * Fit the image onto the canvas */ public void fitCanvas() { if (sourceImage == null) return; Rectangle imageBound = 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 resetTransform() { if (sourceImage == null) return; transform = new AffineTransform(); syncScrollBars(); } /** * Perform a zooming operation centered on the given point * (dx, dy) and using the given scale factor. * The given AffineTransform instance is preconcatenated. * @param dx center x * @param dy center y * @param scale zoom rate * @param af original affinetransform */ public 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)); transform = af; syncScrollBars(); } /** * Zoom in around the center of client Area. */ public void zoomIn() { if (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, ZOOMIN_RATE, transform); } /** * Zoom out around the center of client Area. */ public void zoomOut() { if (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, ZOOMOUT_RATE, transform); } /** * Given an arbitrary rectangle, get the rectangle with the given transform. * The result rectangle is positive width and positive height. * @param af AffineTransform * @param src source rectangle * @return rectangle after transform with positive width and height */ protected static Rectangle transformRect(AffineTransform af, Rectangle src){ Rectangle dest= new Rectangle(0,0,0,0); src=absRect(src); Point p1=new Point(src.x,src.y); p1=transformPoint(af,p1); dest.x=p1.x; dest.y=p1.y; dest.width=(int)(src.width*af.getScaleX()); dest.height=(int)(src.height*af.getScaleY()); return dest; } /** * Given an arbitrary rectangle, get the rectangle with the inverse given transform. * The result rectangle is positive width and positive height. * @param af AffineTransform * @param src source rectangle * @return rectangle after transform with positive width and height */ protected static Rectangle inverseTransformRect(AffineTransform af, Rectangle src){ Rectangle dest= new Rectangle(0,0,0,0); src=absRect(src); Point p1=new Point(src.x,src.y); p1=inverseTransformPoint(af,p1); dest.x=p1.x; dest.y=p1.y; dest.width=(int)(src.width/af.getScaleX()); dest.height=(int)(src.height/af.getScaleY()); return dest; } /** * Given an arbitrary point, get the point with the given transform. * @param af affine transform * @param pt point to be transformed * @return point after tranform */ protected static Point transformPoint(AffineTransform af, Point pt) { Point2D src = new Point2D.Float(pt.x, pt.y); Point2D dest= af.transform(src, null); Point point=new Point((int)Math.floor(dest.getX()), (int)Math.floor(dest.getY())); return point; } /** * Given an arbitrary point, get the point with the inverse given transform. * @param af AffineTransform * @param pt source point * @return point after transform */ protected static Point inverseTransformPoint(AffineTransform af, Point pt){ Point2D src=new Point2D.Float(pt.x,pt.y); try{ Point2D dest= af.inverseTransform(src, null); return new Point((int)Math.floor(dest.getX()), (int)Math.floor(dest.getY())); }catch (Exception e){ e.printStackTrace(); return new Point(0,0); } } /** * Given arbitrary rectangle, return a rectangle with upper-left * start and positive width and height. * @param src source rectangle * @return result rectangle with positive width and height */ protected static Rectangle absRect(Rectangle src){ Rectangle dest= new Rectangle(0,0,0,0); if(src.width<0) { dest.x=src.x+src.width+1; dest.width=-src.width; } else{ dest.x=src.x; dest.width=src.width; } if(src.height<0) { dest.y=src.y+src.height+1; dest.height=-src.height; } else{ dest.y=src.y; dest.height=src.height; } return dest; } }