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 }