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