Image reflection : Image « Advanced Graphics « Java






Image reflection

Image reflection
  
 
 

/*
 * 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.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * @author Romain Guy <romain.guy@mac.com>
 */
public class ImageReflectionDemoFilthy extends JFrame {
    private ReflectionPanel reflectionPanel;
    private JSlider opacitySlider;
    private JSlider lengthSlider;
    private JSlider radiusSlider;

    public ImageReflectionDemoFilthy() {
        super("Reflections");

        setContentPane(new GradientPanel());

        reflectionPanel = new ReflectionPanel();
        add(reflectionPanel);

        opacitySlider = new JSlider(0, 100, 35);
        opacitySlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                reflectionPanel.setOpacity(opacitySlider.getValue() / 100.0f);
            }
        });

        lengthSlider = new JSlider(0, 100, 40);
        lengthSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                reflectionPanel.setLength(lengthSlider.getValue() / 100.0f);
            }
        });

        radiusSlider = new JSlider(1, 20, 1);
        radiusSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                reflectionPanel.setRadius(radiusSlider.getValue());
            }
        });

        JPanel controls = new JPanel(new GridBagLayout());
        JLabel label;
        controls.setOpaque(false);
        controls.add(label = new JLabel("Opacity: 0%"),
                     new GridBagConstraints(0, 0, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 6, 0, 0),
                                            0, 0));
        label.setForeground(Color.WHITE);
        controls.add(opacitySlider,
                     new GridBagConstraints(1, 0, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 0, 0),
                                            0, 0));
        controls.add(label = new JLabel("100%"),
                     new GridBagConstraints(2, 0, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 0, 0),
                                            0, 0));
        label.setForeground(Color.WHITE);

        controls.add(label = new JLabel("Length: 0%"),
                     new GridBagConstraints(0, 1, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 6, 0, 0),
                                            0, 0));
        label.setForeground(Color.WHITE);
        controls.add(lengthSlider,
                     new GridBagConstraints(1, 1, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 0, 0),
                                            0, 0));
        controls.add(label = new JLabel("100%"),
                     new GridBagConstraints(2, 1, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 0, 0),
                                            0, 0));
        label.setForeground(Color.WHITE);

        controls.add(label = new JLabel("Blur Radius: 1px"),
                     new GridBagConstraints(0, 2, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 6, 0, 0),
                                            0, 0));
        label.setForeground(Color.WHITE);
        controls.add(radiusSlider,
                     new GridBagConstraints(1, 2, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 0, 0),
                                            0, 0));
        controls.add(label = new JLabel("20px"),
                     new GridBagConstraints(2, 2, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 0, 0),
                                            0, 0));
        label.setForeground(Color.WHITE);

        JCheckBox blurCheckBox = new JCheckBox("Blur Enabled");
        blurCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent changeEvent) {
                reflectionPanel.setBlurEnabled(
                        ((JCheckBox) changeEvent.getSource()).isSelected());
            }
        });
        blurCheckBox.setOpaque(false);
        blurCheckBox.setForeground(Color.WHITE);
        controls.add(blurCheckBox,
                     new GridBagConstraints(0, 3, 1, 1, 0.0, 1.0,
                                            GridBagConstraints.LINE_START,
                                            GridBagConstraints.NONE,
                                            new Insets(0, 0, 6, 0),
                                            0, 0));

        //add(controls, BorderLayout.SOUTH);
        reflectionPanel.setLayout(new BorderLayout());
        reflectionPanel.add(controls, BorderLayout.SOUTH);

        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private static class ReflectionPanel extends JPanel {
        private BufferedImage image = null;
        private BufferedImage imageA;
        private ReflectionRenderer renderer = new ReflectionRenderer();

        public ReflectionPanel() {
            try {
                imageA = GraphicsUtilities.loadCompatibleImage(getClass().getResource("A.jpg"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            image = renderer.createReflection(imageA);
            setOpaque(false);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(640, 520);
        }

        @Override
        protected void paintComponent(Graphics g) {
            int x = (getWidth() - imageA.getWidth()) / 2;
            int y = 24;
            if (renderer.isBlurEnabled()) {
                x -= renderer.getEffectiveBlurRadius();
                y -= renderer.getEffectiveBlurRadius() + 1;
            }
            g.drawImage(image, x, y + imageA.getHeight(), null);
            if (renderer.isBlurEnabled()) {
                x += renderer.getEffectiveBlurRadius();
                y += renderer.getEffectiveBlurRadius() + 1;
            }
            g.drawImage(imageA, x, y, null);
        }

        public void setOpacity(float opacity) {
            renderer.setOpacity(opacity);
            image = renderer.createReflection(imageA);
            repaint();
        }

        public void setLength(float length) {
            renderer.setLength(length);
            image = renderer.createReflection(imageA);
            repaint();
        }

        public void setBlurEnabled(boolean selected) {
            renderer.setBlurEnabled(selected);
            image = renderer.createReflection(imageA);
            repaint();
        }

        public void setRadius(int radius) {
            renderer.setBlurRadius(radius);
            image = renderer.createReflection(imageA);
            repaint();
        }
    }

    private static class GradientPanel extends JPanel {
        GradientPanel() {
            super(new BorderLayout());
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            Rectangle clip = g2.getClipBounds();
            Paint paint = g2.getPaint();
            g2.setPaint(new GradientPaint(0.0f, getHeight() * 0.22f,
                                          new Color(0x202737),
                                          0.0f, getHeight() * 0.7f,
                                          Color.BLACK));
            g2.fillRect(clip.x, clip.y, clip.width, clip.height);
            g2.setPaint(paint);
        }
    }

    public static void main(String... args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ImageReflectionDemoFilthy().setVisible(true);
            }
        });
    }
}
/*
 * 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.
 */


/**
 * <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;
        BufferedImage temp = null;

        Graphics2D g2 = null;

        int previousWidth = width;
        int previousHeight = height;

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

            if (temp == null) {
                temp = createCompatibleImage(image, width, height);
                g2 = temp.createGraphics();
                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            }
            g2.drawImage(thumb, 0, 0, width, height,
                         0, 0, previousWidth, previousHeight, null);

            previousWidth = width;
            previousHeight = height;

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

        g2.dispose();

        if (width != thumb.getWidth() || height != thumb.getHeight()) {
            temp = createCompatibleImage(image, width, height);
            g2 = temp.createGraphics();
            g2.drawImage(thumb, 0, 0, null);
            g2.dispose();
            thumb = temp;
        }

        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;
        BufferedImage temp = null;

        Graphics2D g2 = null;

        int previousWidth = width;
        int previousHeight = height;

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

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

            if (temp == null) {
                temp = createCompatibleImage(image, width, height);
                g2 = temp.createGraphics();
                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            }
            g2.drawImage(thumb, 0, 0, width, height,
                         0, 0, previousWidth, previousHeight, null);

            previousWidth = width;
            previousHeight = height;

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

        g2.dispose();

        if (width != thumb.getWidth() || height != thumb.getHeight()) {
            temp = createCompatibleImage(image, width, height);
            g2 = temp.createGraphics();
            g2.drawImage(thumb, 0, 0, null);
            g2.dispose();
            thumb = temp;
        }

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


/**
 * <p>A fast blur filter can be used to blur pictures quickly. This filter is an
 * implementation of the box blur algorithm. The blurs generated by this
 * algorithm might show square artifacts, especially on pictures containing
 * straight lines (rectangles, text, etc.) On most pictures though, the
 * result will look very good.</p>
 * <p>The force of the blur can be controlled with a radius and the
 * default radius is 3. Since the blur clamps values on the edges of the
 * source picture, you might need to provide a picture with empty borders
 * to avoid artifacts at the edges. The performance of this filter are
 * independant from the radius.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
 */
class FastBlurFilter extends AbstractFilter {
    private final int radius;

    /**
     * <p>Creates a new blur filter with a default radius of 3.</p>
     */
    public FastBlurFilter() {
        this(3);
    }

    /**
     * <p>Creates a new blur filter with the specified radius. If the radius
     * is lower than 1, a radius of 1 will be used automatically.</p>
     *
     * @param radius the radius, in pixels, of the blur
     */
    public FastBlurFilter(int radius) {
        if (radius < 1) {
            radius = 1;
        }

        this.radius = radius;
    }

    /**
     * <p>Returns the radius used by this filter, in pixels.</p>
     *
     * @return the radius of the blur
     */
    public int getRadius() {
        return radius;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BufferedImage filter(BufferedImage src, BufferedImage dst) {
        int width = src.getWidth();
        int height = src.getHeight();

        if (dst == null) {
            dst = createCompatibleDestImage(src, null);
        }

        int[] srcPixels = new int[width * height];
        int[] dstPixels = new int[width * height];

        GraphicsUtilities.getPixels(src, 0, 0, width, height, srcPixels);
        // horizontal pass
        blur(srcPixels, dstPixels, width, height, radius);
        // vertical pass
        //noinspection SuspiciousNameCombination
        blur(dstPixels, srcPixels, height, width, radius);
        // the result is now stored in srcPixels due to the 2nd pass
        GraphicsUtilities.setPixels(dst, 0, 0, width, height, srcPixels);

        return dst;
    }

    /**
     * <p>Blurs the source pixels into the destination pixels. The force of
     * the blur is specified by the radius which must be greater than 0.</p>
     * <p>The source and destination pixels arrays are expected to be in the
     * INT_ARGB format.</p>
     * <p>After this method is executed, dstPixels contains a transposed and
     * filtered copy of srcPixels.</p>
     *
     * @param srcPixels the source pixels
     * @param dstPixels the destination pixels
     * @param width the width of the source picture
     * @param height the height of the source picture
     * @param radius the radius of the blur effect
     */
    static void blur(int[] srcPixels, int[] dstPixels,
                     int width, int height, int radius) {
        final int windowSize = radius * 2 + 1;
        final int radiusPlusOne = radius + 1;

        int sumAlpha;
        int sumRed;
        int sumGreen;
        int sumBlue;

        int srcIndex = 0;
        int dstIndex;
        int pixel;

        int[] sumLookupTable = new int[256 * windowSize];
        for (int i = 0; i < sumLookupTable.length; i++) {
            sumLookupTable[i] = i / windowSize;
        }

        int[] indexLookupTable = new int[radiusPlusOne];
        if (radius < width) {
            for (int i = 0; i < indexLookupTable.length; i++) {
                indexLookupTable[i] = i;
            }
        } else {
            for (int i = 0; i < width; i++) {
                indexLookupTable[i] = i;
            }
            for (int i = width; i < indexLookupTable.length; i++) {
                indexLookupTable[i] = width - 1;
            }
        }

        for (int y = 0; y < height; y++) {
            sumAlpha = sumRed = sumGreen = sumBlue = 0;
            dstIndex = y;

            pixel = srcPixels[srcIndex];
            sumAlpha += radiusPlusOne * ((pixel >> 24) & 0xFF);
            sumRed   += radiusPlusOne * ((pixel >> 16) & 0xFF);
            sumGreen += radiusPlusOne * ((pixel >>  8) & 0xFF);
            sumBlue  += radiusPlusOne * ( pixel        & 0xFF);

            for (int i = 1; i <= radius; i++) {
                pixel = srcPixels[srcIndex + indexLookupTable[i]];
                sumAlpha += (pixel >> 24) & 0xFF;
                sumRed   += (pixel >> 16) & 0xFF;
                sumGreen += (pixel >>  8) & 0xFF;
                sumBlue  +=  pixel        & 0xFF;
            }

            for  (int x = 0; x < width; x++) {
                dstPixels[dstIndex] = sumLookupTable[sumAlpha] << 24 |
                                      sumLookupTable[sumRed]   << 16 |
                                      sumLookupTable[sumGreen] <<  8 |
                                      sumLookupTable[sumBlue];
                dstIndex += height;

                int nextPixelIndex = x + radiusPlusOne;
                if (nextPixelIndex >= width) {
                    nextPixelIndex = width - 1;
                }

                int previousPixelIndex = x - radius;
                if (previousPixelIndex < 0) {
                    previousPixelIndex = 0;
                }

                int nextPixel = srcPixels[srcIndex + nextPixelIndex];
                int previousPixel = srcPixels[srcIndex + previousPixelIndex];

                sumAlpha += (nextPixel     >> 24) & 0xFF;
                sumAlpha -= (previousPixel >> 24) & 0xFF;

                sumRed += (nextPixel     >> 16) & 0xFF;
                sumRed -= (previousPixel >> 16) & 0xFF;

                sumGreen += (nextPixel     >> 8) & 0xFF;
                sumGreen -= (previousPixel >> 8) & 0xFF;

                sumBlue += nextPixel & 0xFF;
                sumBlue -= previousPixel & 0xFF;
            }

            srcIndex += width;
        }
    }
}
/*
 * 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.
 */



/**
 * <p>Provides an abstract implementation of the <code>BufferedImageOp</code>
 * interface. This class can be used to created new image filters based
 * on <code>BufferedImageOp</code>.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
 */

abstract class AbstractFilter implements BufferedImageOp {
    public abstract BufferedImage filter(BufferedImage src, BufferedImage dest);

    /**
     * {@inheritDoc}
     */
    public Rectangle2D getBounds2D(BufferedImage src) {
        return new Rectangle(0, 0, src.getWidth(), src.getHeight());
    }

