Java/Advanced Graphics/Shadow

Материал из Java эксперт
Перейти к: навигация, поиск

Drop Shadow Demo

   <source lang="java">

/*

* 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.Color; import java.awt.image.BufferedImage; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; 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.ruposite; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.image.BufferedImage; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.io.IOException; import javax.swing.Box; 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.ru>
*/

public class DropShadowDemo extends JFrame {

   private BlurTestPanel blurTestPanel;
   private JSlider shadowSizeSlider;
   private JSlider shadowOpacitySlider;
   private JCheckBox fastRenderingCheck;
   public DropShadowDemo() {
       super("Drop Shadow");
       blurTestPanel = new BlurTestPanel();
       add(blurTestPanel);
       shadowSizeSlider = new JSlider(1, 20, 5);
       shadowSizeSlider.addChangeListener(new ChangeListener() {
           public void stateChanged(ChangeEvent e) {
               blurTestPanel.setShadowSize(shadowSizeSlider.getValue());
           }
       });
       
       shadowOpacitySlider = new JSlider(0, 100, 50);
       shadowOpacitySlider.addChangeListener(new ChangeListener() {
           public void stateChanged(ChangeEvent e) {
               blurTestPanel.setShadowOpacity((float) shadowOpacitySlider.getValue() / 100.0f);
           }
       });
       
       fastRenderingCheck = new JCheckBox("Fast rendering");
       fastRenderingCheck.addChangeListener(new ChangeListener() {
           public void stateChanged(ChangeEvent e) {
               blurTestPanel.setFastRendering(fastRenderingCheck.isSelected());
           }
       });
       
       JPanel metaControls = new JPanel(new GridLayout(3, 1));
       JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT));
       controls.add(new JLabel("Size: 1px"));
       controls.add(shadowSizeSlider);
       controls.add(new JLabel("20px"));
       metaControls.add(controls);
       
       controls = new JPanel(new FlowLayout(FlowLayout.LEFT));
       controls.add(new JLabel("Opacity: 0%"));
       controls.add(shadowOpacitySlider);
       controls.add(new JLabel("100%"));
       metaControls.add(controls);
       
       controls = new JPanel(new FlowLayout(FlowLayout.LEFT));
       controls.add(fastRenderingCheck);
       metaControls.add(controls);
       add(metaControls, BorderLayout.SOUTH);
       pack();
       setLocationRelativeTo(null);
       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }
   private static class BlurTestPanel extends JPanel {
       private BufferedImage image = null;
       private BufferedImage imageA;
       private int shadowSize = 5;
       private boolean fastRendering = false;
       private float shadowOpacity = 0.5f;
       public BlurTestPanel() {
           try {
               imageA = GraphicsUtilities.loadCompatibleImage(getClass().getResource("A.png"));
           } catch (IOException e) {
               e.printStackTrace();
           }
           setOpaque(false);
       }
       @Override
       public Dimension getPreferredSize() {
           return new Dimension(imageA.getWidth(), imageA.getHeight());
       }
       @Override
       protected void paintComponent(Graphics g) {
           if (image == null) {
               long start = System.nanoTime();
               if (!fastRendering) {
                   image = createDropShadow(imageA, shadowSize);
               } else {
                   ShadowRenderer renderer = new ShadowRenderer(shadowSize / 2, 1.0f, Color.BLACK);
                   image = renderer.createShadow(imageA);
               }
               
               long delay = System.nanoTime() - start;
               System.out.println("time = " + (delay / 1000.0f / 1000.0f) + "ms");
           }
           int x = (getWidth() - imageA.getWidth()) / 2;
           int y = (getHeight() - imageA.getHeight()) / 2;
           
           Graphics2D g2 = (Graphics2D) g;
           Composite c = g2.getComposite();
           g2.setComposite(AlphaComposite.SrcOver.derive(shadowOpacity));
           
           if (!fastRendering) {
               g.drawImage(image, x - shadowSize * 2 + 5, y - shadowSize * 2 + 5, null);
           } else {
               g.drawImage(image, x - shadowSize / 2 + 5, y - shadowSize / 2 + 5, null);
           }
           
           g2.setComposite(c);
           
           g.drawImage(imageA, x, y, null);
       }
       public void setShadowSize(int radius) {
           this.shadowSize = radius;
           image = null;
           repaint();
       }
       private void setFastRendering(boolean fastRendering) {
           this.fastRendering = fastRendering;
           image = null;
           repaint();
       }
       private void setShadowOpacity(float shadowOpacity) {
           this.shadowOpacity = shadowOpacity;
           image = null;
           repaint();
       }
   }
   
   public static BufferedImage createDropShadow(BufferedImage image,
           int size) {
       BufferedImage shadow = new BufferedImage(
           image.getWidth() + 4 * size,
           image.getHeight() + 4 * size,
           BufferedImage.TYPE_INT_ARGB);
       
       Graphics2D g2 = shadow.createGraphics();
       g2.drawImage(image, size * 2, size * 2, null);
       
       g2.setComposite(AlphaComposite.SrcIn);
       g2.setColor(Color.BLACK);
       g2.fillRect(0, 0, shadow.getWidth(), shadow.getHeight());       
       
       g2.dispose();
       
       shadow = getGaussianBlurFilter(size, true).filter(shadow, null);
       shadow = getGaussianBlurFilter(size, false).filter(shadow, null);
       
       return shadow;
   }
   
   public static ConvolveOp getGaussianBlurFilter(int radius,
           boolean horizontal) {
       if (radius < 1) {
           throw new IllegalArgumentException("Radius must be >= 1");
       }
       
       int size = radius * 2 + 1;
       float[] data = new float[size];
       
       float sigma = radius / 3.0f;
       float twoSigmaSquare = 2.0f * sigma * sigma;
       float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
       float total = 0.0f;
       
       for (int i = -radius; i <= radius; i++) {
           float distance = i * i;
           int index = i + radius;
           data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
           total += data[index];
       }
       
       for (int i = 0; i < data.length; i++) {
           data[i] /= total;
       }        
       
       Kernel kernel = null;
       if (horizontal) {
           kernel = new Kernel(size, 1, data);
       } else {
           kernel = new Kernel(1, size, data);
       }
       return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
   }
   
   public static void main(String... args) {
       SwingUtilities.invokeLater(new Runnable() {
           public void run() {
               new DropShadowDemo().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.
*/

/**

*

GraphicsUtilities contains a set of tools to perform * common graphics operations easily. These operations are divided into * several themes, listed below.

*

Compatible Images

*

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.

*

Creating Thumbnails

*

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.

*

All these methodes are both faster than * {@link java.awt.Image#getScaledInstance(int, int, int)} and produce * better-looking results than the various drawImage() methods * in {@link java.awt.Graphics}, which can be used for image scaling.

*

Image Manipulation

*

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.

*
* @author Romain Guy <romain.guy@mac.ru>
*/

class GraphicsUtilities {

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

Returns a new BufferedImage 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.

    *
    * @param image the reference image from which the color model of the new
    *   image is obtained
    * @return a new BufferedImage, compatible with the color model
    *   of image
    */
   public static BufferedImage createColorModelCompatibleImage(BufferedImage image) {
       ColorModel cm = image.getColorModel();
       return new BufferedImage(cm,
           cm.createCompatibleWritableRaster(image.getWidth(),
                                             image.getHeight()),
           cm.isAlphaPremultiplied(), null);
   }
   /**
*

Returns a new compatible image with the same width, height and * transparency as the image specified as a parameter.

    *
    * @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 BufferedImage with the same
    *   dimension and transparency as image
    */
   public static BufferedImage createCompatibleImage(BufferedImage image) {
       return createCompatibleImage(image, image.getWidth(), image.getHeight());
   }
   /**
*

Returns a new compatible image of the specified width and height, and * the same transparency setting as the image specified as a parameter.

    *
    * @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 BufferedImage with the same
    *   transparency as image and the specified dimension
    */
   public static BufferedImage createCompatibleImage(BufferedImage image,
                                                     int width, int height) {
       return getGraphicsConfiguration().createCompatibleImage(width, height,
                                                  image.getTransparency());
   }
   /**
*

Returns a new opaque compatible image of the specified width and * height.

    *
    * @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 BufferedImage of the
    *   specified width and height
    */
   public static BufferedImage createCompatibleImage(int width, int height) {
       return getGraphicsConfiguration().createCompatibleImage(width, height);
   }
   /**
*

Returns a new translucent compatible image of the specified width * and height.

    *
    * @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 BufferedImage of the
    *   specified width and height
    */
   public static BufferedImage createCompatibleTranslucentImage(int width,
                                                                int height) {
       return getGraphicsConfiguration().createCompatibleImage(width, height,
                                                  Transparency.TRANSLUCENT);
   }
   /**
*

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.

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

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.

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

Returns a thumbnail of a source image. newSize 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.

*

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.

    *
    * @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 BufferedImage containing a
    *   thumbnail of image
    * @throws IllegalArgumentException if newSize is larger than
    *   the largest dimension of image or <= 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;
   }
   /**
*

Returns a thumbnail of a source image.

*

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.

    *
    * @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 BufferedImage containing a
    *   thumbnail of image
    * @throws IllegalArgumentException if newWidth is larger than
    *   the width of image or if code>newHeight</code> is larger
    *   than the height of image or if one of the dimensions
    *   is <= 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;
   }
   /**
*

Returns a thumbnail of a source image. newSize 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.

*

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.

    *
    * @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 BufferedImage containing a
    *   thumbnail of image
    * @throws IllegalArgumentException if newSize is larger than
    *   the largest dimension of image or <= 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;
   }
   /**
*

Returns a thumbnail of a source image.

*

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.

    *
    * @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 BufferedImage containing a
    *   thumbnail of image
    * @throws IllegalArgumentException if newWidth is larger than
    *   the width of image or if code>newHeight</code> is larger
    *   than the height of image or if one the dimensions is not > 0
    */
   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;
   }
   /**
*

Returns an array of pixels, stored as integers, from a * BufferedImage. 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 BufferedImage.TYPE_INT_ARGB * and BufferedImage.TYPE_INT_RGB will unmanage the image.

    *
    * @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 pixels if non-null, a new array of integers
    *   otherwise
    * @throws IllegalArgumentException is pixels is non-null and
    *   of length < 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);
   }
   /**
*

Writes a rectangular area of pixels in the destination * BufferedImage. Calling this method on * an image of type different from BufferedImage.TYPE_INT_ARGB * and BufferedImage.TYPE_INT_RGB will unmanage the image.

    *
    * @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 pixels is non-null and
    *   of length < 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);
       }
   }

} /*

* $Id: ShadowRenderer.java,v 1.1 2007/01/15 23:39:23 gfx Exp $
*
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* Licensed under LGPL.
*/

/**

*

A shadow renderer generates a drop shadow for any given picture, respecting * the transparency channel if present. The resulting picture contains the * shadow only and to create a drop shadow effect you will need to stack the * original picture and the shadow generated by the renderer.

*

Shadow Properties

*

A shadow is defined by three properties: *

    *
  • size: The size, in pixels, of the shadow. This property also * defines the fuzzyness.
  • *
  • opacity: The opacity, between 0.0 and 1.0, of the shadow.
  • *
  • color: The color of the shadow. Shadows are not meant to be * black only.
  • *
* You can set these properties using the provided mutaters or the appropriate
* constructor. Here are two ways of creating a green shadow of size 10 and
* with an opacity of 50%:
*
 * ShadowRenderer renderer = new ShadowRenderer(10, 0.5f, Color.GREEN);
 * // ..
 * renderer = new ShadowRenderer();
 * renderer.setSize(10);
 * renderer.setOpacity(0.5f);
 * renderer.setColor(Color.GREEN);
 * 
* The default constructor provides the following default values:
*
    *
  • size: 5 pixels
  • *
  • opacity: 50%
  • *
  • color: Black
  • *

*

Generating a Shadow

*

A shadow is generated as a BufferedImage from another * BufferedImage. Once the renderer is set up, you must call * {@link #createShadow} to actually generate the shadow: *

 * ShadowRenderer renderer = new ShadowRenderer();
 * // renderer setup
 * BufferedImage shadow = renderer.createShadow(bufferedImage);
 * 

*

The generated image dimensions are computed as following:

*
 * width  = imageWidth  + 2 * shadowSize
 * height = imageHeight + 2 * shadowSize
 * 
*

Properties Changes

*

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.

*

Threading Issues

*

ShadowRenderer is not guaranteed to be thread-safe.

* 
* @author Romain Guy <romain.guy@mac.ru>
* @author Sebastien Petrucci
*/
class ShadowRenderer {
   /**
*

Identifies a change to the size used to render the shadow.

*

When the property change event is fired, the old value and the new * value are provided as Integer instances.

    */
   public static final String SIZE_CHANGED_PROPERTY = "shadow_size";
   
   /**
*

Identifies a change to the opacity used to render the shadow.

*

When the property change event is fired, the old value and the new * value are provided as Float instances.

    */
   public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity";
   
   /**
*

Identifies a change to the color used to render the shadow.

    */
   public static final String COLOR_CHANGED_PROPERTY = "shadow_color";
   // size of the shadow in pixels (defines the fuzziness)
   private int size = 5;
   
   // opacity of the shadow
   private float opacity = 0.5f;
   
   // color of the shadow
   private Color color = Color.BLACK;
   
   // notifies listeners of properties changes
   private PropertyChangeSupport changeSupport;
   /**
*

Creates a default good looking shadow generator. * The default shadow renderer provides the following default values: *

    *
  • size: 5 pixels
  • *
  • opacity: 50%
  • *
  • color: Black
  • *

*

These properties provide a regular, good looking shadow.

    */
   public ShadowRenderer() {
       this(5, 0.5f, Color.BLACK);
   }
   
   /**
*

A shadow renderer needs three properties to generate shadows. * These properties are:

*
    *
  • size: The size, in pixels, of the shadow. This property also * defines the fuzzyness.
  • *
  • opacity: The opacity, between 0.0 and 1.0, of the shadow.
  • *
  • color: The color of the shadow. Shadows are not meant to be * black only.
  • *
    * @param size the size of the shadow in pixels. Defines the fuzziness.
    * @param opacity the opacity of the shadow.
    * @param color the color of the shadow.
    */
   public ShadowRenderer(final int size, final float opacity, final Color color) {
       //noinspection ThisEscapedInObjectConstruction
       changeSupport = new PropertyChangeSupport(this);
       setSize(size);
       setOpacity(opacity);
       setColor(color);
   }
   /**
*

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 * listener is null, no exception is thrown and no action * is taken.

    * @param listener the PropertyChangeListener to be added
    */
   public void addPropertyChangeListener(PropertyChangeListener listener) {
       changeSupport.addPropertyChangeListener(listener);
   }
   /**
*

Remove a PropertyChangeListener from the listener list. This removes * a PropertyChangeListener that was registered for all properties. If * listener was added more than once to the same event source, * it will be notified one less time after being removed. If * listener is null, or was never added, no exception is thrown * and no action is taken.

    * @param listener the PropertyChangeListener to be removed
    */
   public void removePropertyChangeListener(PropertyChangeListener listener) {
       changeSupport.removePropertyChangeListener(listener);
   }
   /**
*

Gets the color used by the renderer to generate shadows.

    * @return this renderer"s shadow color
    */
   public Color getColor() {
       return color;
   }
   /**
*

Sets the color used by the renderer to generate shadows.

*

Consecutive calls to {@link #createShadow} will all use this color * until it is set again.

*

If the color provided is null, the previous color will be retained.

    * @param shadowColor the generated shadows color
    */
   public void setColor(final Color shadowColor) {
       if (shadowColor != null) {
           Color oldColor = this.color;
           this.color = shadowColor;
           changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY,
                                            oldColor,
                                            this.color);
       }
   }
   /**
*

Gets the opacity used by the renderer to generate shadows.

*

The opacity is comprised between 0.0f and 1.0f; 0.0f being fully * transparent and 1.0f fully opaque.

    * @return this renderer"s shadow opacity
    */
   public float getOpacity() {
       return opacity;
   }
   /**
*

Sets the opacity used by the renderer to generate shadows.

*

Consecutive calls to {@link #createShadow} will all use this opacity * until it is set again.

*

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.

    * @param shadowOpacity the generated shadows opacity
    */
   public void setOpacity(final float shadowOpacity) {
       float oldOpacity = this.opacity;
       
       if (shadowOpacity < 0.0) {
           this.opacity = 0.0f;
       } else if (shadowOpacity > 1.0f) {
           this.opacity = 1.0f;
       } else {
           this.opacity = shadowOpacity;
       }
       
       changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY,
                                        oldOpacity,
                                        this.opacity);
   }
   /**
*

Gets the size in pixel used by the renderer to generate shadows.

    * @return this renderer"s shadow size
    */
   public int getSize() {
       return size;
   }
   /**
*

Sets the size, in pixels, used by the renderer to generate shadows.

*

The size defines the blur radius applied to the shadow to create the * fuzziness.

*

There is virtually no limit to the size. The size cannot be negative. * If you provide a negative value, the size will be 0 instead.

    * @param shadowSize the generated shadows size in pixels (fuzziness)
    */
   public void setSize(final int shadowSize) {
       int oldSize = this.size;
       
       if (shadowSize < 0) {
           this.size = 0;
       } else {
           this.size = shadowSize;
       }
       
       changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY,
                                        new Integer(oldSize),
                                        new Integer(this.size));
   }
   /**
*

Generates the shadow for a given picture and the current properties * of the renderer.

*

The generated image dimensions are computed as following:

*
     * width  = imageWidth  + 2 * shadowSize
     * height = imageHeight + 2 * shadowSize
     * 
    * @param image the picture from which the shadow must be cast
    * @return the picture containing the shadow of image 
    */
   public BufferedImage createShadow(final BufferedImage image) {
       // Written by Sesbastien Petrucci
       int shadowSize = size * 2;
       int srcWidth = image.getWidth();
       int srcHeight = image.getHeight();
       int dstWidth = srcWidth + shadowSize;
       int dstHeight = srcHeight + shadowSize;
       int left = size;
       int right = shadowSize - left;
       int yStop = dstHeight - right;
       int shadowRgb = color.getRGB() & 0x00FFFFFF;
       int[] aHistory = new int[shadowSize];
       int historyIdx;
       int aSum;
       BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
                                             BufferedImage.TYPE_INT_ARGB);
       int[] dstBuffer = new int[dstWidth * dstHeight];
       int[] srcBuffer = new int[srcWidth * srcHeight];
       GraphicsUtilities.getPixels(image, 0, 0, srcWidth, srcHeight, srcBuffer);
       int lastPixelOffset = right * dstWidth;
       float hSumDivider = 1.0f / shadowSize;
       float vSumDivider = opacity / shadowSize;
       int[] hSumLookup = new int[256 * shadowSize];
       for (int i = 0; i < hSumLookup.length; i++) {
           hSumLookup[i] = (int) (i * hSumDivider);
       }
       int[] vSumLookup = new int[256 * shadowSize];
       for (int i = 0; i < vSumLookup.length; i++) {
           vSumLookup[i] = (int) (i * vSumDivider);
       }
       int srcOffset;
       // horizontal pass : extract the alpha mask from the source picture and
       // blur it into the destination picture
       for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {
           // first pixels are empty
           for (historyIdx = 0; historyIdx < shadowSize; ) {
               aHistory[historyIdx++] = 0;
           }
           aSum = 0;
           historyIdx = 0;
           srcOffset = srcY * srcWidth;
           // compute the blur average with pixels from the source image
           for (int srcX = 0; srcX < srcWidth; srcX++) {
               int a = hSumLookup[aSum];
               dstBuffer[dstOffset++] = a << 24;   // store the alpha value only
                                                   // the shadow color will be added in the next pass
               aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
               // extract the new pixel ...
               a = srcBuffer[srcOffset + srcX] >>> 24;
               aHistory[historyIdx] = a;   // ... and store its value into history
               aSum += a;                  // ... and add its value to the sum
               if (++historyIdx >= shadowSize) {
                   historyIdx -= shadowSize;
               }
           }
           // blur the end of the row - no new pixels to grab
           for (int i = 0; i < shadowSize; i++) {
               int a = hSumLookup[aSum];
               dstBuffer[dstOffset++] = a << 24;
               // substract the oldest pixel from the sum ... and nothing new to add !
               aSum -= aHistory[historyIdx];
               if (++historyIdx >= shadowSize) {
                   historyIdx -= shadowSize;
               }
           }
       }
       // vertical pass
       for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
           aSum = 0;
           // first pixels are empty
           for (historyIdx = 0; historyIdx < left;) {
               aHistory[historyIdx++] = 0;
           }
           // and then they come from the dstBuffer
           for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
               int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha
               aHistory[historyIdx++] = a;                     // store into history
               aSum += a;                                      // and add to sum
           }
           bufferOffset = x;
           historyIdx = 0;
           // compute the blur avera`ge with pixels from the previous pass
           for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {
               int a = vSumLookup[aSum];
               dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color
               aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
               a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...
               aHistory[historyIdx] = a;                               // ... and store its value into history
               aSum += a;                                              // ... and add its value to the sum
               if (++historyIdx >= shadowSize) {
                   historyIdx -= shadowSize;
               }
           }
           // blur the end of the column - no pixels to grab anymore
           for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {
               int a = vSumLookup[aSum];
               dstBuffer[bufferOffset] = a << 24 | shadowRgb;
               aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
               if (++historyIdx >= shadowSize) {
                   historyIdx -= shadowSize;
               }
           }
       }
       GraphicsUtilities.setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer);
       return dst;
   }

}


</source>