Morphing Demo : Animation « Swing Components « Java






Morphing Demo

Morphing Demo

/*
 * Copyright (c) 2007, Romain Guy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.GeneralPath;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jdesktop.animation.timing.triggers.MouseTrigger;
import org.jdesktop.animation.timing.triggers.MouseTriggerEvent;
/**
 *
 * @author Romain Guy <romain.guy@mac.com>
 */
public class MorphingDemo extends JFrame {
    private ImageViewer imageViewer;

    public MorphingDemo() {
        super("Morphing Demo");
        
        add(buildImageViewer());
        add(buildControls(), BorderLayout.SOUTH);
        
        pack();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }
        
    private JComponent buildImageViewer() {
        return imageViewer = new ImageViewer();
    }
    
    private JComponent buildControls() {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        
        JButton button;
        panel.add(button = new DirectionButton("Backward",
                DirectionButton.Direction.LEFT));
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                imageViewer.previous();
            }
        });
        
        panel.add(button = new DirectionButton("Forward",
                DirectionButton.Direction.RIGHT));
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                imageViewer.next();
            }
        });
        
        return panel;
    }
    
    public static class DirectionButton extends JButton {
        public enum Direction {
            LEFT,
            RIGHT
        };
        private DirectionButton.Direction direction;
        private Map desktopHints;
        private float morphing = 0.0f;
        
        private DirectionButton(String text, Direction direction) {
            super(text);
            this.direction = direction;
            
            setupTriggers();
            setFont(getFont().deriveFont(Font.BOLD));
            setOpaque(false);
            setBorderPainted(false);
            setContentAreaFilled(false);
            setFocusPainted(false);
        }
        
        private void setupTriggers() {
            Animator animator = PropertySetter.createAnimator(
                    150, this, "morphing", 0.0f, 1.0f);
            animator.setAcceleration(0.2f);
            animator.setDeceleration(0.3f);
            MouseTrigger.addTrigger(this, animator, MouseTriggerEvent.ENTER, true);
        }
        
        private Morphing2D createMorph() {
            Shape sourceShape = new RoundRectangle2D.Double(2.0, 2.0,
                    getWidth() - 4.0, getHeight() - 4.0, 12.0, 12.0);
            
            GeneralPath.Double destinationShape = new GeneralPath.Double();
            destinationShape.moveTo(2.0, getHeight() / 2.0);
            destinationShape.lineTo(22.0, 0.0);
            destinationShape.lineTo(22.0, 5.0);
            destinationShape.lineTo(getWidth() - 2.0, 5.0);
            destinationShape.lineTo(getWidth() - 2.0, getHeight() - 5.0);
            destinationShape.lineTo(22.0, getHeight() - 5.0);
            destinationShape.lineTo(22.0, getHeight());
            destinationShape.closePath();
            
            return new Morphing2D(sourceShape, destinationShape);
        }
        
        public float getMorphing() {
            return morphing;
        }

        public void setMorphing(float morphing) {
            this.morphing = morphing;
            repaint();
        }
        
        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            
            if (desktopHints == null) {
                Toolkit tk = Toolkit.getDefaultToolkit();
                desktopHints = (Map) (tk.getDesktopProperty("awt.font.desktophints"));
            }

            if (desktopHints != null) {
                g2.addRenderingHints(desktopHints);
            }
            
            LinearGradientPaint p;
            Color[] colors;
            if (!getModel().isArmed()) {
                colors = new Color[] {
                    new Color(0x63a5f7),
                    new Color(0x3799f4),
                    new Color(0x2d7eeb),
                    new Color(0x30a5f9) };
            } else {
                colors = new Color[] {
                    new Color(0x63a5f7).darker(),
                    new Color(0x3799f4).darker(),
                    new Color(0x2d7eeb).darker(),
                    new Color(0x30a5f9).darker() };
            }
            
            p = new LinearGradientPaint(0.0f, 0.0f, 0.0f, getHeight(),
                new float[] { 0.0f, 0.5f, 0.501f, 1.0f },
                colors);
            
            g2.setPaint(p);
            
            Morphing2D morph = createMorph();
            morph.setMorphing(getMorphing());
            if (direction == Direction.RIGHT) {
                g2.translate(getWidth(), 0.0);
                g2.scale(-1.0, 1.0);
            }
            g2.fill(morph);
            if (direction == Direction.RIGHT) {
                g2.scale(-1.0, 1.0);
                g2.translate(-getWidth(), 0.0);
            }
            
            int width = g2.getFontMetrics().stringWidth(getText());
            
            int x = (getWidth() - width) / 2;
            int y = getHeight() / 2 + g2.getFontMetrics().getAscent() / 2 - 1;

            g2.setColor(Color.BLACK);
            g2.drawString(getText(), x, y + 1);
            g2.setColor(Color.WHITE);
            g2.drawString(getText(), x, y);
        }
    }

    public static class ImageViewer extends JComponent {
        private BufferedImage firstImage;
        private BufferedImage secondImage;
        
        private float alpha = 0.0f;
        
        private ImageViewer() {
            try {
                firstImage = GraphicsUtilities.loadCompatibleImage(
                    getClass().getResource("suzhou.jpg"));
                secondImage = GraphicsUtilities.loadCompatibleImage(
                    getClass().getResource("shanghai.jpg"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(firstImage.getWidth(), firstImage.getHeight());
        }
        
        public void next() {
            Animator animator = new Animator(500);
            animator.addTarget(new PropertySetter(this, "alpha", 1.0f));
            animator.setAcceleration(0.2f);
            animator.setDeceleration(0.4f);
            animator.start();
        }
        
        public void previous() {
            Animator animator = new Animator(500);
            animator.addTarget(new PropertySetter(this, "alpha", 0.0f));
            animator.setAcceleration(0.2f);
            animator.setDeceleration(0.4f);
            animator.start();
        }
        
        public void setAlpha(float alpha) {
            this.alpha = alpha;
            repaint();
        }
        
        public float getAlpha() {
            return this.alpha;
        }
        
        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g.create();
            
            g2.setComposite(AlphaComposite.SrcOver.derive(1.0f - alpha));
            g2.drawImage(firstImage, 0, 0, null);
            g2.setComposite(AlphaComposite.SrcOver.derive(alpha));
            g2.drawImage(secondImage, 0, 0, null);
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() { 
                new MorphingDemo().setVisible(true);
            }
        });
    }
}
/*
 * $Id: Morphing2D.java,v 1.1 2007/01/26 17:35:35 gfx Exp $
 *
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * Licensed under LGPL.
 */

