org.csstudio.swt.widgets.figures.ImageFigure.java Source code

Java tutorial

Introduction

Here is the source code for org.csstudio.swt.widgets.figures.ImageFigure.java

Source

/*
 * Copyright (c) 2008 Stiftung Deutsches Elektronen-Synchrotron,
 * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
 *
 * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
 * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
 * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
 * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
 * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
 * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
 * OR MODIFICATIONS.
 * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
 * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
 * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
 * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
 */
package org.csstudio.swt.widgets.figures;

import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.util.XMLResourceDescriptor;
import org.csstudio.utility.batik.SVGUtils;
import org.csstudio.utility.batik.SimpleImageTranscoder;
import org.apache.commons.lang.time.DateUtils;
import org.csstudio.java.thread.ExecutionService;
import org.csstudio.swt.widgets.Activator;
import org.csstudio.swt.widgets.introspection.DefaultWidgetIntrospector;
import org.csstudio.swt.widgets.introspection.Introspectable;
import org.csstudio.swt.widgets.symbol.util.ImageUtils;
import org.csstudio.swt.widgets.symbol.util.PermutationMatrix;
import org.csstudio.swt.widgets.util.AbstractInputStreamRunnable;
import org.csstudio.swt.widgets.util.IImageLoadedListener;
import org.csstudio.swt.widgets.util.IJobErrorHandler;
import org.csstudio.swt.widgets.util.ResourceUtil;
import org.csstudio.swt.widgets.util.SingleSourceHelper;
import org.csstudio.swt.widgets.util.TextPainter;
import org.csstudio.ui.util.thread.UIBundlingThread;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.draw2d.Border;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.w3c.dom.Document;

/**
 * An image figure.
 * 
 * @author jbercic, Xihui Chen
 * 
 */

public final class ImageFigure extends Figure implements Introspectable {

    private static final int MILLISEC_IN_SEC = 1000;

    /**
     * The {@link IPath} to the image.
     */
    private IPath filePath = new Path("");
    /**
     * The image itself.
     */
    private Image staticImage = null;
    /**
     * The width of the image.
     */
    private int imgWidth = 0;
    /**
     * The height of the image.
     */
    private int imgHeight = 0;

    /**
     * The amount of pixels, which are cropped from the top.
     */
    private int topCrop = 0;

    /**
     * The amount of pixels, which are cropped from the bottom.
     */
    private int bottomCrop = 0;
    /**
     * The amount of pixels, which are cropped from the left.
     */
    private int leftCrop = 0;
    /**
     * The amount of pixels, which are cropped from the right.
     */
    private int rightCrop = 0;
    /**
     * The stretch state for the image.
     */
    private boolean stretch = true;
    /**
     * If this is an animated image
     */
    private boolean animated = false;
    private boolean alignedToNearestSecond = false;

    private Image offScreenImage;

    private GC offScreenImageGC;

    /**
     * The imaged data array for animated image
     */
    private ImageData[] imageDataArray;
    private ImageData[] originalImageDataArray;

    /**
     * The index in image data array
     */
    private int showIndex = 0;

    /**
     * The animated image is being refreshed by editpart
     */
    private boolean refreshing = false;

    private boolean animationDisabled = false;

    private boolean loadingError = false;

    private ImageLoader loader = new ImageLoader();

    // private boolean useGIFBackground = false;

    private ImageData staticImageData = null;
    private ImageData originalStaticImageData = null;

    private int repeatCount;

    private int animationIndex = 0;
    private long lastUpdateTime;
    private long interval_ms;
    private ScheduledFuture<?> scheduledFuture;

    private boolean startAnimationRequested = false;

    private volatile boolean loadingImage;
    private IImageLoadedListener imageLoadedListener;

    private PermutationMatrix oldPermutationMatrix = null;
    private PermutationMatrix permutationMatrix = PermutationMatrix.generateIdentityMatrix();

    // SVG attributes
    private boolean workingWithSVG = false;
    private boolean failedToLoadDocument;
    private SimpleImageTranscoder transcoder;
    private Document svgDocument;

    private double scale = 1.0;

    /**
     * dispose the resources used by this figure
     */
    public synchronized void dispose() {
        stopAnimation();
        if (offScreenImage != null && !offScreenImage.isDisposed()) {
            offScreenImage.dispose();
            offScreenImage = null;
        }

        if (offScreenImageGC != null && !offScreenImageGC.isDisposed()) {
            offScreenImageGC.dispose();
            offScreenImage = null;
        }

        if (staticImage != null && !staticImage.isDisposed()) {
            staticImage.dispose();
            staticImage = null;
        }
    }

