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 }