![JAR search and dependency download from the Maven repository](/logo.png)
org.jdesktop.swingx.border.DropShadowBorder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swingx-all Show documentation
Show all versions of swingx-all Show documentation
Fork of the inactive swingx-all library
/*
* $Id: DropShadowBorder.java 4147 2012-02-01 17:13:24Z kschaefe $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.border;
import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.util.GraphicsUtilities;
import javax.swing.border.Border;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.Serializable;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
/**
* Implements a DropShadow for components. In general, the DropShadowBorder will
* work with any rectangular components that do not have a default border
* installed as part of the look and feel, or otherwise. For example,
* DropShadowBorder works wonderfully with JPanel, but horribly with JComboBox.
*
* Note: {@code DropShadowBorder} should usually be added to non-opaque
* components, otherwise the background is likely to bleed through.
* Note: Since generating drop shadows is relatively expensive operation,
* {@code DropShadowBorder} keeps internal static cache that allows sharing
* same border for multiple re-rendering and between different instances of the
* class. Since this cache is shared at class level and never reset, it might
* bleed your app memory in case you tend to create many different borders
* rapidly.
*
* @author rbair
*/
@JavaBean
public class DropShadowBorder implements Border, Serializable {
/**
*
*/
private static final long serialVersionUID = 715287754750604058L;
private enum Position {
TOP, TOP_LEFT, LEFT, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT
}
private static final Map> CACHE = new HashMap<>();
private Color shadowColor;
private int shadowSize;
private float shadowOpacity;
private int cornerSize;
private boolean showTopShadow;
private boolean showLeftShadow;
private boolean showBottomShadow;
private boolean showRightShadow;
public DropShadowBorder() {
this(Color.BLACK, 5);
}
public DropShadowBorder(Color shadowColor, int shadowSize) {
this(shadowColor, shadowSize, .5f, 12, false, false, true, true);
}
public DropShadowBorder(boolean showLeftShadow) {
this(Color.BLACK, 5, .5f, 12, false, showLeftShadow, true, true);
}
public DropShadowBorder(Color shadowColor, int shadowSize,
float shadowOpacity, int cornerSize, boolean showTopShadow,
boolean showLeftShadow, boolean showBottomShadow, boolean showRightShadow) {
this.shadowColor = shadowColor;
this.shadowSize = shadowSize;
this.shadowOpacity = shadowOpacity;
this.cornerSize = cornerSize;
this.showTopShadow = showTopShadow;
this.showLeftShadow = showLeftShadow;
this.showBottomShadow = showBottomShadow;
this.showRightShadow = showRightShadow;
}
/**
* {@inheritDoc}
*/
@Override
public void paintBorder(Component c, Graphics graphics, int x, int y, int width, int height) {
/*
* 1) Get images for this border
* 2) Paint the images for each side of the border that should be painted
*/
Map images = getImages((Graphics2D) graphics);
Graphics2D g2 = (Graphics2D) graphics.create();
try {
//The location and size of the shadows depends on which shadows are being
//drawn. For instance, if the left & bottom shadows are being drawn, then
//the left shadow extends all the way down to the corner, a corner is drawn,
//and then the bottom shadow begins at the corner. If, however, only the
//bottom shadow is drawn, then the bottom-left corner is drawn to the
//right of the corner, and the bottom shadow is somewhat shorter than before.
int shadowOffset = 2; //the distance between the shadow and the edge
Point topLeftShadowPoint = null;
if (showLeftShadow || showTopShadow) {
topLeftShadowPoint = new Point();
if (showLeftShadow && !showTopShadow) {
topLeftShadowPoint.setLocation(x, y + shadowOffset);
} else if (showLeftShadow && showTopShadow) {
topLeftShadowPoint.setLocation(x, y);
} else if (!showLeftShadow && showTopShadow) {
topLeftShadowPoint.setLocation(x + shadowSize, y);
}
}
Point bottomLeftShadowPoint = null;
if (showLeftShadow || showBottomShadow) {
bottomLeftShadowPoint = new Point();
if (showLeftShadow && !showBottomShadow) {
bottomLeftShadowPoint.setLocation(x, y + height - shadowSize - shadowSize);
} else if (showLeftShadow && showBottomShadow) {
bottomLeftShadowPoint.setLocation(x, y + height - shadowSize);
} else if (!showLeftShadow && showBottomShadow) {
bottomLeftShadowPoint.setLocation(x + shadowSize, y + height - shadowSize);
}
}
Point bottomRightShadowPoint = null;
if (showRightShadow || showBottomShadow) {
bottomRightShadowPoint = new Point();
if (showRightShadow && !showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize - shadowSize);
} else if (showRightShadow && showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize);
} else if (!showRightShadow && showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y + height - shadowSize);
}
}
Point topRightShadowPoint = null;
if (showRightShadow || showTopShadow) {
topRightShadowPoint = new Point();
if (showRightShadow && !showTopShadow) {
topRightShadowPoint.setLocation(x + width - shadowSize, y + shadowOffset);
} else if (showRightShadow && showTopShadow) {
topRightShadowPoint.setLocation(x + width - shadowSize, y);
} else if (!showRightShadow && showTopShadow) {
topRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y);
}
}
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
if (showLeftShadow) {
Rectangle leftShadowRect = new Rectangle(
x, topLeftShadowPoint.y + shadowSize,
shadowSize, bottomLeftShadowPoint.y - topLeftShadowPoint.y - shadowSize
);
g2.drawImage(
images.get(Position.LEFT),
leftShadowRect.x, leftShadowRect.y, leftShadowRect.width, leftShadowRect.height,
null
);
}
if (showBottomShadow) {
Rectangle bottomShadowRect = new Rectangle(
bottomLeftShadowPoint.x + shadowSize, y + height - shadowSize,
bottomRightShadowPoint.x - bottomLeftShadowPoint.x - shadowSize, shadowSize
);
g2.drawImage(
images.get(Position.BOTTOM),
bottomShadowRect.x, bottomShadowRect.y, bottomShadowRect.width, bottomShadowRect.height,
null
);
}
if (showRightShadow) {
Rectangle rightShadowRect = new Rectangle(
x + width - shadowSize, topRightShadowPoint.y + shadowSize,
shadowSize, bottomRightShadowPoint.y - topRightShadowPoint.y - shadowSize
);
g2.drawImage(
images.get(Position.RIGHT), rightShadowRect.x, rightShadowRect.y, rightShadowRect.width, rightShadowRect.height, null
);
}
if (showTopShadow) {
Rectangle topShadowRect = new Rectangle(
topLeftShadowPoint.x + shadowSize, y,
topRightShadowPoint.x - topLeftShadowPoint.x - shadowSize, shadowSize
);
g2.drawImage(
images.get(Position.TOP), topShadowRect.x, topShadowRect.y, topShadowRect.width, topShadowRect.height, null
);
}
if (showLeftShadow || showTopShadow) {
g2.drawImage(images.get(Position.TOP_LEFT), topLeftShadowPoint.x, topLeftShadowPoint.y, null);
}
if (showLeftShadow || showBottomShadow) {
g2.drawImage(images.get(Position.BOTTOM_LEFT), bottomLeftShadowPoint.x, bottomLeftShadowPoint.y, null);
}
if (showRightShadow || showBottomShadow) {
g2.drawImage(images.get(Position.BOTTOM_RIGHT), bottomRightShadowPoint.x, bottomRightShadowPoint.y, null);
}
if (showRightShadow || showTopShadow) {
g2.drawImage(images.get(Position.TOP_RIGHT), topRightShadowPoint.x, topRightShadowPoint.y, null);
}
} finally {
g2.dispose();
}
}
private Map getImages(Graphics2D g2) {
//first, check to see if an image for this size has already been rendered
//if so, use the cache. Else, draw and save
double hash = shadowSize + shadowColor.hashCode() * .3 + shadowOpacity * .12; //TODO do a real hash
Map images = CACHE.get(hash);
if (images == null) {
images = new EnumMap<>(Position.class);
/*
* To draw a drop shadow, I have to:
* 1) Create a rounded rectangle
* 2) Create a BufferedImage to draw the rounded rect in
* 3) Translate the graphics for the image, so that the rectangle
* is centered in the drawn space. The border around the rectangle
* needs to be shadowWidth wide, so that there is space for the
* shadow to be drawn.
* 4) Draw the rounded rect as shadowColor, with an opacity of shadowOpacity
* 5) Create the BLUR_KERNEL
* 6) Blur the image
* 7) copy off the corners, sides, etc into images to be used for
* drawing the Border
*/
int rectWidth = cornerSize + 1;
RoundRectangle2D rect = new RoundRectangle2D.Double(0, 0, rectWidth, rectWidth, cornerSize, cornerSize);
int imageWidth = rectWidth + shadowSize * 2;
BufferedImage image = GraphicsUtilities.createCompatibleTranslucentImage(imageWidth, imageWidth);
Graphics2D buffer = (Graphics2D) image.getGraphics();
try {
buffer.setPaint(new Color(
shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(), (int) (shadowOpacity * 255)
));
buffer.translate(shadowSize, shadowSize);
buffer.fill(rect);
} finally {
buffer.dispose();
}
float blurry = 1.0f / (shadowSize * shadowSize);
float[] blurKernel = new float[shadowSize * shadowSize];
Arrays.fill(blurKernel, blurry);
ConvolveOp blur = new ConvolveOp(new Kernel(shadowSize, shadowSize, blurKernel));
BufferedImage targetImage = GraphicsUtilities.createCompatibleTranslucentImage(imageWidth, imageWidth);
((Graphics2D) targetImage.getGraphics()).drawImage(image, blur, -(shadowSize / 2), -(shadowSize / 2));
int x = 1;
int y = 1;
int w = shadowSize;
int h = shadowSize;
images.put(Position.TOP_LEFT, getSubImage(targetImage, x, y, w, h));
x = 1;
y = h;
w = shadowSize;
h = 1;
images.put(Position.LEFT, getSubImage(targetImage, x, y, w, h));
x = 1;
y = rectWidth;
w = shadowSize;
h = shadowSize;
images.put(Position.BOTTOM_LEFT, getSubImage(targetImage, x, y, w, h));
x = cornerSize + 1;
y = rectWidth;
w = 1;
h = shadowSize;
images.put(Position.BOTTOM, getSubImage(targetImage, x, y, w, h));
x = rectWidth;
y = x;
w = shadowSize;
h = shadowSize;
images.put(Position.BOTTOM_RIGHT, getSubImage(targetImage, x, y, w, h));
x = rectWidth;
y = cornerSize + 1;
w = shadowSize;
h = 1;
images.put(Position.RIGHT, getSubImage(targetImage, x, y, w, h));
x = rectWidth;
y = 1;
w = shadowSize;
h = shadowSize;
images.put(Position.TOP_RIGHT, getSubImage(targetImage, x, y, w, h));
x = shadowSize;
y = 1;
w = 1;
h = shadowSize;
images.put(Position.TOP, getSubImage(targetImage, x, y, w, h));
image.flush();
CACHE.put(hash, images); //TODO do a real hash
}
return images;
}
/**
* Returns a new BufferedImage that represents a subregion of the given
* BufferedImage. (Note that this method does not use
* BufferedImage.getSubimage(), which will defeat image acceleration
* strategies on later JDKs.)
*/
private static BufferedImage getSubImage(BufferedImage img, int x, int y, int w, int h) {
BufferedImage ret = GraphicsUtilities.createCompatibleTranslucentImage(w, h);
Graphics2D g2 = ret.createGraphics();
try {
g2.drawImage(img, 0, 0, w, h, x, y, x + w, y + h, null);
} finally {
g2.dispose();
}
return ret;
}
/**
* @inheritDoc
*/
@Override
public Insets getBorderInsets(Component c) {
int top = showTopShadow ? shadowSize : 0;
int left = showLeftShadow ? shadowSize : 0;
int bottom = showBottomShadow ? shadowSize : 0;
int right = showRightShadow ? shadowSize : 0;
return new Insets(top, left, bottom, right);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isBorderOpaque() {
return false;
}
public boolean isShowTopShadow() {
return showTopShadow;
}
public boolean isShowLeftShadow() {
return showLeftShadow;
}
public boolean isShowRightShadow() {
return showRightShadow;
}
public boolean isShowBottomShadow() {
return showBottomShadow;
}
public int getShadowSize() {
return shadowSize;
}
public Color getShadowColor() {
return shadowColor;
}
public float getShadowOpacity() {
return shadowOpacity;
}
public int getCornerSize() {
return cornerSize;
}
public void setShadowColor(Color shadowColor) {
this.shadowColor = shadowColor;
}
public void setShadowSize(int shadowSize) {
this.shadowSize = shadowSize;
}
public void setShadowOpacity(float shadowOpacity) {
this.shadowOpacity = shadowOpacity;
}
public void setCornerSize(int cornerSize) {
this.cornerSize = cornerSize;
}
public void setShowTopShadow(boolean showTopShadow) {
this.showTopShadow = showTopShadow;
}
public void setShowLeftShadow(boolean showLeftShadow) {
this.showLeftShadow = showLeftShadow;
}
public void setShowBottomShadow(boolean showBottomShadow) {
this.showBottomShadow = showBottomShadow;
}
public void setShowRightShadow(boolean showRightShadow) {
this.showRightShadow = showRightShadow;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy