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}