/**
 * <p>A morphing shape is a shape which geometry is constructed from two
 * other shapes: a start shape and an end shape.</p>
 * <p>The morphing property of a morphing shape defines the amount of
 * transformation applied to the start shape to turn it into the end shape.</p>
 * <p>Both shapes must have the same winding rule.</p>
 *
 * @author Jim Graham
 * @author Romain Guy <romain.guy@mac.com> (Maintainer)
 */
class Morphing2D implements Shape {
    private double morph;
    private Geometry startGeometry;
    private Geometry endGeometry;

    /**
     * <p>Creates a new morphing shape. A morphing shape can be used to turn
     * one shape into another one. The transformation can be controlled by the
     * morph property.</p>
     *
     * @param startShape the shape to morph from
     * @param endShape   the shape to morph to
     *
     * @throws IllegalPathStateException if the shapes do not have the same
     *                                   winding rule
     * @see #getMorphing()
     * @see #setMorphing(double)
     */
    public Morphing2D(Shape startShape, Shape endShape) {
        startGeometry = new Geometry(startShape);
        endGeometry = new Geometry(endShape);
        if (startGeometry.getWindingRule() != endGeometry.getWindingRule()) {
            throw new IllegalPathStateException("shapes must use same " +
                                                "winding rule");
        }
        double tvals0[] = startGeometry.getTvals();
        double tvals1[] = endGeometry.getTvals();
        double masterTvals[] = mergeTvals(tvals0, tvals1);
        startGeometry.setTvals(masterTvals);
        endGeometry.setTvals(masterTvals);
    }

    /**
     * <p>Returns the morphing value between the two shapes.</p>
     *
     * @return the morphing value between the two shapes
     *
     * @see #setMorphing(double)
     */
    public double getMorphing() {
        return morph;
    }

    /**
     * <p>Sets the morphing value between the two shapes. This value controls
     * the transformation from the start shape to the end shape. A value of 0.0
     * is the start shap. A value of 1.0 is the end shape. A value of 0.5 is a
     * new shape, morphed half way from the start shape to the end shape.</p>
     * <p>The specified value should be between 0.0 and 1.0. If not, the value
     * is clamped in the appropriate range.</p>
     *
     * @param morph the morphing value between the two shapes
     *
     * @see #getMorphing()
     */
    public void setMorphing(double morph) {
        if (morph > 1) {
            morph = 1;
        } else if (morph >= 0) {
            // morphing is finite, not NaN, and in range
        } else {
            // morph is < 0 or NaN
            morph = 0;
        }
        this.morph = morph;
    }

    private static double interp(double v0, double v1, double t) {
        return (v0 + ((v1 - v0) * t));
    }

    private static double[] mergeTvals(double tvals0[], double tvals1[]) {
        int i0 = 0;
        int i1 = 0;
        int numtvals = 0;
        while (i0 < tvals0.length && i1 < tvals1.length) {
            double t0 = tvals0[i0];
            double t1 = tvals1[i1];
            if (t0 <= t1) {
                i0++;
            }
            if (t1 <= t0) {
                i1++;
            }
            numtvals++;
        }
        double newtvals[] = new double[numtvals];
        i0 = 0;
        i1 = 0;
        numtvals = 0;
        while (i0 < tvals0.length && i1 < tvals1.length) {
            double t0 = tvals0[i0];
            double t1 = tvals1[i1];
            if (t0 <= t1) {
                newtvals[numtvals] = t0;
                i0++;
            }
            if (t1 <= t0) {
                newtvals[numtvals] = t1;
                i1++;
            }
            numtvals++;
        }
        return newtvals;
    }

    /**
     * @{inheritDoc}
     */
    public Rectangle getBounds() {
        return getBounds2D().getBounds();
    }

