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