001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, 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, 2011 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     *
040     */
041    
042    package org.jfree.chart.util;
043    
044    import java.awt.Color;
045    import java.awt.Graphics2D;
046    import java.awt.image.BufferedImage;
047    import java.awt.image.DataBufferInt;
048    import java.io.Serializable;
049    import org.jfree.chart.HashUtilities;
050    
051    /**
052     * A default implementation of the {@link ShadowGenerator} interface, based on
053     * code in a 
054     * <a href="http://www.jroller.com/gfx/entry/fast_or_good_drop_shadows">blog
055     * post by Romain Guy</a>.
056     *
057     * @since 1.0.14
058     */
059    public class DefaultShadowGenerator implements ShadowGenerator, Serializable {
060    
061        private static final long serialVersionUID = 2732993885591386064L;
062    
063        /** The shadow size. */
064        private int shadowSize;
065    
066        /** The shadow color. */
067        private Color shadowColor;
068    
069        /** The shadow opacity. */
070        private float shadowOpacity;
071    
072        /** The shadow offset angle (in radians). */
073        private double angle;
074    
075        /** The shadow offset distance (in Java2D units). */
076        private int distance;
077    
078        /**
079         * Creates a new instance with default attributes.
080         */
081        public DefaultShadowGenerator() {
082            this(5, Color.black, 0.5f, 5, -Math.PI / 4);
083        }
084    
085        /**
086         * Creates a new instance with the specified attributes.
087         *
088         * @param size  the shadow size.
089         * @param color  the shadow color.
090         * @param opacity  the shadow opacity.
091         * @param distance  the shadow offset distance.
092         * @param angle  the shadow offset angle (in radians).
093         */
094        public DefaultShadowGenerator(int size, Color color, float opacity,
095                int distance, double angle) {
096            if (color == null) {
097                throw new IllegalArgumentException("Null 'color' argument.");
098            }
099            this.shadowSize = size;
100            this.shadowColor = color;
101            this.shadowOpacity = opacity;
102            this.distance = distance;
103            this.angle = angle;
104        }
105    
106        /**
107         * Returns the shadow size.
108         *
109         * @return The shadow size.
110         */
111        public int getShadowSize() {
112            return this.shadowSize;
113        }
114    
115        /**
116         * Returns the shadow color.
117         *
118         * @return The shadow color (never <code>null</code>).
119         */
120        public Color getShadowColor() {
121            return this.shadowColor;
122        }
123    
124        /**
125         * Returns the shadow opacity.
126         *
127         * @return The shadow opacity.
128         */
129        public float getShadowOpacity() {
130            return this.shadowOpacity;
131        }
132    
133        /**
134         * Returns the shadow offset distance.
135         *
136         * @return The shadow offset distance (in Java2D units).
137         */
138        public int getDistance() {
139            return this.distance;
140        }
141    
142        /**
143         * Returns the shadow offset angle (in radians).
144         *
145         * @return The angle (in radians).
146         */
147        public double getAngle() {
148            return this.angle;
149        }
150    
151        /**
152         * Calculates the x-offset for drawing the shadow image relative to the
153         * source.
154         *
155         * @return The x-offset.
156         */
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        public int calculateOffsetY() {
168            return -(int) (Math.sin(this.angle) * this.distance) - this.shadowSize;
169        }
170    
171        /**
172         * Creates and returns an image containing the drop shadow for the
173         * specified source image.
174         *
175         * @param source  the source image.
176         *
177         * @return A new image containing the shadow.
178         */
179        public BufferedImage createDropShadow(BufferedImage source) {
180            BufferedImage subject = new BufferedImage(
181                    source.getWidth() + this.shadowSize * 2,
182                    source.getHeight() + this.shadowSize * 2,
183                    BufferedImage.TYPE_INT_ARGB);
184    
185            Graphics2D g2 = subject.createGraphics();
186            g2.drawImage(source, null, this.shadowSize, this.shadowSize);
187            g2.dispose();
188            applyShadow(subject);
189            return subject;
190        }
191    
192        /**
193         * Applies a shadow to the image.
194         *
195         * @param image  the image.
196         */
197        protected void applyShadow(BufferedImage image) {
198            int dstWidth = image.getWidth();
199            int dstHeight = image.getHeight();
200    
201            int left = (this.shadowSize - 1) >> 1;
202            int right = this.shadowSize - left;
203            int xStart = left;
204            int xStop = dstWidth - right;
205            int yStart = left;
206            int yStop = dstHeight - right;
207    
208            int shadowRgb = this.shadowColor.getRGB() & 0x00FFFFFF;
209    
210            int[] aHistory = new int[this.shadowSize];
211            int historyIdx = 0;
212    
213            int aSum;
214    
215            int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
216            int lastPixelOffset = right * dstWidth;
217            float sumDivider = this.shadowOpacity / this.shadowSize;
218    
219            // horizontal pass
220    
221            for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
222                aSum = 0;
223                historyIdx = 0;
224                for (int x = 0; x < this.shadowSize; x++, bufferOffset++) {
225                    int a = dataBuffer[bufferOffset] >>> 24;
226                    aHistory[x] = a;
227                    aSum += a;
228                }
229    
230                bufferOffset -= right;
231    
232                for (int x = xStart; x < xStop; x++, bufferOffset++) {
233                    int a = (int) (aSum * sumDivider);
234                    dataBuffer[bufferOffset] = a << 24 | shadowRgb;
235    
236                    // substract the oldest pixel from the sum
237                    aSum -= aHistory[historyIdx];
238    
239                    // get the lastest pixel
240                    a = dataBuffer[bufferOffset + right] >>> 24;
241                    aHistory[historyIdx] = a;
242                    aSum += a;
243    
244                    if (++historyIdx >= this.shadowSize) {
245                        historyIdx -= this.shadowSize;
246                    }
247                }
248            }
249    
250            // vertical pass
251            for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
252                aSum = 0;
253                historyIdx = 0;
254                for (int y = 0; y < this.shadowSize; y++,
255                        bufferOffset += dstWidth) {
256                    int a = dataBuffer[bufferOffset] >>> 24;
257                    aHistory[y] = a;
258                    aSum += a;
259                }
260    
261                bufferOffset -= lastPixelOffset;
262    
263                for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
264                    int a = (int) (aSum * sumDivider);
265                    dataBuffer[bufferOffset] = a << 24 | shadowRgb;
266    
267                    // substract the oldest pixel from the sum
268                    aSum -= aHistory[historyIdx];
269    
270                    // get the lastest pixel
271                    a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
272                    aHistory[historyIdx] = a;
273                    aSum += a;
274    
275                    if (++historyIdx >= this.shadowSize) {
276                        historyIdx -= this.shadowSize;
277                    }
278                }
279            }
280        }
281    
282        /**
283         * Tests this object for equality with an arbitrary object.
284         * 
285         * @param obj  the object (<code>null</code> permitted).
286         * 
287         * @return The object.
288         */
289        public boolean equals(Object obj) {
290            if (obj == this) {
291                return true;
292            }
293            if (!(obj instanceof DefaultShadowGenerator)) {
294                return false;
295            }
296            DefaultShadowGenerator that = (DefaultShadowGenerator) obj;
297            if (this.shadowSize != that.shadowSize) {
298                return false;
299            }
300            if (!this.shadowColor.equals(that.shadowColor)) {
301                return false;
302            }
303            if (this.shadowOpacity != that.shadowOpacity) {
304                return false;
305            }
306            if (this.distance != that.distance) {
307                return false;
308            }
309            if (this.angle != that.angle) {
310                return false;
311            }
312            return true;
313        }
314    
315        /**
316         * Returns a hash code for this instance.
317         * 
318         * @return The hash code.
319         */
320        public int hashCode() {
321            int hash = HashUtilities.hashCode(17, this.shadowSize);
322            hash = HashUtilities.hashCode(hash, this.shadowColor);
323            hash = HashUtilities.hashCode(hash, this.shadowOpacity);
324            hash = HashUtilities.hashCode(hash, this.distance);
325            hash = HashUtilities.hashCode(hash, this.angle);
326            return hash;
327        }
328    
329    }