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 }