    /**
     * {@inheritDoc}
     */
    public BufferedImage createCompatibleDestImage(BufferedImage src,
                                                   ColorModel destCM) {
        if (destCM == null) {
            destCM = src.getColorModel();
        }

        return new BufferedImage(destCM,
                                 destCM.createCompatibleWritableRaster(
                                         src.getWidth(), src.getHeight()),
                                 destCM.isAlphaPremultiplied(), null);
    }

    /**
     * {@inheritDoc}
     */
    public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
        return (Point2D) srcPt.clone();
    }

    /**
     * {@inheritDoc}
     */
    public RenderingHints getRenderingHints() {
        return null;
    }
}
/*
 * 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.
 */



/**
 * <p>A reflection renderer generates the reflection of a given picture. The
 * result can be either the reflection itself, or an image containing both the
 * source image and its reflection.</p>
 * <h2>Reflection Properties</h2>
 * <p>A reflection is defined by three properties:
 * <ul>
 *   <li><i>opacity</i>: the opacity of the reflection. You will usually
 *   change this valued according to the background color.</li>
 *   <li><i>length</i>: the length of the reflection. The length is a fraction
 *   of the height of the source image.</li>
 *   <li><i>blur enabled</i>: perfect reflections are hardly natural. You can
 *    blur the reflection to make it look a bit more natural.</li>
 * </ul>
 * You can set these properties using the provided mutaters or the appropriate
 * constructor. Here are two ways of creating a blurred reflection, with an
 * opacity of 50% and a length of 30% the height of the original image:
 * <pre>
 * ReflectionRenderer renderer = new ReflectionRenderer(0.5f, 0.3f, true);
 * // ..
 * renderer = new ReflectionRenderer();
 * renderer.setOpacity(0.5f);
 * renderer.setLength(0.3f);
 * renderer.setBlurEnabled(true);
 * </pre>
 * The default constructor provides the following default values:
 * <ul>
 *   <li><i>opacity</i>: 35%</li>
 *   <li><i>length</i>: 40%</li>
 *   <li><i>blur enabled</i>: false</li>
 * </ul></p>
 * <h2>Generating Reflections</h2>
 * <p>A reflection is generated as a <code>BufferedImage</code> from another
 * <code>BufferedImage</code>. Once the renderer is set up, you must call
 * {@link #createReflection(java.awt.image.BufferedImage)} to actually generate
 * the reflection:
 * <pre>
 * ReflectionRenderer renderer = new ReflectionRenderer();
 * // renderer setup
 * BufferedImage reflection = renderer.createReflection(bufferedImage);
 * </pre></p>
 * <p>The returned image contains only the reflection. You will have to append
 * it to the source image at painting time to get a realistic results. You can
 * also asks the rendered to return a picture composed of both the source image
 * and its reflection:
 * <pre>
 * ReflectionRenderer renderer = new ReflectionRenderer();
 * // renderer setup
 * BufferedImage reflection = renderer.appendReflection(bufferedImage);
 * </pre></p>
 * <h2>Properties Changes</h2>
 * <p>This renderer allows to register property change listeners with
 * {@link #addPropertyChangeListener}. Listening to properties changes is very
 * useful when you emebed the renderer in a graphical component and give the API
 * user the ability to access the renderer. By listening to properties changes,
 * you can easily repaint the component when needed.</p>
 * <h2>Threading Issues</h2>
 * <p><code>ReflectionRenderer</code> is not guaranteed to be thread-safe.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
 */
class ReflectionRenderer {
    /**
     * <p>Identifies a change to the opacity used to render the reflection.</p>
     */
    public static final String OPACITY_CHANGED_PROPERTY = "reflection_opacity";

    /**
     * <p>Identifies a change to the length of the rendered reflection.</p>
     */
    public static final String LENGTH_CHANGED_PROPERTY = "reflection_length";

    /**
     * <p>Identifies a change to the blurring of the rendered reflection.</p>
     */
    public static final String BLUR_ENABLED_CHANGED_PROPERTY = "reflection_blur";

    // opacity of the reflection
    private float opacity;

    // length of the reflection
    private float length;

    // should the reflection be blurred?
    private boolean blurEnabled;

    // notifies listeners of properties changes
    private PropertyChangeSupport changeSupport;
    private StackBlurFilter stackBlurFilter;

    /**
     * <p>Creates a default good looking reflections generator.
     * The default reflection renderer provides the following default values:
     * <ul>
     *   <li><i>opacity</i>: 35%</li>
     *   <li><i>length</i>: 40%</li>
     *   <li><i>blurring</i>: disabled with a radius of 1 pixel</li>
     * </ul></p>
     * <p>These properties provide a regular, good looking reflection.</p>
     *
     * @see #getOpacity()
     * @see #setOpacity(float)
     * @see #getLength()
     * @see #setLength(float)
     * @see #isBlurEnabled()
     * @see #setBlurEnabled(boolean)
     * @see #getBlurRadius()
     * @see #setBlurRadius(int)
     */
    public ReflectionRenderer() {
        this(0.35f, 0.4f, false);
    }

    /**
     * <p>Creates a default good looking reflections generator with the
     * specified opacity. The default reflection renderer provides the following
     * default values:
     * <ul>
     *   <li><i>length</i>: 40%</li>
     *   <li><i>blurring</i>: disabled with a radius of 1 pixel</li>
     * </ul></p>
     *
     * @param opacity the opacity of the reflection, between 0.0 and 1.0
     * @see #getOpacity()
     * @see #setOpacity(float)
     * @see #getLength()
     * @see #setLength(float)
     * @see #isBlurEnabled()
     * @see #setBlurEnabled(boolean)
     * @see #getBlurRadius()
     * @see #setBlurRadius(int)
     */
    public ReflectionRenderer(float opacity) {
        this(opacity, 0.4f, false);
    }

    /**
     * <p>Creates a reflections generator with the specified properties. Both
     * opacity and length are numbers between 0.0 (0%) and 1.0 (100%). If the
     * provided numbers are outside this range, they are clamped.</p>
     * <p>Enabling the blur generates a different kind of reflections that might
     * look more natural. The default blur radius is 1 pixel</p>
     *
     * @param opacity the opacity of the reflection
     * @param length the length of the reflection
     * @param blurEnabled if true, the reflection is blurred
     * @see #getOpacity(),#setOpacity(float),#getLength(),#setLength(float)
     * @see #isBlurEnabled(),#setBlurEnabled(boolean)
     * @see #getBlurRadius()
     * @see #setBlurRadius(int)
     */
    public ReflectionRenderer(float opacity, float length, boolean blurEnabled) {
        //noinspection ThisEscapedInObjectConstruction
        this.changeSupport = new PropertyChangeSupport(this);
        this.stackBlurFilter = new StackBlurFilter(1);

        setOpacity(opacity);
        setLength(length);
        setBlurEnabled(blurEnabled);
    }

