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