001    /*
002     * $Id: DropShadowBorder.java,v 1.13 2006/04/24 22:29:56 gfx Exp $
003     *
004     * Copyright 2004 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    package org.jdesktop.swingx.border;
023    
024    import java.awt.Color;
025    import java.awt.Component;
026    import java.awt.Graphics;
027    import java.awt.Graphics2D;
028    import java.awt.Image;
029    import java.awt.Insets;
030    import java.awt.Point;
031    import java.awt.Rectangle;
032    import java.awt.RenderingHints;
033    import java.awt.geom.RoundRectangle2D;
034    import java.awt.image.BufferedImage;
035    import java.awt.image.ConvolveOp;
036    import java.awt.image.Kernel;
037    import java.util.HashMap;
038    import java.util.Map;
039    
040    import javax.swing.UIManager;
041    import javax.swing.border.Border;
042    
043    /**
044     * Implements a DropShadow for components. In general, the DropShadowBorder will
045     * work with any rectangular components that do not have a default border installed
046     * as part of the look and feel, or otherwise. For example, DropShadowBorder works
047     * wonderfully with JPanel, but horribly with JComboBox.
048     *
049     * @author rbair
050     */
051    public class DropShadowBorder implements Border {
052        private static enum Position {TOP, TOP_LEFT, LEFT, BOTTOM_LEFT,
053                        BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT};
054                        
055        private static final Map<Integer,Map<Position,BufferedImage>> CACHE 
056                = new HashMap<Integer,Map<Position,BufferedImage>>();
057                            
058        private final Color lineColor;
059        private final int lineWidth;
060        private final int shadowSize;
061        private final float shadowOpacity;
062        private final int cornerSize;
063        private final boolean showTopShadow;
064        private final boolean showLeftShadow;
065        private final boolean showBottomShadow;
066        private final boolean showRightShadow;
067        
068        public DropShadowBorder() {
069            this(UIManager.getColor("Control"), 1, 5);
070        }
071        
072        public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize) {
073            this(lineColor, lineWidth, shadowSize, .5f, 12, false, false, true, true);
074        }
075        
076        public DropShadowBorder(Color lineColor, int lineWidth, boolean showLeftShadow) {
077            this(lineColor, lineWidth, 5, .5f, 12, false, showLeftShadow, true, true);
078        }
079        
080        public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize,
081                float shadowOpacity, int cornerSize, boolean showTopShadow,
082                boolean showLeftShadow, boolean showBottomShadow, boolean showRightShadow) {
083            this.lineColor = lineColor;
084            this.lineWidth = lineWidth;
085            this.shadowSize = shadowSize;
086            this.shadowOpacity = shadowOpacity;
087            this.cornerSize = cornerSize;
088            this.showTopShadow = showTopShadow;
089            this.showLeftShadow = showLeftShadow;
090            this.showBottomShadow = showBottomShadow;
091            this.showRightShadow = showRightShadow;
092        }
093        
094        /**
095         * @inheritDoc
096         */
097        public void paintBorder(Component c, Graphics graphics, int x, int y, int width, int height) {
098            /*
099             * 1) Get images for this border
100             * 2) Paint the images for each side of the border that should be painted
101             */
102            Map<Position,BufferedImage> images = getImages(null);
103            
104            //compute the edges of the component -- not including the border
105    //        Insets borderInsets = getBorderInsets(c);
106    //        int leftEdge = x + borderInsets.left;
107    //        int rightEdge = x + width - borderInsets.right;
108    //        int topEdge = y + borderInsets.top;
109    //        int bottomEdge = y + height - borderInsets.bottom;
110            Graphics2D g2 = (Graphics2D)graphics.create();
111            g2.setColor(lineColor);
112            
113            //The location and size of the shadows depends on which shadows are being
114            //drawn. For instance, if the left & bottom shadows are being drawn, then
115            //the left shadow extends all the way down to the corner, a corner is drawn,
116            //and then the bottom shadow begins at the corner. If, however, only the
117            //bottom shadow is drawn, then the bottom-left corner is drawn to the
118            //right of the corner, and the bottom shadow is somewhat shorter than before.
119            
120            Point topLeftShadowPoint = null;
121            if (showLeftShadow || showTopShadow) {
122                topLeftShadowPoint = new Point();
123                if (showLeftShadow && !showTopShadow) {
124                    topLeftShadowPoint.setLocation(x, y + shadowSize);
125                } else if (showLeftShadow && showTopShadow) {
126                    topLeftShadowPoint.setLocation(x, y);
127                } else if (!showLeftShadow && showTopShadow) {
128                    topLeftShadowPoint.setLocation(x + shadowSize, y);
129                }
130            }
131      
132            Point bottomLeftShadowPoint = null;
133            if (showLeftShadow || showBottomShadow) {
134                bottomLeftShadowPoint = new Point();
135                if (showLeftShadow && !showBottomShadow) {
136                    bottomLeftShadowPoint.setLocation(x, y + height - shadowSize - shadowSize);
137                } else if (showLeftShadow && showBottomShadow) {
138                    bottomLeftShadowPoint.setLocation(x, y + height - shadowSize);
139                } else if (!showLeftShadow && showBottomShadow) {
140                    bottomLeftShadowPoint.setLocation(x + shadowSize, y + height - shadowSize);
141                }
142            }
143            
144            Point bottomRightShadowPoint = null;
145            if (showRightShadow || showBottomShadow) {
146                bottomRightShadowPoint = new Point();
147                if (showRightShadow && !showBottomShadow) {
148                    bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize - shadowSize);
149                } else if (showRightShadow && showBottomShadow) {
150                    bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize);
151                } else if (!showRightShadow && showBottomShadow) {
152                    bottomRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y + height - shadowSize);
153                }
154            }
155            
156            Point topRightShadowPoint = null;
157            if (showRightShadow || showTopShadow) {
158                topRightShadowPoint = new Point();
159                if (showRightShadow && !showTopShadow) {
160                    topRightShadowPoint.setLocation(x + width - shadowSize, y + shadowSize);
161                } else if (showRightShadow && showTopShadow) {
162                    topRightShadowPoint.setLocation(x + width - shadowSize, y);
163                } else if (!showRightShadow && showTopShadow) {
164                    topRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y);
165                }
166            }
167     
168            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
169                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
170            
171            if (showLeftShadow) {
172                Rectangle leftShadowRect =
173                    new Rectangle(x,
174                                  topLeftShadowPoint.y + shadowSize,
175                                  shadowSize,
176                                  bottomLeftShadowPoint.y - topLeftShadowPoint.y - shadowSize);
177                g2.drawImage(images.get(Position.LEFT),
178                             leftShadowRect.x, leftShadowRect.y,
179                             leftShadowRect.width, leftShadowRect.height, null);
180            }
181    
182            if (showBottomShadow) {
183                Rectangle bottomShadowRect =
184                    new Rectangle(bottomLeftShadowPoint.x + shadowSize,
185                                  y + height - shadowSize,
186                                  bottomRightShadowPoint.x - bottomLeftShadowPoint.x - shadowSize,
187                                  shadowSize);
188                g2.drawImage(images.get(Position.BOTTOM),
189                             bottomShadowRect.x, bottomShadowRect.y,
190                             bottomShadowRect.width, bottomShadowRect.height, null);
191            }
192            
193            if (showRightShadow) {
194                Rectangle rightShadowRect =
195                    new Rectangle(x + width - shadowSize,
196                                  topRightShadowPoint.y + shadowSize,
197                                  shadowSize,
198                                  bottomRightShadowPoint.y - topRightShadowPoint.y - shadowSize);
199                g2.drawImage(images.get(Position.RIGHT),
200                             rightShadowRect.x, rightShadowRect.y,
201                             rightShadowRect.width, rightShadowRect.height, null);
202            }
203            
204            if (showTopShadow) {
205                Rectangle topShadowRect =
206                    new Rectangle(topLeftShadowPoint.x + shadowSize,
207                                  y,
208                                  topRightShadowPoint.x - topLeftShadowPoint.x - shadowSize,
209                                  shadowSize);
210                g2.drawImage(images.get(Position.TOP),
211                             topShadowRect.x, topShadowRect.y,
212                             topShadowRect.width, topShadowRect.height, null);
213            }
214            
215            if (showLeftShadow || showTopShadow) {
216                g2.drawImage(images.get(Position.TOP_LEFT),
217                             topLeftShadowPoint.x, topLeftShadowPoint.y, null);
218            }
219            if (showLeftShadow || showBottomShadow) {
220                g2.drawImage(images.get(Position.BOTTOM_LEFT),
221                             bottomLeftShadowPoint.x, bottomLeftShadowPoint.y, null);
222            }
223            if (showRightShadow || showBottomShadow) {
224                g2.drawImage(images.get(Position.BOTTOM_RIGHT),
225                             bottomRightShadowPoint.x, bottomRightShadowPoint.y, null);
226            }
227            if (showRightShadow || showTopShadow) {
228                g2.drawImage(images.get(Position.TOP_RIGHT),
229                             topRightShadowPoint.x, topRightShadowPoint.y, null);
230            }
231            
232            g2.dispose();
233        }
234        
235        private Map<Position,BufferedImage> getImages(Graphics2D g2) {
236            //first, check to see if an image for this size has already been rendered
237            //if so, use the cache. Else, draw and save
238            Map<Position,BufferedImage> images = CACHE.get(shadowSize);
239            if (images == null) {
240                images = new HashMap<Position,BufferedImage>();
241    
242                /*
243                 * Do draw a drop shadow, I have to:
244                 *  1) Create a rounded rectangle
245                 *  2) Create a BufferedImage to draw the rounded rect in
246                 *  3) Translate the graphics for the image, so that the rectangle
247                 *     is centered in the drawn space. The border around the rectangle
248                 *     needs to be shadowWidth wide, so that there is space for the
249                 *     shadow to be drawn.
250                 *  4) Draw the rounded rect as black, with an opacity of 50%
251                 *  5) Create the BLUR_KERNEL
252                 *  6) Blur the image
253                 *  7) copy off the corners, sides, etc into images to be used for
254                 *     drawing the Border
255                 */
256                int rectWidth = cornerSize + 1;
257                RoundRectangle2D rect = new RoundRectangle2D.Double(0, 0, rectWidth, rectWidth, cornerSize, cornerSize);
258                int imageWidth = rectWidth + shadowSize * 2;
259                BufferedImage image = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
260                Graphics2D buffer = (Graphics2D)image.getGraphics();
261                buffer.setColor(new Color(0.0f, 0.0f, 0.0f, shadowOpacity));
262                buffer.translate(shadowSize, shadowSize);
263                buffer.fill(rect);
264                buffer.dispose();
265                
266                float blurry = 1.0f / (float)(shadowSize * shadowSize);
267                float[] blurKernel = new float[shadowSize * shadowSize];
268                for (int i=0; i<blurKernel.length; i++) {
269                    blurKernel[i] = blurry;
270                }
271                ConvolveOp blur = new ConvolveOp(new Kernel(shadowSize, shadowSize, blurKernel));
272                BufferedImage targetImage = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
273                ((Graphics2D)targetImage.getGraphics()).drawImage(image, blur, -(shadowSize/2), -(shadowSize/2));
274    
275                int x = 1;
276                int y = 1;
277                int w = shadowSize;
278                int h = shadowSize;
279                images.put(Position.TOP_LEFT, getSubImage(targetImage, x, y, w, h));
280                x = 1;
281                y = h;
282                w = shadowSize;
283                h = 1;
284                images.put(Position.LEFT, getSubImage(targetImage, x, y, w, h));
285                x = 1;
286                y = rectWidth;
287                w = shadowSize;
288                h = shadowSize;
289                images.put(Position.BOTTOM_LEFT, getSubImage(targetImage, x, y, w, h));
290                x = cornerSize + 1;
291                y = rectWidth;
292                w = 1;
293                h = shadowSize;
294                images.put(Position.BOTTOM, getSubImage(targetImage, x, y, w, h));
295                x = rectWidth;
296                y = x;
297                w = shadowSize;
298                h = shadowSize;
299                images.put(Position.BOTTOM_RIGHT, getSubImage(targetImage, x, y, w, h));
300                x = rectWidth;
301                y = cornerSize + 1;
302                w = shadowSize;
303                h = 1;
304                images.put(Position.RIGHT, getSubImage(targetImage, x, y, w, h));
305                x = rectWidth;
306                y = 1;
307                w = shadowSize;
308                h = shadowSize;
309                images.put(Position.TOP_RIGHT, getSubImage(targetImage, x, y, w, h));
310                x = shadowSize;
311                y = 1;
312                w = 1;
313                h = shadowSize;
314                images.put(Position.TOP, getSubImage(targetImage, x, y, w, h));
315    
316                image.flush();
317            }
318            return images;
319        }
320        
321        /**
322         * Returns a new BufferedImage that represents a subregion of the given
323         * BufferedImage.  (Note that this method does not use
324         * BufferedImage.getSubimage(), which will defeat image acceleration
325         * strategies on later JDKs.)
326         */
327        private BufferedImage getSubImage(BufferedImage img,
328                                          int x, int y, int w, int h) {
329            BufferedImage ret = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
330            Graphics2D g2 = ret.createGraphics();
331            g2.drawImage(img,
332                         0, 0, w, h,
333                         x, y, x+w, y+h,
334                         null);
335            g2.dispose();
336            return ret;
337        }
338        
339        /**
340         * @inheritDoc
341         */
342        public Insets getBorderInsets(Component c) {
343            int top = showTopShadow ? lineWidth + shadowSize : lineWidth;
344            int left = showLeftShadow ? lineWidth + shadowSize : lineWidth;
345            int bottom = showBottomShadow ? lineWidth + shadowSize : lineWidth;
346            int right = showRightShadow ? lineWidth + shadowSize : lineWidth;
347            return new Insets(top, left, bottom, right);
348        }
349        
350        /**
351         * @inheritDoc
352         */
353        public boolean isBorderOpaque() {
354            return false;
355        }
356        
357        public boolean isShowTopShadow() {
358            return showTopShadow;
359        }
360        
361        public boolean isShowLeftShadow() {
362            return showLeftShadow;
363        }
364        
365        public boolean isShowRightShadow() {
366            return showRightShadow;
367        }
368        
369        public boolean isShowBottomShadow() {
370            return showBottomShadow;
371        }
372        
373        public int getLineWidth() {
374            return lineWidth;
375        }
376        
377        public Color getLineColor() {
378            return lineColor;
379        }
380        
381        public int getShadowSize() {
382            return shadowSize;
383        }
384        
385        public float getShadowOpacity() {
386            return shadowOpacity;
387        }
388        
389        public int getCornerSize() {
390            return cornerSize;
391        }
392    }