    /**
     * <p>Add a PropertyChangeListener to the listener list. The listener is
     * registered for all properties. The same listener object may be added
     * more than once, and will be called as many times as it is added. If
     * <code>listener</code> is null, no exception is thrown and no action
     * is taken.</p>
     *
     * @param listener the PropertyChangeListener to be added
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.addPropertyChangeListener(listener);
    }

    /**
     * <p>Remove a PropertyChangeListener from the listener list. This removes
     * a PropertyChangeListener that was registered for all properties. If
     * <code>listener</code> was added more than once to the same event source,
     * it will be notified one less time after being removed. If
     * <code>listener</code> is null, or was never added, no exception is thrown
     * and no action is taken.</p>
     *
     * @param listener the PropertyChangeListener to be removed
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.removePropertyChangeListener(listener);
    }

    /**
     * <p>Gets the opacity used by the factory to generate reflections.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque.</p>
     *
     * @return this factory's shadow opacity
     * @see #getOpacity()
     * @see #createReflection(java.awt.image.BufferedImage)
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public float getOpacity() {
        return opacity;
    }

    /**
     * <p>Sets the opacity used by the factory to generate reflections.</p>
     * <p>Consecutive calls to {@link #createReflection} will all use this
     * opacity until it is set again.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque. If you provide a value out of these
     * boundaries, it will be restrained to the closest boundary.</p>
     *
     * @param opacity the generated reflection opacity
     * @see #setOpacity(float)
     * @see #createReflection(java.awt.image.BufferedImage)
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public void setOpacity(float opacity) {
        float oldOpacity = this.opacity;

        if (opacity < 0.0f) {
            opacity = 0.0f;
        } else if (opacity > 1.0f) {
            opacity = 1.0f;
        }

        if (oldOpacity != opacity) {
            this.opacity = opacity;
            changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY,
                                             oldOpacity,
                                             this.opacity);
        }
    }

    /**
     * <p>Returns the length of the reflection. The result is a number between
     * 0.0 and 1.0. This number is the fraction of the height of the source
     * image that is used to compute the size of the reflection.</p>
     *
     * @return the length of the reflection, as a fraction of the source image
     *   height
     * @see #setLength(float)
     * @see #createReflection(java.awt.image.BufferedImage)
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public float getLength() {
        return length;
    }

    /**
     * <p>Sets the length of the reflection, as a fraction of the height of the
     * source image.</p>
     * <p>Consecutive calls to {@link #createReflection} will all use this
     * opacity until it is set again.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque. If you provide a value out of these
     * boundaries, it will be restrained to the closest boundary.</p>
     *
     * @param length the length of the reflection, as a fraction of the source
     *   image height
     * @see #getLength()
     * @see #createReflection(java.awt.image.BufferedImage)
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public void setLength(float length) {
        float oldLength = this.length;

        if (length < 0.0f) {
            length = 0.0f;
        } else if (length > 1.0f) {
            length = 1.0f;
        }

        if (oldLength != length) {
            this.length = length;
            changeSupport.firePropertyChange(LENGTH_CHANGED_PROPERTY,
                                             oldLength,
                                             this.length);
        }
    }

    /**
     * <p>Returns true if the blurring of the reflection is enabled, false
     * otherwise. When blurring is enabled, the reflection is blurred to look
     * more natural.</p>
     *
     * @return true if blur is enabled, false otherwise
     * @see #setBlurEnabled(boolean)
     * @see #createReflection(java.awt.image.BufferedImage)
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public boolean isBlurEnabled() {
        return blurEnabled;
    }

    /**
     * <p>Setting the blur to true will enable the blurring of the reflection
     * when {@link #createReflection} is invoked.</p>
     * <p>Enabling the blurring of the reflection can yield to more natural
     * results which may or may not be better looking, depending on the source
     * picture.</p>
     * <p>Consecutive calls to {@link #createReflection} will all use this
     * opacity until it is set again.</p>
     *
     * @param blurEnabled true to enable the blur, false otherwise
     * @see #isBlurEnabled()
     * @see #createReflection(java.awt.image.BufferedImage)
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public void setBlurEnabled(boolean blurEnabled) {
        if (blurEnabled != this.blurEnabled) {
            boolean oldBlur = this.blurEnabled;
            this.blurEnabled= blurEnabled;

            changeSupport.firePropertyChange(BLUR_ENABLED_CHANGED_PROPERTY,
                                             oldBlur,
                                             this.blurEnabled);
        }
    }

    /**
     * <p>Returns the effective radius, in pixels, of the blur used by this
     * renderer when {@link #isBlurEnabled()} is true.</p>
     *
     * @return the effective radius of the blur used when
     *   <code>isBlurEnabled</code> is true
     * @see #isBlurEnabled()
     * @see #setBlurEnabled(boolean)
     * @see #setBlurRadius(int)
     * @see #getBlurRadius()
     */
    public int getEffectiveBlurRadius() {
        return stackBlurFilter.getEffectiveRadius();
    }

    /**
     * <p>Returns the radius, in pixels, of the blur used by this renderer when
     * {@link #isBlurEnabled()} is true.</p>
     *
     * @return the radius of the blur used when <code>isBlurEnabled</code>
     *         is true
     * @see #isBlurEnabled()
     * @see #setBlurEnabled(boolean)
     * @see #setBlurRadius(int)
     * @see #getEffectiveBlurRadius()
     */
    public int getBlurRadius() {
        return stackBlurFilter.getRadius();
    }

    /**
     * <p>Sets the radius, in pixels, of the blur used by this renderer when
     * {@link #isBlurEnabled()} is true. This radius changes the size of the
     * generated image when blurring is applied.</p>
     *
     * @param radius the radius, in pixels, of the blur
     * @see #isBlurEnabled()
     * @see #setBlurEnabled(boolean)
     * @see #getBlurRadius()
     */
    public void setBlurRadius(int radius) {
        this.stackBlurFilter = new StackBlurFilter(radius);
    }

