001/* 002 * $Id: ShadowRenderer.java 4082 2011-11-15 18:39:43Z kschaefe $ 003 * 004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jdesktop.swingx.graphics; 022 023import static org.jdesktop.swingx.util.GraphicsUtilities.createCompatibleTranslucentImage; 024 025import java.awt.Color; 026import java.awt.image.BufferedImage; 027import java.beans.PropertyChangeListener; 028import java.beans.PropertyChangeSupport; 029 030import org.jdesktop.swingx.util.GraphicsUtilities; 031 032/** 033 * <p>A shadow renderer generates a drop shadow for any given picture, respecting 034 * the transparency channel if present. The resulting picture contains the 035 * shadow only and to create a drop shadow effect you will need to stack the 036 * original picture and the shadow generated by the renderer.</p> 037 * <h2>Shadow Properties</h2> 038 * <p>A shadow is defined by three properties: 039 * <ul> 040 * <li><i>size</i>: The size, in pixels, of the shadow. This property also 041 * defines the fuzziness.</li> 042 * <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li> 043 * <li><i>color</i>: The color of the shadow. Shadows are not meant to be 044 * black only.</li> 045 * </ul> 046 * You can set these properties using the provided mutators or the appropriate 047 * constructor. Here are two ways of creating a green shadow of size 10 and 048 * with an opacity of 50%: 049 * <pre> 050 * ShadowRenderer renderer = new ShadowRenderer(10, 0.5f, Color.GREEN); 051 * // .. 052 * renderer = new ShadowRenderer(); 053 * renderer.setSize(10); 054 * renderer.setOpacity(0.5f); 055 * renderer.setColor(Color.GREEN); 056 * </pre> 057 * The default constructor provides the following default values: 058 * <ul> 059 * <li><i>size</i>: 5 pixels</li> 060 * <li><i>opacity</i>: 50%</li> 061 * <li><i>color</i>: Black</li> 062 * </ul></p> 063 * <h2>Generating a Shadow</h2> 064 * <p>A shadow is generated as a <code>BufferedImage</code> from another 065 * <code>BufferedImage</code>. Once the renderer is set up, you must call 066 * {@link #createShadow} to actually generate the shadow: 067 * <pre> 068 * ShadowRenderer renderer = new ShadowRenderer(); 069 * // renderer setup 070 * BufferedImage shadow = renderer.createShadow(bufferedImage); 071 * </pre></p> 072 * <p>The generated image dimensions are computed as following:</p> 073 * <pre> 074 * width = imageWidth + 2 * shadowSize 075 * height = imageHeight + 2 * shadowSize 076 * </pre> 077 * <h2>Properties Changes</h2> 078 * <p>This renderer allows to register property change listeners with 079 * {@link #addPropertyChangeListener}. Listening to properties changes is very 080 * useful when you embed the renderer in a graphical component and give the API 081 * user the ability to access the renderer. By listening to properties changes, 082 * you can easily repaint the component when needed.</p> 083 * <h2>Threading Issues</h2> 084 * <p><code>ShadowRenderer</code> is not guaranteed to be thread-safe.</p> 085 * 086 * @author Romain Guy <romain.guy@mac.com> 087 * @author Sebastien Petrucci 088 */ 089public class ShadowRenderer { 090 /** 091 * <p>Identifies a change to the size used to render the shadow.</p> 092 * <p>When the property change event is fired, the old value and the new 093 * value are provided as <code>Integer</code> instances.</p> 094 */ 095 public static final String SIZE_CHANGED_PROPERTY = "shadow_size"; 096 097 /** 098 * <p>Identifies a change to the opacity used to render the shadow.</p> 099 * <p>When the property change event is fired, the old value and the new 100 * value are provided as <code>Float</code> instances.</p> 101 */ 102 public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity"; 103 104 /** 105 * <p>Identifies a change to the color used to render the shadow.</p> 106 */ 107 public static final String COLOR_CHANGED_PROPERTY = "shadow_color"; 108 109 // size of the shadow in pixels (defines the fuzziness) 110 private int size = 5; 111 112 // opacity of the shadow 113 private float opacity = 0.5f; 114 115 // color of the shadow 116 private Color color = Color.BLACK; 117 118 // notifies listeners of properties changes 119 private PropertyChangeSupport changeSupport; 120 121 /** 122 * <p>Creates a default good looking shadow generator. 123 * The default shadow renderer provides the following default values: 124 * <ul> 125 * <li><i>size</i>: 5 pixels</li> 126 * <li><i>opacity</i>: 50%</li> 127 * <li><i>color</i>: Black</li> 128 * </ul></p> 129 * <p>These properties provide a regular, good looking shadow.</p> 130 */ 131 public ShadowRenderer() { 132 this(5, 0.5f, Color.BLACK); 133 } 134 135 /** 136 * <p>A shadow renderer needs three properties to generate shadows. 137 * These properties are:</p> 138 * <ul> 139 * <li><i>size</i>: The size, in pixels, of the shadow. This property also 140 * defines the fuzziness.</li> 141 * <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li> 142 * <li><i>color</i>: The color of the shadow. Shadows are not meant to be 143 * black only.</li> 144 * </ul> 145 * @param size the size of the shadow in pixels. Defines the fuzziness. 146 * @param opacity the opacity of the shadow. 147 * @param color the color of the shadow. 148 */ 149 public ShadowRenderer(final int size, final float opacity, final Color color) { 150 //noinspection ThisEscapedInObjectConstruction 151 changeSupport = new PropertyChangeSupport(this); 152 153 setSize(size); 154 setOpacity(opacity); 155 setColor(color); 156 } 157 158 /** 159 * <p>Add a PropertyChangeListener to the listener list. The listener is 160 * registered for all properties. The same listener object may be added 161 * more than once, and will be called as many times as it is added. If 162 * <code>listener</code> is null, no exception is thrown and no action 163 * is taken.</p> 164 * @param listener the PropertyChangeListener to be added 165 */ 166 public void addPropertyChangeListener(PropertyChangeListener listener) { 167 changeSupport.addPropertyChangeListener(listener); 168 } 169 170 /** 171 * <p>Remove a PropertyChangeListener from the listener list. This removes 172 * a PropertyChangeListener that was registered for all properties. If 173 * <code>listener</code> was added more than once to the same event source, 174 * it will be notified one less time after being removed. If 175 * <code>listener</code> is null, or was never added, no exception is thrown 176 * and no action is taken.</p> 177 * @param listener the PropertyChangeListener to be removed 178 */ 179 public void removePropertyChangeListener(PropertyChangeListener listener) { 180 changeSupport.removePropertyChangeListener(listener); 181 } 182 183 /** 184 * <p>Gets the color used by the renderer to generate shadows.</p> 185 * @return this renderer's shadow color 186 */ 187 public Color getColor() { 188 return color; 189 } 190 191 /** 192 * <p>Sets the color used by the renderer to generate shadows.</p> 193 * <p>Consecutive calls to {@link #createShadow} will all use this color 194 * until it is set again.</p> 195 * <p>If the color provided is null, the previous color will be retained.</p> 196 * @param shadowColor the generated shadows color 197 */ 198 public void setColor(final Color shadowColor) { 199 if (shadowColor != null) { 200 Color oldColor = this.color; 201 this.color = shadowColor; 202 changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY, 203 oldColor, 204 this.color); 205 } 206 } 207 208 /** 209 * <p>Gets the opacity used by the renderer to generate shadows.</p> 210 * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully 211 * transparent and 1.0f fully opaque.</p> 212 * @return this renderer's shadow opacity 213 */ 214 public float getOpacity() { 215 return opacity; 216 } 217 218 /** 219 * <p>Sets the opacity used by the renderer to generate shadows.</p> 220 * <p>Consecutive calls to {@link #createShadow} will all use this opacity 221 * until it is set again.</p> 222 * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully 223 * transparent and 1.0f fully opaque. If you provide a value out of these 224 * boundaries, it will be restrained to the closest boundary.</p> 225 * @param shadowOpacity the generated shadows opacity 226 */ 227 public void setOpacity(final float shadowOpacity) { 228 float oldOpacity = this.opacity; 229 230 if (shadowOpacity < 0.0) { 231 this.opacity = 0.0f; 232 } else if (shadowOpacity > 1.0f) { 233 this.opacity = 1.0f; 234 } else { 235 this.opacity = shadowOpacity; 236 } 237 238 changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY, 239 oldOpacity, 240 this.opacity); 241 } 242 243 /** 244 * <p>Gets the size in pixel used by the renderer to generate shadows.</p> 245 * @return this renderer's shadow size 246 */ 247 public int getSize() { 248 return size; 249 } 250 251 /** 252 * <p>Sets the size, in pixels, used by the renderer to generate shadows.</p> 253 * <p>The size defines the blur radius applied to the shadow to create the 254 * fuzziness.</p> 255 * <p>There is virtually no limit to the size. The size cannot be negative. 256 * If you provide a negative value, the size will be 0 instead.</p> 257 * @param shadowSize the generated shadows size in pixels (fuzziness) 258 */ 259 public void setSize(final int shadowSize) { 260 int oldSize = this.size; 261 262 if (shadowSize < 0) { 263 this.size = 0; 264 } else { 265 this.size = shadowSize; 266 } 267 268 changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY, 269 new Integer(oldSize), 270 new Integer(this.size)); 271 } 272 273 /** 274 * <p>Generates the shadow for a given picture and the current properties 275 * of the renderer.</p> 276 * <p>The generated image dimensions are computed as following:</p> 277 * <pre> 278 * width = imageWidth + 2 * shadowSize 279 * height = imageHeight + 2 * shadowSize 280 * </pre> 281 * @param image the picture from which the shadow must be cast 282 * @return the picture containing the shadow of <code>image</code> 283 */ 284 public BufferedImage createShadow(final BufferedImage image) { 285 // Written by Sesbastien Petrucci 286 int shadowSize = size * 2; 287 288 int srcWidth = image.getWidth(); 289 int srcHeight = image.getHeight(); 290 291 int dstWidth = srcWidth + shadowSize; 292 int dstHeight = srcHeight + shadowSize; 293 294 int left = size; 295 int right = shadowSize - left; 296 297 int yStop = dstHeight - right; 298 299 int shadowRgb = color.getRGB() & 0x00FFFFFF; 300 int[] aHistory = new int[shadowSize]; 301 int historyIdx; 302 303 int aSum; 304 305 BufferedImage dst = createCompatibleTranslucentImage(dstWidth, dstHeight); 306 307 int[] dstBuffer = new int[dstWidth * dstHeight]; 308 int[] srcBuffer = new int[srcWidth * srcHeight]; 309 310 GraphicsUtilities.getPixels(image, 0, 0, srcWidth, srcHeight, srcBuffer); 311 312 int lastPixelOffset = right * dstWidth; 313 float hSumDivider = 1.0f / shadowSize; 314 float vSumDivider = opacity / shadowSize; 315 316 int[] hSumLookup = new int[256 * shadowSize]; 317 for (int i = 0; i < hSumLookup.length; i++) { 318 hSumLookup[i] = (int) (i * hSumDivider); 319 } 320 321 int[] vSumLookup = new int[256 * shadowSize]; 322 for (int i = 0; i < vSumLookup.length; i++) { 323 vSumLookup[i] = (int) (i * vSumDivider); 324 } 325 326 int srcOffset; 327 328 // horizontal pass : extract the alpha mask from the source picture and 329 // blur it into the destination picture 330 for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) { 331 332 // first pixels are empty 333 for (historyIdx = 0; historyIdx < shadowSize; ) { 334 aHistory[historyIdx++] = 0; 335 } 336 337 aSum = 0; 338 historyIdx = 0; 339 srcOffset = srcY * srcWidth; 340 341 // compute the blur average with pixels from the source image 342 for (int srcX = 0; srcX < srcWidth; srcX++) { 343 344 int a = hSumLookup[aSum]; 345 dstBuffer[dstOffset++] = a << 24; // store the alpha value only 346 // the shadow color will be added in the next pass 347 348 aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum 349 350 // extract the new pixel ... 351 a = srcBuffer[srcOffset + srcX] >>> 24; 352 aHistory[historyIdx] = a; // ... and store its value into history 353 aSum += a; // ... and add its value to the sum 354 355 if (++historyIdx >= shadowSize) { 356 historyIdx -= shadowSize; 357 } 358 } 359 360 // blur the end of the row - no new pixels to grab 361 for (int i = 0; i < shadowSize; i++) { 362 363 int a = hSumLookup[aSum]; 364 dstBuffer[dstOffset++] = a << 24; 365 366 // substract the oldest pixel from the sum ... and nothing new to add ! 367 aSum -= aHistory[historyIdx]; 368 369 if (++historyIdx >= shadowSize) { 370 historyIdx -= shadowSize; 371 } 372 } 373 } 374 375 // vertical pass 376 for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) { 377 378 aSum = 0; 379 380 // first pixels are empty 381 for (historyIdx = 0; historyIdx < left;) { 382 aHistory[historyIdx++] = 0; 383 } 384 385 // and then they come from the dstBuffer 386 for (int y = 0; y < right; y++, bufferOffset += dstWidth) { 387 int a = dstBuffer[bufferOffset] >>> 24; // extract alpha 388 aHistory[historyIdx++] = a; // store into history 389 aSum += a; // and add to sum 390 } 391 392 bufferOffset = x; 393 historyIdx = 0; 394 395 // compute the blur avera`ge with pixels from the previous pass 396 for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) { 397 398 int a = vSumLookup[aSum]; 399 dstBuffer[bufferOffset] = a << 24 | shadowRgb; // store alpha value + shadow color 400 401 aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum 402 403 a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24; // extract the new pixel ... 404 aHistory[historyIdx] = a; // ... and store its value into history 405 aSum += a; // ... and add its value to the sum 406 407 if (++historyIdx >= shadowSize) { 408 historyIdx -= shadowSize; 409 } 410 } 411 412 // blur the end of the column - no pixels to grab anymore 413 for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) { 414 415 int a = vSumLookup[aSum]; 416 dstBuffer[bufferOffset] = a << 24 | shadowRgb; 417 418 aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum 419 420 if (++historyIdx >= shadowSize) { 421 historyIdx -= shadowSize; 422 } 423 } 424 } 425 426 GraphicsUtilities.setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer); 427 return dst; 428 } 429}