    /**
     * @{inheritDoc}
     */
    public Rectangle2D getBounds2D() {
        int n = startGeometry.getNumCoords();
        double xmin, ymin, xmax, ymax;
        xmin = xmax = interp(startGeometry.getCoord(0), endGeometry.getCoord(0),
                             morph);
        ymin = ymax = interp(startGeometry.getCoord(1), endGeometry.getCoord(1),
                             morph);
        for (int i = 2; i < n; i += 2) {
            double x = interp(startGeometry.getCoord(i),
                              endGeometry.getCoord(i), morph);
            double y = interp(startGeometry.getCoord(i + 1),
                              endGeometry.getCoord(i + 1), morph);
            if (xmin > x) {
                xmin = x;
            }
            if (ymin > y) {
                ymin = y;
            }
            if (xmax < x) {
                xmax = x;
            }
            if (ymax < y) {
                ymax = y;
            }
        }
        return new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin);
    }

    /**
     * @{inheritDoc}
     */
    public boolean contains(double x, double y) {
        throw new InternalError("unimplemented");
    }

    /**
     * @{inheritDoc}
     */
    public boolean contains(Point2D p) {
        return contains(p.getX(), p.getY());
    }

    /**
     * @{inheritDoc}
     */
    public boolean intersects(double x, double y, double w, double h) {
        throw new InternalError("unimplemented");
    }

    /**
     * @{inheritDoc}
     */
    public boolean intersects(Rectangle2D r) {
        return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
    }

    /**
     * @{inheritDoc}
     */
    public boolean contains(double x, double y, double w, double h) {
        throw new InternalError("unimplemented");
    }

    /**
     * @{inheritDoc}
     */
    public boolean contains(Rectangle2D r) {
        return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
    }

    /**
     * @{inheritDoc}
     */
    public PathIterator getPathIterator(AffineTransform at) {
        return new Iterator(at, startGeometry, endGeometry, morph);
    }

    /**
     * @{inheritDoc}
     */
    public PathIterator getPathIterator(AffineTransform at, double flatness) {
        return new FlatteningPathIterator(getPathIterator(at), flatness);
    }

    private static class Geometry {
        static final double THIRD = (1.0 / 3.0);
        static final double MIN_LEN = 0.001;
        double bezierCoords[];
        int numCoords;
        int windingrule;
        double myTvals[];

        public Geometry(Shape s) {
            // Multiple of 6 plus 2 more for initial moveto
            bezierCoords = new double[20];
            PathIterator pi = s.getPathIterator(null);
            windingrule = pi.getWindingRule();
            if (pi.isDone()) {
                // We will have 1 segment and it will be all zeros
                // It will have 8 coordinates (2 for moveto, 6 for cubic)
                numCoords = 8;
            }
            double coords[] = new double[6];
            int type = pi.currentSegment(coords);
            pi.next();
            if (type != PathIterator.SEG_MOVETO) {
                throw new IllegalPathStateException("missing initial moveto");
            }
            double curx = bezierCoords[0] = coords[0];
            double cury = bezierCoords[1] = coords[1];
            double newx, newy;
            numCoords = 2;
            while (!pi.isDone()) {
                if (numCoords + 6 > bezierCoords.length) {
                    // Keep array size to a multiple of 6 plus 2
                    int newsize = (numCoords - 2) * 2 + 2;
                    double newCoords[] = new double[newsize];
                    System.arraycopy(bezierCoords, 0, newCoords, 0, numCoords);
                    bezierCoords = newCoords;
                }
                switch (pi.currentSegment(coords)) {
                    case PathIterator.SEG_MOVETO:
                        throw new InternalError(
                                "Cannot handle multiple subpaths");
                    case PathIterator.SEG_CLOSE:
                        if (curx == bezierCoords[0] && cury == bezierCoords[1])
                        {
                            break;
                        }
                        coords[0] = bezierCoords[0];
                        coords[1] = bezierCoords[1];
                        /* NO BREAK */
                    case PathIterator.SEG_LINETO:
                        newx = coords[0];
                        newy = coords[1];
                        // A third of the way from curxy to newxy:
                        bezierCoords[numCoords++] = interp(curx, newx, THIRD);
                        bezierCoords[numCoords++] = interp(cury, newy, THIRD);
                        // A third of the way from newxy back to curxy:
                        bezierCoords[numCoords++] = interp(newx, curx, THIRD);
                        bezierCoords[numCoords++] = interp(newy, cury, THIRD);
                        bezierCoords[numCoords++] = curx = newx;
                        bezierCoords[numCoords++] = cury = newy;
                        break;
                    case PathIterator.SEG_QUADTO:
                        double ctrlx = coords[0];
                        double ctrly = coords[1];
                        newx = coords[2];
                        newy = coords[3];
                        // A third of the way from ctrlxy back to curxy:
                        bezierCoords[numCoords++] = interp(ctrlx, curx, THIRD);
                        bezierCoords[numCoords++] = interp(ctrly, cury, THIRD);
                        // A third of the way from ctrlxy to newxy:
                        bezierCoords[numCoords++] = interp(ctrlx, newx, THIRD);
                        bezierCoords[numCoords++] = interp(ctrly, newy, THIRD);
                        bezierCoords[numCoords++] = curx = newx;
                        bezierCoords[numCoords++] = cury = newy;
                        break;
                    case PathIterator.SEG_CUBICTO:
                        bezierCoords[numCoords++] = coords[0];
                        bezierCoords[numCoords++] = coords[1];
                        bezierCoords[numCoords++] = coords[2];
                        bezierCoords[numCoords++] = coords[3];
                        bezierCoords[numCoords++] = curx = coords[4];
                        bezierCoords[numCoords++] = cury = coords[5];
                        break;
                }
                pi.next();
            }
            // Add closing segment if either:
            // - we only have initial moveto - expand it to an empty cubic
            // - or we are not back to the starting point
            if ((numCoords < 8) ||
                curx != bezierCoords[0] ||
                cury != bezierCoords[1]) {
                newx = bezierCoords[0];
                newy = bezierCoords[1];
                // A third of the way from curxy to newxy:
                bezierCoords[numCoords++] = interp(curx, newx, THIRD);
                bezierCoords[numCoords++] = interp(cury, newy, THIRD);
                // A third of the way from newxy back to curxy:
                bezierCoords[numCoords++] = interp(newx, curx, THIRD);
                bezierCoords[numCoords++] = interp(newy, cury, THIRD);
                bezierCoords[numCoords++] = newx;
                bezierCoords[numCoords++] = newy;
            }
            // Now find the segment endpoint with the smallest Y coordinate
            int minPt = 0;
            double minX = bezierCoords[0];
            double minY = bezierCoords[1];
            for (int ci = 6; ci < numCoords; ci += 6) {
                double x = bezierCoords[ci];
                double y = bezierCoords[ci + 1];
                if (y < minY || (y == minY && x < minX)) {
                    minPt = ci;
                    minX = x;
                    minY = y;
                }
            }
            // If the smallest Y coordinate is not the first coordinate,
            // rotate the points so that it is...
            if (minPt > 0) {
                // Keep in mind that first 2 coords == last 2 coords
                double newCoords[] = new double[numCoords];
                // Copy all coordinates from minPt to the end of the
                // array to the beginning of the new array
                System.arraycopy(bezierCoords, minPt,
                                 newCoords, 0,
                                 numCoords - minPt);
                // Now we do not want to copy 0,1 as they are duplicates
                // of the last 2 coordinates which we just copied.  So
                // we start the source copy at index 2, but we still
                // copy a full minPt coordinates which copies the two
                // coordinates that were at minPt to the last two elements
                // of the array, thus ensuring that thew new array starts
                // and ends with the same pair of coordinates...
                System.arraycopy(bezierCoords, 2,
                                 newCoords, numCoords - minPt,
                                 minPt);
                bezierCoords = newCoords;
            }
            /* Clockwise enforcement:
             * - This technique is based on the formula for calculating
             *   the area of a Polygon.  The standard formula is:
             *   Area(Poly) = 1/2 * sum(x[i]*y[i+1] - x[i+1]y[i])
             * - The returned area is negative if the polygon is
             *   "mostly clockwise" and positive if the polygon is
             *   "mostly counter-clockwise".
             * - One failure mode of the Area calculation is if the
             *   Polygon is self-intersecting.  This is due to the
             *   fact that the areas on each side of the self-intersection
             *   are bounded by segments which have opposite winding
             *   direction.  Thus, those areas will have opposite signs
             *   on the acccumulation of their area summations and end
             *   up canceling each other out partially.
             * - This failure mode of the algorithm in determining the
             *   exact magnitude of the area is not actually a big problem
             *   for our needs here since we are only using the sign of
             *   the resulting area to figure out the overall winding
             *   direction of the path.  If self-intersections cause
             *   different parts of the path to disagree as to the
             *   local winding direction, that is no matter as we just
             *   wait for the final answer to tell us which winding
             *   direction had greater representation.  If the final
             *   result is zero then the path was equal parts clockwise
             *   and counter-clockwise and we do not care about which
             *   way we order it as either way will require half of the
             *   path to unwind and re-wind itself.
             */
            double area = 0;
            // Note that first and last points are the same so we
            // do not need to process coords[0,1] against coords[n-2,n-1]
            curx = bezierCoords[0];
            cury = bezierCoords[1];
            for (int i = 2; i < numCoords; i += 2) {
                newx = bezierCoords[i];
                newy = bezierCoords[i + 1];
                area += curx * newy - newx * cury;
                curx = newx;
                cury = newy;
            }
            if (area < 0) {
                /* The area is negative so the shape was clockwise
                 * in a Euclidean sense.  But, our screen coordinate
                 * systems have the origin in the upper left so they
                 * are flipped.  Thus, this path "looks" ccw on the
                 * screen so we are flipping it to "look" clockwise.
                 * Note that the first and last points are the same
                 * so we do not need to swap them.
                 * (Not that it matters whether the paths end up cw
                 *  or ccw in the end as long as all of them are the
                 *  same, but above we called this section "Clockwise
                 *  Enforcement", so we do not want to be liars. ;-)
                 */
                // Note that [0,1] do not need to be swapped with [n-2,n-1]
                // So first pair to swap is [2,3] and [n-4,n-3]
                int i = 2;
                int j = numCoords - 4;
                while (i < j) {
                    curx = bezierCoords[i];
                    cury = bezierCoords[i + 1];
                    bezierCoords[i] = bezierCoords[j];
                    bezierCoords[i + 1] = bezierCoords[j + 1];
                    bezierCoords[j] = curx;
                    bezierCoords[j + 1] = cury;
                    i += 2;
                    j -= 2;
                }
            }
        }

        public int getWindingRule() {
            return windingrule;
        }

        public int getNumCoords() {
            return numCoords;
        }

        public double getCoord(int i) {
            return bezierCoords[i];
        }

        public double[] getTvals() {
            if (myTvals != null) {
                return myTvals;
            }

            // assert(numCoords >= 8);
            // assert(((numCoords - 2) % 6) == 0);
            double tvals[] = new double[(numCoords - 2) / 6 + 1];

            // First calculate total "length" of path
            // Length of each segment is averaged between
            // the length between the endpoints (a lower bound for a cubic)
            // and the length of the control polygon (an upper bound)
            double segx = bezierCoords[0];
            double segy = bezierCoords[1];
            double tlen = 0;
            int ci = 2;
            int ti = 0;
            while (ci < numCoords) {
                double prevx, prevy, newx, newy;
                prevx = segx;
                prevy = segy;
                newx = bezierCoords[ci++];
                newy = bezierCoords[ci++];
                prevx -= newx;
                prevy -= newy;
                double len = Math.sqrt(prevx * prevx + prevy * prevy);
                prevx = newx;
                prevy = newy;
                newx = bezierCoords[ci++];
                newy = bezierCoords[ci++];
                prevx -= newx;
                prevy -= newy;
                len += Math.sqrt(prevx * prevx + prevy * prevy);
                prevx = newx;
                prevy = newy;
                newx = bezierCoords[ci++];
                newy = bezierCoords[ci++];
                prevx -= newx;
                prevy -= newy;
                len += Math.sqrt(prevx * prevx + prevy * prevy);
                // len is now the total length of the control polygon
                segx -= newx;
                segy -= newy;
                len += Math.sqrt(segx * segx + segy * segy);
                // len is now sum of linear length and control polygon length
                len /= 2;
                // len is now average of the two lengths

                /* If the result is zero length then we will have problems
                 * below trying to do the math and bookkeeping to split
                 * the segment or pair it against the segments in the
                 * other shape.  Since these lengths are just estimates
                 * to map the segments of the two shapes onto corresponding
                 * segments of "approximately the same length", we will
                 * simply fudge the length of this segment to be at least
                 * a minimum value and it will simply grow from zero or
                 * near zero length to a non-trivial size as it morphs.
                 */
                if (len < MIN_LEN) {
                    len = MIN_LEN;
                }
                tlen += len;
                tvals[ti++] = tlen;
                segx = newx;
                segy = newy;
            }

            // Now set tvals for each segment to its proportional
            // part of the length
            double prevt = tvals[0];
            tvals[0] = 0;
            for (ti = 1; ti < tvals.length - 1; ti++) {
                double nextt = tvals[ti];
                tvals[ti] = prevt / tlen;
                prevt = nextt;
            }
            tvals[ti] = 1;
            return (myTvals = tvals);
        }

        public void setTvals(double newTvals[]) {
            double oldCoords[] = bezierCoords;
            double newCoords[] = new double[2 + (newTvals.length - 1) * 6];
            double oldTvals[] = getTvals();
            int oldci = 0;
            double x0, xc0, xc1, x1;
            double y0, yc0, yc1, y1;
            x0 = xc0 = xc1 = x1 = oldCoords[oldci++];
            y0 = yc0 = yc1 = y1 = oldCoords[oldci++];
            int newci = 0;
            newCoords[newci++] = x0;
            newCoords[newci++] = y0;
            double t0 = 0;
            double t1 = 0;
            int oldti = 1;
            int newti = 1;
            while (newti < newTvals.length) {
                if (t0 >= t1) {
                    x0 = x1;
                    y0 = y1;
                    xc0 = oldCoords[oldci++];
                    yc0 = oldCoords[oldci++];
                    xc1 = oldCoords[oldci++];
                    yc1 = oldCoords[oldci++];
                    x1 = oldCoords[oldci++];
                    y1 = oldCoords[oldci++];
                    t1 = oldTvals[oldti++];
                }
                double nt = newTvals[newti++];
                // assert(nt > t0);
                if (nt < t1) {
                    // Make nt proportional to [t0 => t1] range
                    double relt = (nt - t0) / (t1 - t0);
                    newCoords[newci++] = x0 = interp(x0, xc0, relt);
                    newCoords[newci++] = y0 = interp(y0, yc0, relt);
                    xc0 = interp(xc0, xc1, relt);
                    yc0 = interp(yc0, yc1, relt);
                    xc1 = interp(xc1, x1, relt);
                    yc1 = interp(yc1, y1, relt);
                    newCoords[newci++] = x0 = interp(x0, xc0, relt);
                    newCoords[newci++] = y0 = interp(y0, yc0, relt);
                    xc0 = interp(xc0, xc1, relt);
                    yc0 = interp(yc0, yc1, relt);
                    newCoords[newci++] = x0 = interp(x0, xc0, relt);
                    newCoords[newci++] = y0 = interp(y0, yc0, relt);
                } else {
                    newCoords[newci++] = xc0;
                    newCoords[newci++] = yc0;
                    newCoords[newci++] = xc1;
                    newCoords[newci++] = yc1;
                    newCoords[newci++] = x1;
                    newCoords[newci++] = y1;
                }
                t0 = nt;
            }
            bezierCoords = newCoords;
            numCoords = newCoords.length;
            myTvals = newTvals;
        }
    }

    private static class Iterator implements PathIterator {
        AffineTransform at;
        Geometry g0;
        Geometry g1;
        double t;
        int cindex;

        public Iterator(AffineTransform at,
                        Geometry g0, Geometry g1,
                        double t) {
            this.at = at;
            this.g0 = g0;
            this.g1 = g1;
            this.t = t;
        }

        /**
         * @{inheritDoc}
         */
        public int getWindingRule() {
            return g0.getWindingRule();
        }

        /**
         * @{inheritDoc}
         */
        public boolean isDone() {
            return (cindex > g0.getNumCoords());
        }

        /**
         * @{inheritDoc}
         */
        public void next() {
            if (cindex == 0) {
                cindex = 2;
            } else {
                cindex += 6;
            }
        }

        double dcoords[];

        /**
         * @{inheritDoc}
         */
        public int currentSegment(float[] coords) {
            if (dcoords == null) {
                dcoords = new double[6];
            }
            int type = currentSegment(dcoords);
            if (type != SEG_CLOSE) {
                coords[0] = (float) dcoords[0];
                coords[1] = (float) dcoords[1];
                if (type != SEG_MOVETO) {
                    coords[2] = (float) dcoords[2];
                    coords[3] = (float) dcoords[3];
                    coords[4] = (float) dcoords[4];
                    coords[5] = (float) dcoords[5];
                }
            }
            return type;
        }

        /**
         * @{inheritDoc}
         */
        public int currentSegment(double[] coords) {
            int type;
            int n;
            if (cindex == 0) {
                type = SEG_MOVETO;
                n = 2;
            } else if (cindex >= g0.getNumCoords()) {
                type = SEG_CLOSE;
                n = 0;
            } else {
                type = SEG_CUBICTO;
                n = 6;
            }
            if (n > 0) {
                for (int i = 0; i < n; i++) {
                    coords[i] = interp(g0.getCoord(cindex + i),
                                       g1.getCoord(cindex + i),
                                       t);
                }
                if (at != null) {
                    at.transform(coords, 0, coords, 0, n / 2);
                }
            }
            return type;
        }
    }
}