    /**
     * <p>Returns the source image and its reflection. The appearance of the
     * reflection is defined by the opacity, the length and the blur
     * properties.</p>
     * * <p>The width of the generated image will be augmented when
     * {@link #isBlurEnabled()} is true. The generated image will have the width
     * of the source image plus twice the effective blur radius (see
     * {@link #getEffectiveBlurRadius()}). The default blur radius is 1 so the
     * width will be augmented by 6. You might need to take this into account
     * at drawing time.</p>
     * <p>The returned image height depends on the value returned by
     * {@link #getLength()} and {@link #getEffectiveBlurRadius()}. For instance,
     * if the length is 0.5 (or 50%) and the source image is 480 pixels high,
     * then the reflection will be 246 (480 * 0.5 + 3 * 2) pixels high.</p>
     * <p>You can create only the reflection by calling
     * {@link #createReflection(java.awt.image.BufferedImage)}.</p>
     *
     * @param image the source image
     * @return the source image with its reflection below
     * @see #createReflection(java.awt.image.BufferedImage)
     */
    public BufferedImage appendReflection(BufferedImage image) {
        BufferedImage reflection = createReflection(image);
        BufferedImage buffer = GraphicsUtilities.createCompatibleTranslucentImage(
                reflection.getWidth(), image.getHeight() + reflection.getHeight());
        Graphics2D g2 = buffer.createGraphics();

        int effectiveRadius = isBlurEnabled() ? stackBlurFilter.getEffectiveRadius() : 0;
        g2.drawImage(image, effectiveRadius, 0, null);
        g2.drawImage(reflection, 0, image.getHeight() - effectiveRadius, null);

        g2.dispose();
        reflection.flush();

        return buffer;
    }

    /**
     * <p>Returns the reflection of the source image. The appearance of the
     * reflection is defined by the opacity, the length and the blur
     * properties.</p>
     * * <p>The width of the generated image will be augmented when
     * {@link #isBlurEnabled()} is true. The generated image will have the width
     * of the source image plus twice the effective blur radius (see
     * {@link #getEffectiveBlurRadius()}). The default blur radius is 1 so the
     * width will be augmented by 6. You might need to take this into account
     * at drawing time.</p>
     * <p>The returned image height depends on the value returned by
     * {@link #getLength()} and {@link #getEffectiveBlurRadius()}. For instance,
     * if the length is 0.5 (or 50%) and the source image is 480 pixels high,
     * then the reflection will be 246 (480 * 0.5 + 3 * 2) pixels high.</p>
     * <p>The returned image contains <strong>only</strong>
     * the reflection. You will have to append it to the source image to produce
     * the illusion of a reflective environement. The method
     * {@link #appendReflection(java.awt.image.BufferedImage)} provides an easy
     * way to create an image containing both the source and the reflection.</p>
     *
     * @param image the source image
     * @return the reflection of the source image
     * @see #appendReflection(java.awt.image.BufferedImage)
     */
    public BufferedImage createReflection(BufferedImage image) {
        if (length == 0.0f) {
            return GraphicsUtilities.createCompatibleTranslucentImage(1, 1);
        }

        int blurOffset = isBlurEnabled() ?
                         stackBlurFilter.getEffectiveRadius() : 0;
        int height = (int) (image.getHeight() * length);

        BufferedImage buffer =
                GraphicsUtilities.createCompatibleTranslucentImage(
                        image.getWidth() + blurOffset * 2,
                        height + blurOffset * 2);
        Graphics2D g2 = buffer.createGraphics();

        g2.translate(0, image.getHeight());
        g2.scale(1.0, -1.0);

        g2.drawImage(image, blurOffset, -blurOffset, null);

        g2.scale(1.0, -1.0);
        g2.translate(0, -image.getHeight());

        g2.setComposite(AlphaComposite.DstIn);
        g2.setPaint(new GradientPaint(0.0f, 0.0f,
                                      new Color(0.0f, 0.0f, 0.0f, getOpacity()),
                                      0.0f, buffer.getHeight(),
                                      new Color(0.0f, 0.0f, 0.0f, 0.0f), true));
        g2.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());

        g2.dispose();
        return isBlurEnabled() ? stackBlurFilter.filter(buffer, null) :
                buffer;
    }
}
/*
 * 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.
 */


/**
 * <p>A stack blur filter can be used to create an approximation of a
 * gaussian blur. The approximation is controlled by the number of times the
 * {@link org.jdesktop.swingx.image.FastBlurFilter} is applied onto the source
 * picture. The default number of iterations, 3, provides a decent compromise
 * between speed and rendering quality.</p>
 * <p>The force of the blur can be controlled with a radius and the
 * default radius is 3. Since the blur clamps values on the edges of the
 * source picture, you might need to provide a picture with empty borders
 * to avoid artifacts at the edges. The performance of this filter are
 * independant from the radius.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
*/
class StackBlurFilter extends AbstractFilter {
    private final int radius;
    private final int iterations;

    /**
     * <p>Creates a new blur filter with a default radius of 3 and 3 iterations.</p>
     */
    public StackBlurFilter() {
        this(3, 3);
    }

