001 /* 002 * $Id: AbstractAreaEffect.java 3256 2009-02-10 20:09:41Z 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 */ 021 022 023 package org.jdesktop.swingx.painter.effects; 024 025 import java.awt.*; 026 import java.awt.geom.Area; 027 import java.awt.geom.Point2D; 028 import java.awt.image.BufferedImage; 029 030 /** 031 * The abstract base class for path effects. It takes care 032 * of soft clipping and interpolating brush sizes and colors. Subclasses 033 * can change these values to provide prefab effect behavior, like 034 * dropshadows and glows. 035 * @author joshy 036 */ 037 public class AbstractAreaEffect implements AreaEffect { 038 private static final boolean debug = false; 039 /** 040 * Creates a new instance of AreaEffect 041 */ 042 public AbstractAreaEffect() { 043 setBrushColor(Color.BLACK); 044 setBrushSteps(10); 045 setEffectWidth(8); 046 setRenderInsideShape(false); 047 setOffset(new Point(4,4)); 048 setShouldFillShape(true); 049 setShapeMasked(true); 050 } 051 052 public void apply(Graphics2D g, Shape clipShape, int width, int height) { 053 // create a rect to hold the bounds 054 width = (int)(clipShape.getBounds2D().getWidth() + clipShape.getBounds2D().getX()); 055 height = (int)(clipShape.getBounds2D().getHeight() + clipShape.getBounds2D().getY()); 056 Rectangle effectBounds = new Rectangle(0,0, 057 width + getEffectWidth()*2 + 1, 058 height + getEffectWidth()*2 + 1); 059 060 // Apply the border glow effect 061 if (isShapeMasked()) { 062 BufferedImage clipImage = getClipImage(effectBounds); 063 Graphics2D g2 = clipImage.createGraphics(); 064 065 try { 066 // clear the buffer 067 g2.setPaint(Color.BLACK); 068 g2.setComposite(AlphaComposite.Clear); 069 g2.fillRect(0, 0, effectBounds.width, effectBounds.height); 070 071 if (debug) { 072 g2.setPaint(Color.WHITE); 073 g2.setComposite(AlphaComposite.SrcOver); 074 g2.drawRect(0, 0, effectBounds.width - 1, 075 effectBounds.height - 1); 076 } 077 078 // turn on smoothing 079 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 080 RenderingHints.VALUE_ANTIALIAS_ON); 081 g2.translate(getEffectWidth() - getOffset().getX(), 082 getEffectWidth() - getOffset().getY()); 083 paintBorderGlow(g2, clipShape, width, height); 084 085 // clip out the parts we don't want 086 g2.setComposite(AlphaComposite.Clear); 087 g2.setColor(Color.WHITE); 088 if (isRenderInsideShape()) { 089 // clip the outside 090 Area area = new Area(effectBounds); 091 area.subtract(new Area(clipShape)); 092 g2.fill(area); 093 } else { 094 // clip the inside 095 g2.fill(clipShape); 096 } 097 } finally { 098 // draw the final image 099 g2.dispose(); 100 } 101 102 g.drawImage(clipImage, -getEffectWidth() + (int) getOffset().getX(), -getEffectWidth() + (int) getOffset().getY(), null); 103 } else { 104 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 105 paintBorderGlow(g, clipShape, width, height); 106 } 107 108 //g.setColor(Color.MAGENTA); 109 //g.draw(clipShape.getBounds2D()); 110 //g.drawRect(0,0,width,height); 111 112 } 113 114 BufferedImage _clipImage = null; 115 private BufferedImage getClipImage(final Rectangle effectBounds) { 116 // set up a temp buffer 117 if(_clipImage == null || 118 _clipImage.getWidth() != effectBounds.width || 119 _clipImage.getHeight() != effectBounds.height) { 120 _clipImage = new BufferedImage( 121 effectBounds.width, 122 effectBounds.height, BufferedImage.TYPE_INT_ARGB); 123 } 124 _clipImage.getGraphics().clearRect(0,0,_clipImage.getWidth(), _clipImage.getHeight()); 125 return _clipImage; 126 } 127 128 129 /* 130 private BufferedImage createClipImage(Shape s, Graphics2D g, int width, int height) { 131 // Create a translucent intermediate image in which we can perform 132 // the soft clipping 133 134 GraphicsConfiguration gc = g.getDeviceConfiguration(); 135 BufferedImage img = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT); 136 Graphics2D g2 = img.createGraphics(); 137 138 // Clear the image so all pixels have zero alpha 139 g2.setComposite(AlphaComposite.Clear); 140 g2.fillRect(0, 0, width, height); 141 142 // Render our clip shape into the image. Note that we enable 143 // antialiasing to achieve the soft clipping effect. Try 144 // commenting out the line that enables antialiasing, and 145 // you will see that you end up with the usual hard clipping. 146 g2.setComposite(AlphaComposite.Src); 147 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 148 g2.setColor(Color.WHITE); 149 g2.fill(s); 150 g2.dispose(); 151 152 return img; 153 }*/ 154 155 156 /* draws the actual shaded border to the specified graphics 157 */ 158 /** 159 * Paints the border glow 160 * @param g2 161 * @param clipShape 162 * @param width 163 * @param height 164 */ 165 protected void paintBorderGlow(Graphics2D g2, 166 Shape clipShape, int width, int height) { 167 168 int steps = getBrushSteps(); 169 float brushAlpha = 1f/steps; 170 171 boolean inside = isRenderInsideShape(); 172 173 g2.setPaint(getBrushColor()); 174 175 g2.translate(offset.getX(), offset.getY()); 176 177 if(isShouldFillShape()) { 178 // set the inside/outside mode 179 if(inside) { 180 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1f)); 181 Area a1 = new Area(new Rectangle( 182 (int)-offset.getX()-20, 183 (int)-offset.getY()-20, 184 width+40,height+40)); 185 Area a2 = new Area(clipShape); 186 a1.subtract(a2); 187 g2.fill(a1); 188 } else { 189 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, 1f)); 190 g2.fill(clipShape); 191 } 192 193 } 194 195 // set the inside/outside mode 196 /* 197 if(inside) { 198 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, brushAlpha)); 199 } else { 200 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, brushAlpha)); 201 }*/ 202 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, brushAlpha)); 203 204 // draw the effect 205 for(float i=0; i<steps; i=i+1f) { 206 float brushWidth = i * effectWidth/steps; 207 g2.setStroke(new BasicStroke(brushWidth, 208 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 209 g2.draw(clipShape); 210 } 211 g2.translate(-offset.getX(), -offset.getY()); 212 213 } 214 215 /** 216 * Holds value of property brushColor. 217 */ 218 private Color brushColor; 219 220 /** 221 * Utility field used by bound properties. 222 */ 223 private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); 224 225 /** 226 * Adds a PropertyChangeListener to the listener list. 227 * @param l The listener to add. 228 */ 229 public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { 230 propertyChangeSupport.addPropertyChangeListener(l); 231 } 232 233 /** 234 * Removes a PropertyChangeListener from the listener list. 235 * @param l The listener to remove. 236 */ 237 public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { 238 propertyChangeSupport.removePropertyChangeListener(l); 239 } 240 241 /** 242 * Getter for property brushColor. 243 * @return Value of property brushColor. 244 */ 245 public Color getBrushColor() { 246 return this.brushColor; 247 } 248 249 /** 250 * Setter for property brushColor. 251 * @param brushColor New value of property brushColor. 252 */ 253 public void setBrushColor(Color brushColor) { 254 Color oldBrushColor = this.brushColor; 255 this.brushColor = brushColor; 256 propertyChangeSupport.firePropertyChange("brushColor", oldBrushColor, brushColor); 257 } 258 259 /** 260 * Holds value of property brushSteps. 261 */ 262 private int brushSteps; 263 264 /** 265 * Getter for property brushSteps. 266 * @return Value of property brushSteps. 267 */ 268 public int getBrushSteps() { 269 return this.brushSteps; 270 } 271 272 /** 273 * Setter for property brushSteps. 274 * @param brushSteps New value of property brushSteps. 275 */ 276 public void setBrushSteps(int brushSteps) { 277 int oldBrushSteps = this.brushSteps; 278 this.brushSteps = brushSteps; 279 propertyChangeSupport.firePropertyChange("brushSteps", new Integer(oldBrushSteps), new Integer(brushSteps)); 280 } 281 282 /** 283 * Holds value of property effectWidth. 284 */ 285 private int effectWidth; 286 287 /** 288 * Getter for property effectWidth. 289 * @return Value of property effectWidth. 290 */ 291 public int getEffectWidth() { 292 return this.effectWidth; 293 } 294 295 /** 296 * Setter for property effectWidth. 297 * @param effectWidth New value of property effectWidth. 298 */ 299 public void setEffectWidth(int effectWidth) { 300 int oldEffectWidth = this.effectWidth; 301 this.effectWidth = effectWidth; 302 propertyChangeSupport.firePropertyChange("effectWidth", new Integer(oldEffectWidth), new Integer(effectWidth)); 303 } 304 305 /** 306 * Holds value of property renderInsideShape. 307 */ 308 private boolean renderInsideShape; 309 310 /** 311 * Getter for property renderInsideShape. 312 * @return Value of property renderInsideShape. 313 */ 314 public boolean isRenderInsideShape() { 315 return this.renderInsideShape; 316 } 317 318 /** 319 * Setter for property renderInsideShape. 320 * @param renderInsideShape New value of property renderInsideShape. 321 */ 322 public void setRenderInsideShape(boolean renderInsideShape) { 323 boolean oldRenderInsideShape = this.renderInsideShape; 324 this.renderInsideShape = renderInsideShape; 325 propertyChangeSupport.firePropertyChange("renderInsideShape", new Boolean(oldRenderInsideShape), new Boolean(renderInsideShape)); 326 } 327 328 /** 329 * Holds value of property offset. 330 */ 331 private Point2D offset; 332 333 /** 334 * Getter for property offset. 335 * @return Value of property offset. 336 */ 337 public Point2D getOffset() { 338 return this.offset; 339 } 340 341 /** 342 * Setter for property offset. 343 * @param offset New value of property offset. 344 */ 345 public void setOffset(Point2D offset) { 346 Point2D oldOffset = this.offset; 347 this.offset = offset; 348 propertyChangeSupport.firePropertyChange("offset", oldOffset, offset); 349 } 350 351 /** 352 * Holds value of property shouldFillShape. 353 */ 354 private boolean shouldFillShape; 355 356 /** 357 * Getter for property shouldFillShape. 358 * @return Value of property shouldFillShape. 359 */ 360 public boolean isShouldFillShape() { 361 return this.shouldFillShape; 362 } 363 364 /** 365 * Setter for property shouldFillShape. 366 * @param shouldFillShape New value of property shouldFillShape. 367 */ 368 public void setShouldFillShape(boolean shouldFillShape) { 369 boolean oldShouldFillShape = this.shouldFillShape; 370 this.shouldFillShape = shouldFillShape; 371 propertyChangeSupport.firePropertyChange("shouldFillShape", new Boolean(oldShouldFillShape), new Boolean(shouldFillShape)); 372 } 373 374 /** 375 * Holds value of property shapeMasked. 376 */ 377 private boolean shapeMasked; 378 379 /** 380 * Getter for property shapeMasked. 381 * @return Value of property shapeMasked. 382 */ 383 public boolean isShapeMasked() { 384 return this.shapeMasked; 385 } 386 387 /** 388 * Setter for property shapeMasked. 389 * @param shapeMasked New value of property shapeMasked. 390 */ 391 public void setShapeMasked(boolean shapeMasked) { 392 boolean oldShapeMasked = this.shapeMasked; 393 this.shapeMasked = shapeMasked; 394 propertyChangeSupport.firePropertyChange("shapeMasked", new Boolean(oldShapeMasked), new Boolean(shapeMasked)); 395 } 396 397 }