    /**
     * @return the auto sized widget dimension according to the static imageSize
     */
    public synchronized Dimension getAutoSizedDimension() {
        if (originalStaticImageData != null) {
            ImageData imageData = (staticImageData == null) ? originalStaticImageData : staticImageData;
            return new Dimension(imageData.width + getInsets().getWidth() - leftCrop - rightCrop,
                    imageData.height + getInsets().getHeight() - topCrop - bottomCrop);
        } else
            return null;
    }

    /**
     * Returns the amount of pixels, which are cropped from the top.
     * 
     * @return The amount of pixels
     */
    public synchronized int getBottomCrop() {
        return bottomCrop;
    }

    /**
     * Returns the path to the image.
     * 
     * @return The path to the image
     */
    public synchronized IPath getFilePath() {
        return filePath;
    }

    /**
     * Returns the amount of pixels, which are cropped from the top.
     * 
     * @return The amount of pixels
     */
    public synchronized int getLeftCrop() {
        return leftCrop;
    }

    /**
     * Returns the amount of pixels, which are cropped from the top.
     * 
     * @return The amount of pixels
     */
    public synchronized int getRightCrop() {
        return rightCrop;
    }

    /**
     * Returns the stretch state for the image.
     * 
     * @return True, if it should be stretched, false otherwise
     */
    public synchronized boolean getStretch() {
        return stretch;
    }

    /**
     * Returns the amount of pixels, which are cropped from the top.
     * 
     * @return The amount of pixels
     */
    public synchronized int getTopCrop() {
        return topCrop;
    }

    /**
     * @return the animationDisabled
     */
    public synchronized boolean isAnimationDisabled() {
        return animationDisabled;
    }

    public boolean isLoadingImage() {
        return loadingImage;
    }

    public void setImageLoadedListener(IImageLoadedListener listener) {
        this.imageLoadedListener = listener;
    }

    private synchronized void fireImageLoadedListeners() {
        imageLoadedListener.imageLoaded(this);
    }

    private synchronized void loadImage(IPath path, IJobErrorHandler errorHandler) {
        AbstractInputStreamRunnable uiTask = new AbstractInputStreamRunnable() {

            @Override
            public void runWithInputStream(InputStream stream) {
                synchronized (ImageFigure.this) {
                    Image temp = null;
                    try {
                        temp = new Image(null, stream);
                        originalStaticImageData = temp.getImageData();
                        imgWidth = originalStaticImageData.width;
                        imgHeight = originalStaticImageData.height;
                    } finally {
                        if (temp != null && !temp.isDisposed())
                            temp.dispose();
                        try {
                            stream.close();
                        } catch (IOException e) {
                        }
                    }
                }
            }
        };
        ResourceUtil.pathToInputStreamInJob(filePath, uiTask, "Loading Image...", errorHandler);
        uiTask = new AbstractInputStreamRunnable() {

            @Override
            public void runWithInputStream(InputStream stream) {
                synchronized (ImageFigure.this) {
                    originalImageDataArray = loader.load(stream);
                    try {
                        stream.close();
                    } catch (IOException e) {
                    }
                    if (SWT.getPlatform().startsWith("rap")) //$NON-NLS-1$
                        animated = false;
                    else
                        animated = (originalImageDataArray.length > 1);
                    loadingImage = false;
                    repaint();
                }
            }
        };
        ResourceUtil.pathToInputStreamInJob(filePath, uiTask, "Loading Image...", errorHandler);

    }

    /**
     *
     */
    @SuppressWarnings("nls")
    private synchronized void loadImageFromFile() {
        // load image from file
        if (staticImage == null && !filePath.isEmpty()) {
            loadingImage = true;
            showIndex = 0;
            // loading by stream
            loadImage(filePath, new IJobErrorHandler() {

                public void handleError(Exception exception) {
                    loadingError = true;
                    loadingImage = false;
                    Activator.getLogger().log(Level.WARNING, "ERROR in loading image " + filePath, exception);
                }
            });
        }

    }

