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