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