1   /*
2    * $Id: DropShadowBorder.java,v 1.13 2006/04/24 22:29:56 gfx Exp $
3    *
4    * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5    * Santa Clara, California 95054, U.S.A. All rights reserved.
6    *
7    * This library is free software; you can redistribute it and/or
8    * modify it under the terms of the GNU Lesser General Public
9    * License as published by the Free Software Foundation; either
10   * version 2.1 of the License, or (at your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this library; if not, write to the Free Software
19   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20   */
21  
22  package org.jdesktop.swingx.border;
23  
24  import java.awt.Color;
25  import java.awt.Component;
26  import java.awt.Graphics;
27  import java.awt.Graphics2D;
28  import java.awt.Insets;
29  import java.awt.Point;
30  import java.awt.Rectangle;
31  import java.awt.RenderingHints;
32  import java.awt.geom.RoundRectangle2D;
33  import java.awt.image.BufferedImage;
34  import java.awt.image.ConvolveOp;
35  import java.awt.image.Kernel;
36  import java.util.HashMap;
37  import java.util.Map;
38  
39  import javax.swing.UIManager;
40  import javax.swing.border.Border;
41  
42  /**
43   * Implements a DropShadow for components. In general, the DropShadowBorder will
44   * work with any rectangular components that do not have a default border installed
45   * as part of the look and feel, or otherwise. For example, DropShadowBorder works
46   * wonderfully with JPanel, but horribly with JComboBox.
47   *
48   * @author rbair
49   */
50  public class DropShadowBorder implements Border {
51      private static enum Position {TOP, TOP_LEFT, LEFT, BOTTOM_LEFT,
52                      BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT}
53  
54      private static final Map<Integer,Map<Position,BufferedImage>> CACHE
55              = new HashMap<Integer,Map<Position,BufferedImage>>();
56  
57      private final Color lineColor;
58      private final int lineWidth;
59      private final int shadowSize;
60      private final float shadowOpacity;
61      private final int cornerSize;
62      private final boolean showTopShadow;
63      private final boolean showLeftShadow;
64      private final boolean showBottomShadow;
65      private final boolean showRightShadow;
66  
67      public DropShadowBorder() {
68          this(UIManager.getColor("Control"), 1, 5);
69      }
70  
71      public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize) {
72          this(lineColor, lineWidth, shadowSize, .5f, 12, false, false, true, true);
73      }
74  
75      public DropShadowBorder(Color lineColor, int lineWidth, boolean showLeftShadow) {
76          this(lineColor, lineWidth, 5, .5f, 12, false, showLeftShadow, true, true);
77      }
78  
79      public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize,
80              float shadowOpacity, int cornerSize, boolean showTopShadow,
81              boolean showLeftShadow, boolean showBottomShadow, boolean showRightShadow) {
82          this.lineColor = lineColor;
83          this.lineWidth = lineWidth;
84          this.shadowSize = shadowSize;
85          this.shadowOpacity = shadowOpacity;
86          this.cornerSize = cornerSize;
87          this.showTopShadow = showTopShadow;
88          this.showLeftShadow = showLeftShadow;
89          this.showBottomShadow = showBottomShadow;
90          this.showRightShadow = showRightShadow;
91      }
92  
93      /**
94       * @inheritDoc
95       */
96      public void paintBorder(Component c, Graphics graphics, int x, int y, int width, int height) {
97          /*
98           * 1) Get images for this border
99           * 2) Paint the images for each side of the border that should be painted
100          */
101        	Map<Position,BufferedImage> images = getImages(null);
102 
103         //compute the edges of the component -- not including the border
104 //        Insets borderInsets = getBorderInsets(c);
105 //        int leftEdge = x + borderInsets.left;
106 //        int rightEdge = x + width - borderInsets.right;
107 //        int topEdge = y + borderInsets.top;
108 //        int bottomEdge = y + height - borderInsets.bottom;
109         Graphics2D g2 = (Graphics2D)graphics.create();
110         g2.setColor(lineColor);
111 
112         //The location and size of the shadows depends on which shadows are being
113         //drawn. For instance, if the left & bottom shadows are being drawn, then
114         //the left shadow extends all the way down to the corner, a corner is drawn,
115         //and then the bottom shadow begins at the corner. If, however, only the
116         //bottom shadow is drawn, then the bottom-left corner is drawn to the
117         //right of the corner, and the bottom shadow is somewhat shorter than before.
118 
119         Point topLeftShadowPoint = null;
120         if (showLeftShadow || showTopShadow) {
121             topLeftShadowPoint = new Point();
122             if (showLeftShadow && !showTopShadow) {
123                 topLeftShadowPoint.setLocation(x, y + shadowSize);
124             } else if (showLeftShadow && showTopShadow) {
125                 topLeftShadowPoint.setLocation(x, y);
126             } else if (!showLeftShadow && showTopShadow) {
127                 topLeftShadowPoint.setLocation(x + shadowSize, y);
128             }
129         }
130 
131         Point bottomLeftShadowPoint = null;
132         if (showLeftShadow || showBottomShadow) {
133             bottomLeftShadowPoint = new Point();
134             if (showLeftShadow && !showBottomShadow) {
135                 bottomLeftShadowPoint.setLocation(x, y + height - shadowSize - shadowSize);
136             } else if (showLeftShadow && showBottomShadow) {
137                 bottomLeftShadowPoint.setLocation(x, y + height - shadowSize);
138             } else if (!showLeftShadow && showBottomShadow) {
139                 bottomLeftShadowPoint.setLocation(x + shadowSize, y + height - shadowSize);
140             }
141         }
142 
143         Point bottomRightShadowPoint = null;
144         if (showRightShadow || showBottomShadow) {
145             bottomRightShadowPoint = new Point();
146             if (showRightShadow && !showBottomShadow) {
147                 bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize - shadowSize);
148             } else if (showRightShadow && showBottomShadow) {
149                 bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize);
150             } else if (!showRightShadow && showBottomShadow) {
151                 bottomRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y + height - shadowSize);
152             }
153         }
154 
155         Point topRightShadowPoint = null;
156         if (showRightShadow || showTopShadow) {
157             topRightShadowPoint = new Point();
158             if (showRightShadow && !showTopShadow) {
159                 topRightShadowPoint.setLocation(x + width - shadowSize, y + shadowSize);
160             } else if (showRightShadow && showTopShadow) {
161                 topRightShadowPoint.setLocation(x + width - shadowSize, y);
162             } else if (!showRightShadow && showTopShadow) {
163                 topRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y);
164             }
165         }
166 
167         g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
168                             RenderingHints.VALUE_INTERPOLATION_BILINEAR);
169 
170         if (showLeftShadow) {
171             Rectangle leftShadowRect =
172                 new Rectangle(x,
173                               topLeftShadowPoint.y + shadowSize,
174                               shadowSize,
175                               bottomLeftShadowPoint.y - topLeftShadowPoint.y - shadowSize);
176             g2.drawImage(images.get(Position.LEFT),
177                          leftShadowRect.x, leftShadowRect.y,
178                          leftShadowRect.width, leftShadowRect.height, null);
179         }
180 
181         if (showBottomShadow) {
182             Rectangle bottomShadowRect =
183                 new Rectangle(bottomLeftShadowPoint.x + shadowSize,
184                               y + height - shadowSize,
185                               bottomRightShadowPoint.x - bottomLeftShadowPoint.x - shadowSize,
186                               shadowSize);
187             g2.drawImage(images.get(Position.BOTTOM),
188                          bottomShadowRect.x, bottomShadowRect.y,
189                          bottomShadowRect.width, bottomShadowRect.height, null);
190         }
191 
192         if (showRightShadow) {
193             Rectangle rightShadowRect =
194                 new Rectangle(x + width - shadowSize,
195                               topRightShadowPoint.y + shadowSize,
196                               shadowSize,
197                               bottomRightShadowPoint.y - topRightShadowPoint.y - shadowSize);
198             g2.drawImage(images.get(Position.RIGHT),
199                          rightShadowRect.x, rightShadowRect.y,
200                          rightShadowRect.width, rightShadowRect.height, null);
201         }
202 
203         if (showTopShadow) {
204             Rectangle topShadowRect =
205                 new Rectangle(topLeftShadowPoint.x + shadowSize,
206                               y,
207                               topRightShadowPoint.x - topLeftShadowPoint.x - shadowSize,
208                               shadowSize);
209             g2.drawImage(images.get(Position.TOP),
210                          topShadowRect.x, topShadowRect.y,
211                          topShadowRect.width, topShadowRect.height, null);
212         }
213 
214         if (showLeftShadow || showTopShadow) {
215             g2.drawImage(images.get(Position.TOP_LEFT),
216                          topLeftShadowPoint.x, topLeftShadowPoint.y, null);
217         }
218         if (showLeftShadow || showBottomShadow) {
219             g2.drawImage(images.get(Position.BOTTOM_LEFT),
220                          bottomLeftShadowPoint.x, bottomLeftShadowPoint.y, null);
221         }
222         if (showRightShadow || showBottomShadow) {
223             g2.drawImage(images.get(Position.BOTTOM_RIGHT),
224                          bottomRightShadowPoint.x, bottomRightShadowPoint.y, null);
225         }
226         if (showRightShadow || showTopShadow) {
227             g2.drawImage(images.get(Position.TOP_RIGHT),
228                          topRightShadowPoint.x, topRightShadowPoint.y, null);
229         }
230 
231         g2.dispose();
232     }
233 
234     private Map<Position,BufferedImage> getImages(Graphics2D g2) {
235         //first, check to see if an image for this size has already been rendered
236         //if so, use the cache. Else, draw and save
237         Map<Position,BufferedImage> images = CACHE.get(shadowSize);
238         if (images == null) {
239             images = new HashMap<Position,BufferedImage>();
240 
241             /*
242              * Do draw a drop shadow, I have to:
243              *  1) Create a rounded rectangle
244              *  2) Create a BufferedImage to draw the rounded rect in
245              *  3) Translate the graphics for the image, so that the rectangle
246              *     is centered in the drawn space. The border around the rectangle
247              *     needs to be shadowWidth wide, so that there is space for the
248              *     shadow to be drawn.
249              *  4) Draw the rounded rect as black, with an opacity of 50%
250              *  5) Create the BLUR_KERNEL
251              *  6) Blur the image
252              *  7) copy off the corners, sides, etc into images to be used for
253              *     drawing the Border
254              */
255             int rectWidth = cornerSize + 1;
256             RoundRectangle2D rect = new RoundRectangle2D.Double(0, 0, rectWidth, rectWidth, cornerSize, cornerSize);
257             int imageWidth = rectWidth + shadowSize * 2;
258             BufferedImage image = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
259             Graphics2D buffer = (Graphics2D)image.getGraphics();
260             buffer.setColor(new Color(0.0f, 0.0f, 0.0f, shadowOpacity));
261             buffer.translate(shadowSize, shadowSize);
262             buffer.fill(rect);
263             buffer.dispose();
264 
265             float blurry = 1.0f / (float)(shadowSize * shadowSize);
266             float[] blurKernel = new float[shadowSize * shadowSize];
267             for (int i=0; i<blurKernel.length; i++) {
268                 blurKernel[i] = blurry;
269             }
270             ConvolveOp blur = new ConvolveOp(new Kernel(shadowSize, shadowSize, blurKernel));
271             BufferedImage targetImage = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
272             ((Graphics2D)targetImage.getGraphics()).drawImage(image, blur, -(shadowSize/2), -(shadowSize/2));
273 
274             int x = 1;
275             int y = 1;
276             int w = shadowSize;
277             int h = shadowSize;
278             images.put(Position.TOP_LEFT, getSubImage(targetImage, x, y, w, h));
279             x = 1;
280             y = h;
281             w = shadowSize;
282             h = 1;
283             images.put(Position.LEFT, getSubImage(targetImage, x, y, w, h));
284             x = 1;
285             y = rectWidth;
286             w = shadowSize;
287             h = shadowSize;
288             images.put(Position.BOTTOM_LEFT, getSubImage(targetImage, x, y, w, h));
289             x = cornerSize + 1;
290             y = rectWidth;
291             w = 1;
292             h = shadowSize;
293             images.put(Position.BOTTOM, getSubImage(targetImage, x, y, w, h));
294             x = rectWidth;
295             y = x;
296             w = shadowSize;
297             h = shadowSize;
298             images.put(Position.BOTTOM_RIGHT, getSubImage(targetImage, x, y, w, h));
299             x = rectWidth;
300             y = cornerSize + 1;
301             w = shadowSize;
302             h = 1;
303             images.put(Position.RIGHT, getSubImage(targetImage, x, y, w, h));
304             x = rectWidth;
305             y = 1;
306             w = shadowSize;
307             h = shadowSize;
308             images.put(Position.TOP_RIGHT, getSubImage(targetImage, x, y, w, h));
309             x = shadowSize;
310             y = 1;
311             w = 1;
312             h = shadowSize;
313             images.put(Position.TOP, getSubImage(targetImage, x, y, w, h));
314 
315             image.flush();
316         }
317         return images;
318     }
319 
320     /**
321      * Returns a new BufferedImage that represents a subregion of the given
322      * BufferedImage.  (Note that this method does not use
323      * BufferedImage.getSubimage(), which will defeat image acceleration
324      * strategies on later JDKs.)
325      */
326     private BufferedImage getSubImage(BufferedImage img,
327                                       int x, int y, int w, int h) {
328         BufferedImage ret = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
329         Graphics2D g2 = ret.createGraphics();
330         g2.drawImage(img,
331                      0, 0, w, h,
332                      x, y, x+w, y+h,
333                      null);
334         g2.dispose();
335         return ret;
336     }
337 
338     /**
339      * @inheritDoc
340      */
341     public Insets getBorderInsets(Component c) {
342         int top = showTopShadow ? lineWidth + shadowSize : lineWidth;
343         int left = showLeftShadow ? lineWidth + shadowSize : lineWidth;
344         int bottom = showBottomShadow ? lineWidth + shadowSize : lineWidth;
345         int right = showRightShadow ? lineWidth + shadowSize : lineWidth;
346         return new Insets(top, left, bottom, right);
347     }
348 
349     /**
350      * @inheritDoc
351      */
352     public boolean isBorderOpaque() {
353         return false;
354     }
355 
356     public boolean isShowTopShadow() {
357         return showTopShadow;
358     }
359 
360     public boolean isShowLeftShadow() {
361         return showLeftShadow;
362     }
363 
364     public boolean isShowRightShadow() {
365         return showRightShadow;
366     }
367 
368     public boolean isShowBottomShadow() {
369         return showBottomShadow;
370     }
371 
372     public int getLineWidth() {
373         return lineWidth;
374     }
375 
376     public Color getLineColor() {
377         return lineColor;
378     }
379 
380     public int getShadowSize() {
381         return shadowSize;
382     }
383 
384     public float getShadowOpacity() {
385         return shadowOpacity;
386     }
387 
388     public int getCornerSize() {
389         return cornerSize;
390     }
391 }