/**
 * <p><code>GraphicsUtilities</code> contains a set of tools to perform
 * common graphics operations easily. These operations are divided into
 * several themes, listed below.</p>
 * <h2>Compatible Images</h2>
 * <p>Compatible images can, and should, be used to increase drawing
 * performance. This class provides a number of methods to load compatible
 * images directly from files or to convert existing images to compatibles
 * images.</p>
 * <h2>Creating Thumbnails</h2>
 * <p>This class provides a number of methods to easily scale down images.
 * Some of these methods offer a trade-off between speed and result quality and
 * shouuld be used all the time. They also offer the advantage of producing
 * compatible images, thus automatically resulting into better runtime
 * performance.</p>
 * <p>All these methodes are both faster than
 * {@link java.awt.Image#getScaledInstance(int, int, int)} and produce
 * better-looking results than the various <code>drawImage()</code> methods
 * in {@link java.awt.Graphics}, which can be used for image scaling.</p>
 * <h2>Image Manipulation</h2>
 * <p>This class provides two methods to get and set pixels in a buffered image.
 * These methods try to avoid unmanaging the image in order to keep good
 * performance.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
 */
class GraphicsUtilities {
    private GraphicsUtilities() {
    }

    // Returns the graphics configuration for the primary screen
    private static GraphicsConfiguration getGraphicsConfiguration() {
        return GraphicsEnvironment.getLocalGraphicsEnvironment().
                    getDefaultScreenDevice().getDefaultConfiguration();
    }

    /**
     * <p>Returns a new <code>BufferedImage</code> using the same color model
     * as the image passed as a parameter. The returned image is only compatible
     * with the image passed as a parameter. This does not mean the returned
     * image is compatible with the hardware.</p>
     *
     * @param image the reference image from which the color model of the new
     *   image is obtained
     * @return a new <code>BufferedImage</code>, compatible with the color model
     *   of <code>image</code>
     */
    public static BufferedImage createColorModelCompatibleImage(BufferedImage image) {
        ColorModel cm = image.getColorModel();
        return new BufferedImage(cm,
            cm.createCompatibleWritableRaster(image.getWidth(),
                                              image.getHeight()),
            cm.isAlphaPremultiplied(), null);
    }

    /**
     * <p>Returns a new compatible image with the same width, height and
     * transparency as the image specified as a parameter.</p>
     *
     * @see java.awt.Transparency
     * @see #createCompatibleImage(int, int)
     * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
     * @see #createCompatibleTranslucentImage(int, int)
     * @see #loadCompatibleImage(java.net.URL)
     * @see #toCompatibleImage(java.awt.image.BufferedImage)
     * @param image the reference image from which the dimension and the
     *   transparency of the new image are obtained
     * @return a new compatible <code>BufferedImage</code> with the same
     *   dimension and transparency as <code>image</code>
     */
    public static BufferedImage createCompatibleImage(BufferedImage image) {
        return createCompatibleImage(image, image.getWidth(), image.getHeight());
    }

    /**
     * <p>Returns a new compatible image of the specified width and height, and
     * the same transparency setting as the image specified as a parameter.</p>
     *
     * @see java.awt.Transparency
     * @see #createCompatibleImage(java.awt.image.BufferedImage)
     * @see #createCompatibleImage(int, int)
     * @see #createCompatibleTranslucentImage(int, int)
     * @see #loadCompatibleImage(java.net.URL)
     * @see #toCompatibleImage(java.awt.image.BufferedImage)
     * @param width the width of the new image
     * @param height the height of the new image
     * @param image the reference image from which the transparency of the new
     *   image is obtained
     * @return a new compatible <code>BufferedImage</code> with the same
     *   transparency as <code>image</code> and the specified dimension
     */
    public static BufferedImage createCompatibleImage(BufferedImage image,
                                                      int width, int height) {
        return getGraphicsConfiguration().createCompatibleImage(width, height,
                                                   image.getTransparency());
    }

    /**
     * <p>Returns a new opaque compatible image of the specified width and
     * height.</p>
     *
     * @see #createCompatibleImage(java.awt.image.BufferedImage)
     * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
     * @see #createCompatibleTranslucentImage(int, int)
     * @see #loadCompatibleImage(java.net.URL)
     * @see #toCompatibleImage(java.awt.image.BufferedImage)
     * @param width the width of the new image
     * @param height the height of the new image
     * @return a new opaque compatible <code>BufferedImage</code> of the
     *   specified width and height
     */
    public static BufferedImage createCompatibleImage(int width, int height) {
        return getGraphicsConfiguration().createCompatibleImage(width, height);
    }

    /**
     * <p>Returns a new translucent compatible image of the specified width
     * and height.</p>
     *
     * @see #createCompatibleImage(java.awt.image.BufferedImage)
     * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
     * @see #createCompatibleImage(int, int)
     * @see #loadCompatibleImage(java.net.URL)
     * @see #toCompatibleImage(java.awt.image.BufferedImage)
     * @param width the width of the new image
     * @param height the height of the new image
     * @return a new translucent compatible <code>BufferedImage</code> of the
     *   specified width and height
     */
    public static BufferedImage createCompatibleTranslucentImage(int width,
                                                                 int height) {
        return getGraphicsConfiguration().createCompatibleImage(width, height,
                                                   Transparency.TRANSLUCENT);
    }

    /**
     * <p>Returns a new compatible image from a URL. The image is loaded from the
     * specified location and then turned, if necessary into a compatible
     * image.</p>
     *
     * @see #createCompatibleImage(java.awt.image.BufferedImage)
     * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
     * @see #createCompatibleImage(int, int)
     * @see #createCompatibleTranslucentImage(int, int)
     * @see #toCompatibleImage(java.awt.image.BufferedImage)
     * @param resource the URL of the picture to load as a compatible image
     * @return a new translucent compatible <code>BufferedImage</code> of the
     *   specified width and height
     * @throws java.io.IOException if the image cannot be read or loaded
     */
    public static BufferedImage loadCompatibleImage(URL resource)
            throws IOException {
        BufferedImage image = ImageIO.read(resource);
        return toCompatibleImage(image);
    }

    /**
     * <p>Return a new compatible image that contains a copy of the specified
     * image. This method ensures an image is compatible with the hardware,
     * and therefore optimized for fast blitting operations.</p>
     *
     * @see #createCompatibleImage(java.awt.image.BufferedImage)
     * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
     * @see #createCompatibleImage(int, int)
     * @see #createCompatibleTranslucentImage(int, int)
     * @see #loadCompatibleImage(java.net.URL)
     * @param image the image to copy into a new compatible image
     * @return a new compatible copy, with the
     *   same width and height and transparency and content, of <code>image</code>
     */
    public static BufferedImage toCompatibleImage(BufferedImage image) {
        if (image.getColorModel().equals(
                getGraphicsConfiguration().getColorModel())) {
            return image;
        }

        BufferedImage compatibleImage =
                getGraphicsConfiguration().createCompatibleImage(
                    image.getWidth(), image.getHeight(),
                    image.getTransparency());
        Graphics g = compatibleImage.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();

        return compatibleImage;
    }

    /**
     * <p>Returns a thumbnail of a source image. <code>newSize</code> defines
     * the length of the longest dimension of the thumbnail. The other
     * dimension is then computed according to the dimensions ratio of the
     * original picture.</p>
     * <p>This method favors speed over quality. When the new size is less than
     * half the longest dimension of the source image,
     * {@link #createThumbnail(BufferedImage, int)} or
     * {@link #createThumbnail(BufferedImage, int, int)} should be used instead
     * to ensure the quality of the result without sacrificing too much
     * performance.</p>
     *
     * @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
     * @see #createThumbnail(java.awt.image.BufferedImage, int)
     * @see #createThumbnail(java.awt.image.BufferedImage, int, int)
     * @param image the source image
     * @param newSize the length of the largest dimension of the thumbnail
     * @return a new compatible <code>BufferedImage</code> containing a
     *   thumbnail of <code>image</code>
     * @throws IllegalArgumentException if <code>newSize</code> is larger than
     *   the largest dimension of <code>image</code> or &lt;= 0
     */
    public static BufferedImage createThumbnailFast(BufferedImage image,
                                                    int newSize) {
        float ratio;
        int width = image.getWidth();
        int height = image.getHeight();

        if (width > height) {
            if (newSize >= width) {
                throw new IllegalArgumentException("newSize must be lower than" +
                                                   " the image width");
            } else if (newSize <= 0) {
                 throw new IllegalArgumentException("newSize must" +
                                                    " be greater than 0");
            }

            ratio = (float) width / (float) height;
            width = newSize;
            height = (int) (newSize / ratio);
        } else {
            if (newSize >= height) {
                throw new IllegalArgumentException("newSize must be lower than" +
                                                   " the image height");
            } else if (newSize <= 0) {
                 throw new IllegalArgumentException("newSize must" +
                                                    " be greater than 0");
            }

            ratio = (float) height / (float) width;
            height = newSize;
            width = (int) (newSize / ratio);
        }

        BufferedImage temp = createCompatibleImage(image, width, height);
        Graphics2D g2 = temp.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
        g2.dispose();

        return temp;
    }

    /**
     * <p>Returns a thumbnail of a source image.</p>
     * <p>This method favors speed over quality. When the new size is less than
     * half the longest dimension of the source image,
     * {@link #createThumbnail(BufferedImage, int)} or
     * {@link #createThumbnail(BufferedImage, int, int)} should be used instead
     * to ensure the quality of the result without sacrificing too much
     * performance.</p>
     *
     * @see #createThumbnailFast(java.awt.image.BufferedImage, int)
     * @see #createThumbnail(java.awt.image.BufferedImage, int)
     * @see #createThumbnail(java.awt.image.BufferedImage, int, int)
     * @param image the source image
     * @param newWidth the width of the thumbnail
     * @param newHeight the height of the thumbnail
     * @return a new compatible <code>BufferedImage</code> containing a
     *   thumbnail of <code>image</code>
     * @throws IllegalArgumentException if <code>newWidth</code> is larger than
     *   the width of <code>image</code> or if code>newHeight</code> is larger
     *   than the height of <code>image</code> or if one of the dimensions
     *   is &lt;= 0
     */
    public static BufferedImage createThumbnailFast(BufferedImage image,
                                                    int newWidth, int newHeight) {
        if (newWidth >= image.getWidth() ||
            newHeight >= image.getHeight()) {
            throw new IllegalArgumentException("newWidth and newHeight cannot" +
                                               " be greater than the image" +
                                               " dimensions");
        } else if (newWidth <= 0 || newHeight <= 0) {
            throw new IllegalArgumentException("newWidth and newHeight must" +
                                               " be greater than 0");
        }

        BufferedImage temp = createCompatibleImage(image, newWidth, newHeight);
        Graphics2D g2 = temp.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
        g2.dispose();

        return temp;
    }

    /**
     * <p>Returns a thumbnail of a source image. <code>newSize</code> defines
     * the length of the longest dimension of the thumbnail. The other
     * dimension is then computed according to the dimensions ratio of the
     * original picture.</p>
     * <p>This method offers a good trade-off between speed and quality.
     * The result looks better than
     * {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
     * the new size is less than half the longest dimension of the source
     * image, yet the rendering speed is almost similar.</p>
     *
     * @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
     * @see #createThumbnailFast(java.awt.image.BufferedImage, int)
     * @see #createThumbnail(java.awt.image.BufferedImage, int, int)
     * @param image the source image
     * @param newSize the length of the largest dimension of the thumbnail
     * @return a new compatible <code>BufferedImage</code> containing a
     *   thumbnail of <code>image</code>
     * @throws IllegalArgumentException if <code>newSize</code> is larger than
     *   the largest dimension of <code>image</code> or &lt;= 0
     */
    public static BufferedImage createThumbnail(BufferedImage image,
                                                int newSize) {
        int width = image.getWidth();
        int height = image.getHeight();

        boolean isWidthGreater = width > height;

        if (isWidthGreater) {
            if (newSize >= width) {
                throw new IllegalArgumentException("newSize must be lower than" +
                                                   " the image width");
            }
        } else if (newSize >= height) {
            throw new IllegalArgumentException("newSize must be lower than" +
                                               " the image height");
        }

        if (newSize <= 0) {
            throw new IllegalArgumentException("newSize must" +
                                               " be greater than 0");
        }

        float ratioWH = (float) width / (float) height;
        float ratioHW = (float) height / (float) width;

        BufferedImage thumb = image;

        do {
            if (isWidthGreater) {
                width /= 2;
                if (width < newSize) {
                    width = newSize;
                }
                height = (int) (width / ratioWH);
            } else {
                height /= 2;
                if (height < newSize) {
                    height = newSize;
                }
                width = (int) (height / ratioHW);
            }


            BufferedImage temp = createCompatibleImage(image, width, height);
            Graphics2D g2 = temp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
            g2.dispose();

            thumb = temp;
        } while (newSize != (isWidthGreater ? width : height));

        return thumb;
    }

    /**
     * <p>Returns a thumbnail of a source image.</p>
     * <p>This method offers a good trade-off between speed and quality.
     * The result looks better than
     * {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
     * the new size is less than half the longest dimension of the source
     * image, yet the rendering speed is almost similar.</p>
     *
     * @see #createThumbnailFast(java.awt.image.BufferedImage, int)
     * @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
     * @see #createThumbnail(java.awt.image.BufferedImage, int)
     * @param image the source image
     * @param newWidth the width of the thumbnail
     * @param newHeight the height of the thumbnail
     * @return a new compatible <code>BufferedImage</code> containing a
     *   thumbnail of <code>image</code>
     * @throws IllegalArgumentException if <code>newWidth</code> is larger than
     *   the width of <code>image</code> or if code>newHeight</code> is larger
     *   than the height of <code>image or if one the dimensions is not &gt; 0</code>
     */
    public static BufferedImage createThumbnail(BufferedImage image,
                                                int newWidth, int newHeight) {
        int width = image.getWidth();
        int height = image.getHeight();

        if (newWidth >= width || newHeight >= height) {
            throw new IllegalArgumentException("newWidth and newHeight cannot" +
                                               " be greater than the image" +
                                               " dimensions");
        } else if (newWidth <= 0 || newHeight <= 0) {
            throw new IllegalArgumentException("newWidth and newHeight must" +
                                               " be greater than 0");
        }

        BufferedImage thumb = image;

        do {
            if (width > newWidth) {
                width /= 2;
                if (width < newWidth) {
                    width = newWidth;
                }
            }

            if (height > newHeight) {
                height /= 2;
                if (height < newHeight) {
                    height = newHeight;
                }
            }

            BufferedImage temp = createCompatibleImage(image, width, height);
            Graphics2D g2 = temp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
            g2.dispose();

            thumb = temp;
        } while (width != newWidth || height != newHeight);

        return thumb;
    }

    /**
     * <p>Returns an array of pixels, stored as integers, from a
     * <code>BufferedImage</code>. The pixels are grabbed from a rectangular
     * area defined by a location and two dimensions. Calling this method on
     * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
     * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
     *
     * @param img the source image
     * @param x the x location at which to start grabbing pixels
     * @param y the y location at which to start grabbing pixels
     * @param w the width of the rectangle of pixels to grab
     * @param h the height of the rectangle of pixels to grab
     * @param pixels a pre-allocated array of pixels of size w*h; can be null
     * @return <code>pixels</code> if non-null, a new array of integers
     *   otherwise
     * @throws IllegalArgumentException is <code>pixels</code> is non-null and
     *   of length &lt; w*h
     */
    public static int[] getPixels(BufferedImage img,
                                  int x, int y, int w, int h, int[] pixels) {
        if (w == 0 || h == 0) {
            return new int[0];
        }

        if (pixels == null) {
            pixels = new int[w * h];
        } else if (pixels.length < w * h) {
            throw new IllegalArgumentException("pixels array must have a length" +
                                               " >= w*h");
        }

        int imageType = img.getType();
        if (imageType == BufferedImage.TYPE_INT_ARGB ||
            imageType == BufferedImage.TYPE_INT_RGB) {
            Raster raster = img.getRaster();
            return (int[]) raster.getDataElements(x, y, w, h, pixels);
        }

        // Unmanages the image
        return img.getRGB(x, y, w, h, pixels, 0, w);
    }

    /**
     * <p>Writes a rectangular area of pixels in the destination
     * <code>BufferedImage</code>. Calling this method on
     * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
     * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
     *
     * @param img the destination image
     * @param x the x location at which to start storing pixels
     * @param y the y location at which to start storing pixels
     * @param w the width of the rectangle of pixels to store
     * @param h the height of the rectangle of pixels to store
     * @param pixels an array of pixels, stored as integers
     * @throws IllegalArgumentException is <code>pixels</code> is non-null and
     *   of length &lt; w*h
     */
    public static void setPixels(BufferedImage img,
                                 int x, int y, int w, int h, int[] pixels) {
        if (pixels == null || w == 0 || h == 0) {
            return;
        } else if (pixels.length < w * h) {
            throw new IllegalArgumentException("pixels array must have a length" +
                                               " >= w*h");
        }

        int imageType = img.getType();
        if (imageType == BufferedImage.TYPE_INT_ARGB ||
            imageType == BufferedImage.TYPE_INT_RGB) {
            WritableRaster raster = img.getRaster();
            raster.setDataElements(x, y, w, h, pixels);
        } else {
            // Unmanages the image
            img.setRGB(x, y, w, h, pixels, 0, w);
        }
    }
}



           
       








Filthy-Rich-Clients-MorphingDemo.zip( 352 k)

Related examples in the same category

1.Pulse AnimationPulse Animation
2.Pulse Animation FieldPulse Animation Field
3.Fade In ButtonFade In Button
4.Fade In DemoFade In Demo
5.Component Transition AnimationComponent Transition Animation
6.Animation By jdesktop animation
7.Animator Setup
8.Basic Race with AnimationBasic Race with Animation
9.Discrete Interpolation
10.Multi Step RaceMulti Step Race
11.NonLinear Race DemoNonLinear Race Demo
12.Setter Race AnimationSetter Race Animation
13.Spline Interpolator TestSpline Interpolator Test
14.Animation TriggerAnimation Trigger
15.Trigger Race AnimationTrigger Race Animation
16.Tumble Item Project