    /**
     * The main drawing routine.
     * 
     * @param gfx
     *            The {@link Graphics} to use
     */
    @SuppressWarnings("deprecation")
    @Override
    public synchronized void paintFigure(final Graphics gfx) {
        if (loadingImage)
            return;
        Rectangle bound = getBounds().getCopy();
        bound.crop(this.getInsets());
        if (bound.width <= 0 || bound.height <= 0)
            return;
        if (loadingError || failedToLoadDocument) {
            if (staticImage != null) {
                staticImage.dispose();
            }
            staticImage = null;
            if (!filePath.isEmpty()) {
                /*
                 * Font f=gfx.getFont(); FontData fd=f.getFontData()[0];
                 * 
                 * if (bound.width>=20*30) { fd.setHeight(30); } else { if
                 * (bound.width/20+1<7) { fd.setHeight(7); } else {
                 * fd.setHeight(bound.width/20+1); } } f=new
                 * Font(Display.getDefault(),fd); gfx.setFont(f);
                 */
                gfx.setBackgroundColor(getBackgroundColor());
                gfx.setForegroundColor(getForegroundColor());
                gfx.fillRectangle(bound);
                gfx.translate(bound.getLocation());
                TextPainter.drawText(gfx, "ERROR in loading image\n" + filePath, bound.width / 2, bound.height / 2,
                        TextPainter.CENTER);
                // f.dispose();
            }
            return;
        }

        // create static image
        if (staticImage == null && originalStaticImageData != null && !workingWithSVG) {
            ImageData imageData = (staticImageData == null) ? originalStaticImageData : staticImageData;
            // Apply rotation / flip
            if (permutationMatrix != null && !permutationMatrix.equals(oldPermutationMatrix)
                    && !permutationMatrix.equals(PermutationMatrix.generateIdentityMatrix()) && !animated) {
                staticImageData = ImageUtils.applyMatrix(originalStaticImageData, permutationMatrix);
                imageData = staticImageData;
            }
            if (stretch) {
                staticImage = new Image(Display.getDefault(), imageData.scaledTo(bound.width + leftCrop + rightCrop,
                        bound.height + topCrop + bottomCrop));
                if (animated) {
                    imageDataArray = new ImageData[originalImageDataArray.length];
                    double widthScaleRatio = (double) (bound.width + leftCrop + rightCrop)
                            / (double) originalStaticImageData.width;
                    double heightScaleRatio = (double) (bound.height + topCrop + bottomCrop)
                            / (double) originalStaticImageData.height;
                    for (int i = 0; i < originalImageDataArray.length; i++) {
                        int scaleWidth = (int) (originalImageDataArray[i].width * widthScaleRatio);
                        int scaleHeight = (int) (originalImageDataArray[i].height * heightScaleRatio);
                        int x = (int) (originalImageDataArray[i].x * widthScaleRatio);
                        int y = (int) (originalImageDataArray[i].y * heightScaleRatio);

                        imageDataArray[i] = originalImageDataArray[i].scaledTo(scaleWidth, scaleHeight);
                        imageDataArray[i].x = x;
                        imageDataArray[i].y = y;
                    }

                }
            } else {
                staticImage = new Image(Display.getDefault(), imageData);
                if (animated)
                    imageDataArray = originalImageDataArray;
            }
            imgWidth = staticImage.getBounds().width;
            imgHeight = staticImage.getBounds().height;

            if (animated) {
                if (offScreenImage != null && !offScreenImage.isDisposed())
                    offScreenImage.dispose();
                offScreenImage = new Image(Display.getDefault(), imgWidth, imgHeight);

                if (offScreenImageGC != null && !offScreenImageGC.isDisposed())
                    offScreenImageGC.dispose();
                offScreenImageGC = SingleSourceHelper.getImageGC(offScreenImage);// new GC(offScreenImage);
            }
        }

        // avoid negative number
        leftCrop = leftCrop > imgWidth ? 0 : leftCrop;
        topCrop = topCrop > imgWidth ? 0 : topCrop;
        int cropedWidth = (imgWidth - leftCrop - rightCrop) > 0 ? (imgWidth - leftCrop - rightCrop) : imgWidth;
        int cropedHeight = (imgHeight - topCrop - bottomCrop) > 0 ? (imgHeight - topCrop - bottomCrop) : imgHeight;

        if (leftCrop + cropedWidth > imgWidth || topCrop + cropedHeight > imgHeight)
            return;

        if (workingWithSVG) { // draw refreshing SVG image
            double newScale = gfx.getAbsoluteScale();
            if (newScale != scale) {
                this.scale = newScale;
                resizeImage();
            }
            if (staticImage == null) {
                // Load document if do not exist
                Document document = getDocument();
                if (document == null)
                    return;
                transcoder.setTransformMatrix(permutationMatrix.getMatrix());

                // Scale image
                java.awt.Dimension dims = transcoder.getDocumentSize();
                int imgWidth = dims.width;
                int imgHeight = dims.height;
                if (stretch) {
                    Rectangle bounds = getBounds().getCopy();
                    if (!bounds.equals(0, 0, 0, 0)) {
                        imgWidth = bounds.width;
                        imgHeight = bounds.height;
                    }
                }
                imgWidth = (int) Math.round(scale * (imgWidth + leftCrop + rightCrop));
                imgHeight = (int) Math.round(scale * (imgHeight + bottomCrop + topCrop));
                transcoder.setCanvasSize(imgWidth, imgHeight);

                BufferedImage awtImage = transcoder.getBufferedImage();
                if (awtImage != null)
                    staticImageData = SVGUtils.toSWT(Display.getCurrent(), awtImage);
                if (staticImageData != null)
                    staticImage = new Image(Display.getDefault(), staticImageData);
            }
            // Calculate areas
            imgWidth = staticImage.getBounds().width;
            imgHeight = staticImage.getBounds().height;
            cropedWidth = imgWidth - (int) Math.round(scale * (leftCrop + rightCrop));
            cropedHeight = imgHeight - (int) Math.round(scale * (bottomCrop + topCrop));
            Rectangle srcArea = new Rectangle(leftCrop, topCrop, cropedWidth, cropedHeight);
            Rectangle destArea = new Rectangle(bounds.x, bounds.y, (int) Math.round(cropedWidth / scale),
                    (int) Math.round(cropedHeight / scale));

            // Draw graphic image
            if (staticImage != null)
                gfx.drawImage(staticImage, srcArea, destArea);
            return;
        }

        if (animated) { // draw refreshing image
            if (startAnimationRequested)
                realStartAnimation();
            ImageData imageData = imageDataArray[showIndex];
            Image refresh_image = new Image(Display.getDefault(), imageData);
            switch (imageData.disposalMethod) {
            case SWT.DM_FILL_BACKGROUND:
                /* Fill with the background color before drawing. */
                offScreenImageGC.setBackground(getBackgroundColor());
                offScreenImageGC.fillRectangle(imageData.x, imageData.y, imageData.width, imageData.height);
                break;
            case SWT.DM_FILL_PREVIOUS:
                /* Restore the previous image before drawing. */
                Image startImage = new Image(Display.getDefault(), imageDataArray[0]);
                offScreenImageGC.drawImage(startImage, 0, 0, imageData.width, imageData.height, imageData.x,
                        imageData.y, imageData.width, imageData.height);
                startImage.dispose();
                break;
            }

            offScreenImageGC.drawImage(refresh_image, 0, 0, imageData.width, imageData.height, imageData.x,
                    imageData.y, imageData.width, imageData.height);

            gfx.drawImage(offScreenImage, leftCrop, topCrop, cropedWidth, cropedHeight, bound.x, bound.y,
                    cropedWidth, cropedHeight);
            refresh_image.dispose();
        } else { // draw static image
            if (animated && animationDisabled && offScreenImage != null && showIndex != 0) {
                gfx.drawImage(offScreenImage, leftCrop, topCrop, cropedWidth, cropedHeight, bound.x, bound.y,
                        cropedWidth, cropedHeight);
            } else
                gfx.drawImage(staticImage, leftCrop, topCrop, cropedWidth, cropedHeight, bound.x, bound.y,
                        cropedWidth, cropedHeight);
        }
    }

    /**
     * Resizes the image.
     */
    public synchronized void resizeImage() {
        if (staticImage != null && !staticImage.isDisposed()) {
            staticImage.dispose();
        }
        staticImage = null;
        if (refreshing && animated) {
            stopAnimation();
            startAnimation();
        }
        repaint();
    }

    /**
     * Automatically make the widget bounds be adjusted to the size of the
     * static image
     * 
     * @param autoSize
     */
    public synchronized void setAutoSize(final boolean autoSize) {
        if (!stretch && autoSize)
            resizeImage();
    }

    public synchronized void setAnimationDisabled(final boolean stop) {
        if (animationDisabled == stop)
            return;
        animationDisabled = stop;
        if (stop) {
            stopAnimation();
        } else if (animated) {
            startAnimation();
        }
    }

    /**
     * Sets the amount of pixels, which are cropped from the bottom.
     * 
     * @param newval
     *            The amount of pixels
     */
    public synchronized void setBottomCrop(final int newval) {
        if (bottomCrop == newval //|| (newval + topCrop) >= imgHeight
                || newval < 0 || (newval + topCrop) < 0)
            return;
        bottomCrop = newval;
        resizeImage();
    }

    /**
     * Sets the path to the image.
     * 
     * @param newval
     *            The path to the image
     */
    public synchronized void setFilePath(final IPath newval) {
        if (newval == null)
            return;
        if (animated) {
            stopAnimation();
            animationIndex = 0;
        }
        loadingError = false;
        filePath = newval;
        if (staticImage != null && !staticImage.isDisposed()) {
            staticImage.dispose();
        }
        staticImage = null;
        if (filePath.getFileExtension() != null && "svg".compareToIgnoreCase(filePath.getFileExtension()) == 0) {
            workingWithSVG = true;
            transcoder = null;
            failedToLoadDocument = false;
            loadDocument();
        } else {
            workingWithSVG = false;
            loadImageFromFile();
            if (animated) {
                startAnimation();
            }
        }
    }

    /**
     * Sets the amount of pixels, which are cropped from the left.
     * 
     * @param newval
     *            The amount of pixels
     */
    public synchronized void setLeftCrop(final int newval) {
        if (leftCrop == newval || newval < 0 //|| (newval + rightCrop) > imgWidth //image may not be loaded when this is set
                || (newval + rightCrop) < 0)
            return;
        leftCrop = newval;
        resizeImage();
    }

    /**
     * Sets the amount of pixels, which are cropped from the right.
     * 
     * @param newval
     *            The amount of pixels
     */
    public synchronized void setRightCrop(final int newval) {
        if (rightCrop == newval || newval < 0 //|| (newval + leftCrop) > imgWidth
                || (newval + leftCrop) < 0)
            return;
        rightCrop = newval;
        resizeImage();
    }

    /**
     * @param showIndex
     *            the showIndex to set
     */
    protected synchronized void setShowIndex(int showIndex) {
        if (showIndex >= imageDataArray.length || this.showIndex == showIndex)
            return;
        this.showIndex = showIndex;
        repaint();
    }

    /**
     * Sets the stretch state for the image.
     * 
     * @param newval
     *            The new state (true, if it should be stretched, false
     *            otherwise)
     */
    public synchronized void setStretch(final boolean newval) {
        if (stretch == newval)
            return;
        stretch = newval;
        if (staticImage != null && !staticImage.isDisposed()) {
            staticImage.dispose();
        }
        staticImage = null;
        if (refreshing && animated) {
            stopAnimation();
            startAnimation();
        }
        repaint();
    }

    /**
     * Sets the amount of pixels, which are cropped from the top.
     * 
     * @param newval
     *            The amount of pixels
     */
    public synchronized void setTopCrop(final int newval) {
        if (topCrop == newval || newval < 0
        //            || (newval + bottomCrop) > imgHeight
                || (newval + bottomCrop) < 0)
            return;
        topCrop = newval;
        resizeImage();
    }

    @Override
    public void setBorder(Border border) {
        super.setBorder(border);
        resizeImage();
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        if (visible)
            startAnimation();
        else {
            stopAnimation();
        }
    }

    /**
     * Start animation. The request will be pended until figure painted for the
     * first time.
     */
    public synchronized void startAnimation() {
        startAnimationRequested = true;
        repaint();
    }

    /**
     * start the animation if the image is an animated GIF image.
     */
    public synchronized void realStartAnimation() {
        startAnimationRequested = false;
        if (animated && !refreshing && !animationDisabled) {
            repeatCount = loader.repeatCount;
            // animationIndex = 0;
            lastUpdateTime = 0;
            interval_ms = 0;
            refreshing = true;
            Runnable animationTask = new Runnable() {
                public void run() {
                    UIBundlingThread.getInstance().addRunnable(new Runnable() {

                        public void run() {
                            synchronized (ImageFigure.this) {
                                if (refreshing && (loader.repeatCount == 0 || repeatCount > 0)) {
                                    long currentTime = System.currentTimeMillis();
                                    // use Math.abs() to ensure that the system
                                    // time adjust won't cause problem
                                    if (Math.abs(currentTime - lastUpdateTime) >= interval_ms) {
                                        setShowIndex(animationIndex);
                                        lastUpdateTime = currentTime;
                                        int ms = originalImageDataArray[animationIndex].delayTime * 10;
                                        animationIndex = (animationIndex + 1) % originalImageDataArray.length;
                                        if (ms < 20)
                                            ms += 30;
                                        if (ms < 30)
                                            ms += 10;
                                        interval_ms = ms;
                                        /*
                                         * If we have just drawn the last image,
                                         * decrement the repeat count and start
                                         * again.
                                         */
                                        if (loader.repeatCount > 0
                                                && animationIndex == originalImageDataArray.length - 1)
                                            repeatCount--;
                                    }
                                } else if (loader.repeatCount > 0 && repeatCount <= 0) { // stop thread
                                    // when
                                    // animation
                                    // finished
                                    if (scheduledFuture != null) {
                                        scheduledFuture.cancel(true);
                                        scheduledFuture = null;
                                    }
                                }
                            }

                        }
                    });
                }
            };

            if (scheduledFuture != null) {
                scheduledFuture.cancel(true);
                scheduledFuture = null;
            }
            long initialDelay = 100;
            if (alignedToNearestSecond) {
                Date now = new Date();
                DateUtils.round(now, Calendar.SECOND);
                Date nearestSecond = DateUtils.round(now, Calendar.SECOND);
                initialDelay = nearestSecond.getTime() - now.getTime();
                if (initialDelay < 0)
                    initialDelay = MILLISEC_IN_SEC + initialDelay;
            }
            scheduledFuture = ExecutionService.getInstance().getScheduledExecutorService()
                    .scheduleAtFixedRate(animationTask, initialDelay, 10, TimeUnit.MILLISECONDS);
        }
    }

    public void setAlignedToNearestSecond(boolean aligned) {
        this.alignedToNearestSecond = aligned;
    }

    /**
     * stop the animation if the image is an animated GIF image.
     */
    public synchronized void stopAnimation() {
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
            scheduledFuture = null;
        }
        refreshing = false;
    }

    /**
     * We want to have local coordinates here.
     * 
     * @return True if here should used local coordinates
     */
    @Override
    protected boolean useLocalCoordinates() {
        return true;
    }

    public BeanInfo getBeanInfo() throws IntrospectionException {
        return new DefaultWidgetIntrospector().getBeanInfo(this.getClass());
    }

    public void setPermutationMatrix(final PermutationMatrix permutationMatrix) {
        this.oldPermutationMatrix = this.permutationMatrix;
        this.permutationMatrix = permutationMatrix;
        staticImageData = null; // Reset data
        if ((oldPermutationMatrix != null && oldPermutationMatrix.equals(permutationMatrix))
                || permutationMatrix == null || animated)
            return;
        dispose();
        repaint();
    }

    public PermutationMatrix getPermutationMatrix() {
        return permutationMatrix;
    }

    public void setAbsoluteScale(double newScale) {
        if (this.scale == newScale)
            return;
        this.scale = newScale;
        if (workingWithSVG)
            repaint();
    }

    // ************************************************************
    // SVG specific methods
    // ************************************************************

    private void loadDocument() {
        transcoder = null;
        failedToLoadDocument = true;
        if (filePath == null || filePath.isEmpty())
            return;
        String parser = XMLResourceDescriptor.getXMLParserClassName();
        SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(parser);
        try {
            String uri = "file://" + filePath.toString();
            final InputStream inputStream = ResourceUtil.pathToInputStream(filePath);
            svgDocument = factory.createDocument(uri, inputStream);
            transcoder = new SimpleImageTranscoder(svgDocument);
            initRenderingHints();
            BufferedImage awtImage = transcoder.getBufferedImage();
            if (awtImage != null) {
                originalStaticImageData = SVGUtils.toSWT(Display.getCurrent(), awtImage);
                imgWidth = originalStaticImageData.width;
                imgHeight = originalStaticImageData.height;
            }
            failedToLoadDocument = false;
            resizeImage();
            fireImageLoadedListeners();
        } catch (Exception e) {
            Activator.getLogger().log(Level.WARNING, "Error loading SVG file " + filePath, e);
        }
    }

    private final Document getDocument() {
        if (failedToLoadDocument)
            return null;
        if (transcoder == null)
            loadDocument();
        return transcoder == null ? null : transcoder.getDocument();
    }

    private void initRenderingHints() {
        transcoder.getRenderingHints().put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        transcoder.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        transcoder.getRenderingHints().put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        transcoder.getRenderingHints().put(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        transcoder.getRenderingHints().put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    }

}