    /**
     * <p>Creates a new blur filter with the specified radius and 3 iterations.
     * If the radius is lower than 1, a radius of 1 will be used automatically.</p>
     *
     * @param radius the radius, in pixels, of the blur
     */
    public StackBlurFilter(int radius) {
        this(radius, 3);
    }

    /**
     * <p>Creates a new blur filter with the specified radius. If the radius
     * is lower than 1, a radius of 1 will be used automatically. The number
     * of iterations controls the approximation to a gaussian blur. If the
     * number of iterations is lower than 1, one iteration will be used
     * automatically.</p>
     *
     * @param radius the radius, in pixels, of the blur
     * @param iterations the number of iterations to approximate a gaussian blur
     */
    public StackBlurFilter(int radius, int iterations) {
        if (radius < 1) {
            radius = 1;
        }
        if (iterations < 1) {
            iterations = 1;
        }

        this.radius = radius;
        this.iterations = iterations;
    }

    /**
     * <p>Returns the effective radius of the stack blur. If the radius of the
     * blur is 1 and the stack iterations count is 3, then the effective blur
     * radius is 1 * 3 = 3.</p>
     * @return the number of iterations times the blur radius
     */
    public int getEffectiveRadius() {
        return getIterations() * getRadius();
    }

    /**
     * <p>Returns the radius used by this filter, in pixels.</p>
     *
     * @return the radius of the blur
     */
    public int getRadius() {
        return radius;
    }

    /**
     * <p>Returns the number of iterations used to approximate a gaussian
     * blur.</p>
     *
     * @return the number of iterations used by this blur
     */
    public int getIterations() {
        return iterations;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BufferedImage filter(BufferedImage src, BufferedImage dst) {
        int width = src.getWidth();
        int height = src.getHeight();

        if (dst == null) {
            dst = createCompatibleDestImage(src, null);
        }

        int[] srcPixels = new int[width * height];
        int[] dstPixels = new int[width * height];

        GraphicsUtilities.getPixels(src, 0, 0, width, height, srcPixels);
        for (int i = 0; i < iterations; i++) {
            // horizontal pass
            FastBlurFilter.blur(srcPixels, dstPixels, width, height, radius);
            // vertical pass
            FastBlurFilter.blur(dstPixels, srcPixels, height, width, radius);
        }
        // the result is now stored in srcPixels due to the 2nd pass
        GraphicsUtilities.setPixels(dst, 0, 0, width, height, srcPixels);

        return dst;
    }
}

 

   
    
  








Related examples in the same category

1.Create an image that does not support transparency
2.Create an image that supports transparent pixels
3.Create an image that supports arbitrary levels of transparency
4.Creating a buffered image using Component.createImage().
5.Creating a Buffered Image from an Image
6.Drawing on a Buffered Image
7.If the buffered image supports transparency
8.Converting a Buffered Image (BufferedImage) from an Image
9.Getting and Setting Pixels in a Buffered Image
10.Scaling a Buffered Image
11.Shearing a Buffered Image
12.Translating a Buffered Image
13.Rotating a Buffered Image
14.Flipping a Buffered Image
15.Flip the image horizontally
16.Flip the image vertically and horizontally, equivalent to rotating the image 180 degrees
17.2D Image Draw
18.Transform image
19.Creating a Image Zoomer using Graphics2D
20.Gaussian Blur DemoGaussian Blur Demo
21.Intermediate ImagesIntermediate Images
22.Bloom DemoBloom Demo
23.Box Blur DemoBox Blur Demo
24.Brightness Increase DemoBrightness Increase Demo
25.Blur an ImageBlur an Image
26.Blur our image: Blur means an unfocused image
27.A reflected image: effect makes an illusion as if the image was reflected in water
28.Enlarge Image With AnimationEnlarge Image With Animation
29.Image ZoomingImage Zooming
30.Simple image handling and drawing interactionSimple image handling and drawing interaction
31.Unsharp Mask DemoUnsharp Mask Demo
32.Create a grayscale image with Java 2D tools
33.A 3x3 kernel that embosses an image.
34.A 3x3 kernel that blurs an image.
35.A 3x3 kernel that sharpens an image.
36.Embossing a Buffered Image
37.Brighten the image by 30%
38.Darken the image by 10%
39.Extend RGBImageFilter to create ColorFilter class
40.Extend RGBImageFilter to create AlphaFilter class
41.Enlarging an image by pixel replication
42.Shrinking an image by skipping pixels