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    }