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}