001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------------------
028 * DefaultShadowGenerator.java
029 * ---------------------------
030 * (C) Copyright 2009-2013 by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 10-Jul-2009 : Version 1 (DG);
038 * 29-Oct-2011 : Fixed Eclipse warnings (DG);
039 * 03-Jul-2013 : Use ParamChecks (DG);
040 *
041 */
042
043package org.jfree.chart.util;
044
045import java.awt.Color;
046import java.awt.Graphics2D;
047import java.awt.image.BufferedImage;
048import java.awt.image.DataBufferInt;
049import java.io.Serializable;
050import org.jfree.chart.HashUtilities;
051
052/**
053 * A default implementation of the {@link ShadowGenerator} interface, based on
054 * code in a 
055 * <a href="http://www.jroller.com/gfx/entry/fast_or_good_drop_shadows">blog
056 * post by Romain Guy</a>.
057 *
058 * @since 1.0.14
059 */
060public class DefaultShadowGenerator implements ShadowGenerator, Serializable {
061
062    private static final long serialVersionUID = 2732993885591386064L;
063
064    /** The shadow size. */
065    private int shadowSize;
066
067    /** The shadow color. */
068    private Color shadowColor;
069
070    /** The shadow opacity. */
071    private float shadowOpacity;
072
073    /** The shadow offset angle (in radians). */
074    private double angle;
075
076    /** The shadow offset distance (in Java2D units). */
077    private int distance;
078
079    /**
080     * Creates a new instance with default attributes.
081     */
082    public DefaultShadowGenerator() {
083        this(5, Color.black, 0.5f, 5, -Math.PI / 4);
084    }
085
086    /**
087     * Creates a new instance with the specified attributes.
088     *
089     * @param size  the shadow size.
090     * @param color  the shadow color.
091     * @param opacity  the shadow opacity.
092     * @param distance  the shadow offset distance.
093     * @param angle  the shadow offset angle (in radians).
094     */
095    public DefaultShadowGenerator(int size, Color color, float opacity,
096            int distance, double angle) {
097        ParamChecks.nullNotPermitted(color, "color");
098        this.shadowSize = size;
099        this.shadowColor = color;
100        this.shadowOpacity = opacity;
101        this.distance = distance;
102        this.angle = angle;
103    }
104
105    /**
106     * Returns the shadow size.
107     *
108     * @return The shadow size.
109     */
110    public int getShadowSize() {
111        return this.shadowSize;
112    }
113
114    /**
115     * Returns the shadow color.
116     *
117     * @return The shadow color (never <code>null</code>).
118     */
119    public Color getShadowColor() {
120        return this.shadowColor;
121    }
122
123    /**
124     * Returns the shadow opacity.
125     *
126     * @return The shadow opacity.
127     */
128    public float getShadowOpacity() {
129        return this.shadowOpacity;
130    }
131
132    /**
133     * Returns the shadow offset distance.
134     *
135     * @return The shadow offset distance (in Java2D units).
136     */
137    public int getDistance() {
138        return this.distance;
139    }
140
141    /**
142     * Returns the shadow offset angle (in radians).
143     *
144     * @return The angle (in radians).
145     */
146    public double getAngle() {
147        return this.angle;
148    }
149
150    /**
151     * Calculates the x-offset for drawing the shadow image relative to the
152     * source.
153     *
154     * @return The x-offset.
155     */
156    @Override
157    public int calculateOffsetX() {
158        return (int) (Math.cos(this.angle) * this.distance) - this.shadowSize;
159    }
160
161    /**
162     * Calculates the y-offset for drawing the shadow image relative to the
163     * source.
164     *
165     * @return The y-offset.
166     */
167    @Override
168    public int calculateOffsetY() {
169        return -(int) (Math.sin(this.angle) * this.distance) - this.shadowSize;
170    }
171
172    /**
173     * Creates and returns an image containing the drop shadow for the
174     * specified source image.
175     *
176     * @param source  the source image.
177     *
178     * @return A new image containing the shadow.
179     */
180    @Override
181    public BufferedImage createDropShadow(BufferedImage source) {
182        BufferedImage subject = new BufferedImage(
183                source.getWidth() + this.shadowSize * 2,
184                source.getHeight() + this.shadowSize * 2,
185                BufferedImage.TYPE_INT_ARGB);
186
187        Graphics2D g2 = subject.createGraphics();
188        g2.drawImage(source, null, this.shadowSize, this.shadowSize);
189        g2.dispose();
190        applyShadow(subject);
191        return subject;
192    }
193
194    /**
195     * Applies a shadow to the image.
196     *
197     * @param image  the image.
198     */
199    protected void applyShadow(BufferedImage image) {
200        int dstWidth = image.getWidth();
201        int dstHeight = image.getHeight();
202
203        int left = (this.shadowSize - 1) >> 1;
204        int right = this.shadowSize - left;
205        int xStart = left;
206        int xStop = dstWidth - right;
207        int yStart = left;
208        int yStop = dstHeight - right;
209
210        int shadowRgb = this.shadowColor.getRGB() & 0x00FFFFFF;
211
212        int[] aHistory = new int[this.shadowSize];
213        int historyIdx;
214
215        int aSum;
216
217        int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
218        int lastPixelOffset = right * dstWidth;
219        float sumDivider = this.shadowOpacity / this.shadowSize;
220
221        // horizontal pass
222
223        for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
224            aSum = 0;
225            historyIdx = 0;
226            for (int x = 0; x < this.shadowSize; x++, bufferOffset++) {
227                int a = dataBuffer[bufferOffset] >>> 24;
228                aHistory[x] = a;
229                aSum += a;
230            }
231
232            bufferOffset -= right;
233
234            for (int x = xStart; x < xStop; x++, bufferOffset++) {
235                int a = (int) (aSum * sumDivider);
236                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
237
238                // substract the oldest pixel from the sum
239                aSum -= aHistory[historyIdx];
240
241                // get the lastest pixel
242                a = dataBuffer[bufferOffset + right] >>> 24;
243                aHistory[historyIdx] = a;
244                aSum += a;
245
246                if (++historyIdx >= this.shadowSize) {
247                    historyIdx -= this.shadowSize;
248                }
249            }
250        }
251
252        // vertical pass
253        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
254            aSum = 0;
255            historyIdx = 0;
256            for (int y = 0; y < this.shadowSize; y++,
257                    bufferOffset += dstWidth) {
258                int a = dataBuffer[bufferOffset] >>> 24;
259                aHistory[y] = a;
260                aSum += a;
261            }
262
263            bufferOffset -= lastPixelOffset;
264
265            for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
266                int a = (int) (aSum * sumDivider);
267                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
268
269                // substract the oldest pixel from the sum
270                aSum -= aHistory[historyIdx];
271
272                // get the lastest pixel
273                a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
274                aHistory[historyIdx] = a;
275                aSum += a;
276
277                if (++historyIdx >= this.shadowSize) {
278                    historyIdx -= this.shadowSize;
279                }
280            }
281        }
282    }
283
284    /**
285     * Tests this object for equality with an arbitrary object.
286     * 
287     * @param obj  the object (<code>null</code> permitted).
288     * 
289     * @return The object.
290     */
291    @Override
292    public boolean equals(Object obj) {
293        if (obj == this) {
294            return true;
295        }
296        if (!(obj instanceof DefaultShadowGenerator)) {
297            return false;
298        }
299        DefaultShadowGenerator that = (DefaultShadowGenerator) obj;
300        if (this.shadowSize != that.shadowSize) {
301            return false;
302        }
303        if (!this.shadowColor.equals(that.shadowColor)) {
304            return false;
305        }
306        if (this.shadowOpacity != that.shadowOpacity) {
307            return false;
308        }
309        if (this.distance != that.distance) {
310            return false;
311        }
312        if (this.angle != that.angle) {
313            return false;
314        }
315        return true;
316    }
317
318    /**
319     * Returns a hash code for this instance.
320     * 
321     * @return The hash code.
322     */
323    @Override
324    public int hashCode() {
325        int hash = HashUtilities.hashCode(17, this.shadowSize);
326        hash = HashUtilities.hashCode(hash, this.shadowColor);
327        hash = HashUtilities.hashCode(hash, this.shadowOpacity);
328        hash = HashUtilities.hashCode(hash, this.distance);
329        hash = HashUtilities.hashCode(hash, this.angle);
330        return hash;
331    }
332
333}