/*
* 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.Composite;
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
*/
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
*/
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
is largerimage
or if one of the dimensionsReturns 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.
BufferedImage
containing aimage
newSize
is larger thanimage
or <= 0Returns 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.
BufferedImage
containing aimage
newWidth
is larger thanimage
or if code>newHeight is largerimage or if one the dimensions is not > 0
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.
pixels
if non-null, a new array of integerspixels
is non-null andWrites 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.
pixels
is non-null andA 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.
A shadow is defined by three properties:
*
* ShadowRenderer renderer = new ShadowRenderer(10, 0.5f, Color.GREEN);
* // ..
* renderer = new ShadowRenderer();
* renderer.setSize(10);
* renderer.setOpacity(0.5f);
* renderer.setColor(Color.GREEN);
*
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
*
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.
ShadowRenderer
is not guaranteed to be thread-safe.
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.
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.
Identifies a change to the color used to render the shadow.
Creates a default good looking shadow generator.
* The default shadow renderer provides the following default values:
*
These properties provide a regular, good looking shadow.
A shadow renderer needs three properties to generate shadows.
* These properties are:
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.
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.
Gets the color used by the renderer to generate shadows.
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.
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.
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.
Gets the size in pixel used by the renderer to generate shadows.
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.
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
*
image