Browse Source

Simplify and improve DropShadowBorder

We always displayed all shadows anyway so no need for the extra complexity.
Also, properly handle the corner radius property.
jsvg
Jannis Weis 3 years ago
parent
commit
e31d484ee8
No known key found for this signature in database
GPG Key ID: 7C9D8D4B558049AB
  1. 366
      core/src/main/java/com/github/weisj/darklaf/components/border/DropShadowBorder.java
  2. 8
      core/src/main/java/com/github/weisj/darklaf/ui/internalframe/DarkInternalFrameBorder.java
  3. 6
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java

366
core/src/main/java/com/github/weisj/darklaf/components/border/DropShadowBorder.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
@ -22,17 +22,16 @@ package com.github.weisj.darklaf.components.border;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.ImageObserver;
import java.awt.image.Kernel;
import java.awt.image.*;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.border.Border;
import com.github.weisj.darklaf.util.ColorUtil;
import com.github.weisj.darklaf.util.Disposable;
import com.github.weisj.darklaf.util.ImageUtil;
@ -50,42 +49,33 @@ import com.github.weisj.darklaf.util.ImageUtil;
* 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.
* level and never reset, it might bleed your app memory in case you tend to create many borders
* rapidly.
*
* @author rbair Adaptions made by
* @author Jannis Weis
*/
public class DropShadowBorder implements Border, Serializable {
private static final Map<Integer, BufferedImage[]> CACHE = new HashMap<>();
private static final Map<ImageHashKey, BufferedImage[]> 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(final Color shadowColor, final int shadowSize) {
this(shadowColor, shadowSize, .5f, 12, false, true, true, true);
this(shadowColor, shadowSize, .5f, 12);
}
public DropShadowBorder(final Color shadowColor, final int shadowSize, final float shadowOpacity,
final int cornerSize, final boolean showTopShadow, final boolean showLeftShadow,
final boolean showBottomShadow, final boolean showRightShadow) {
final int cornerSize) {
setShadowColor(shadowColor);
setShadowSize(shadowSize);
setShadowOpacity(shadowOpacity);
setCornerSize(cornerSize);
setShowTopShadow(showTopShadow);
setShowLeftShadow(showLeftShadow);
setShowBottomShadow(showBottomShadow);
setShowRightShadow(showRightShadow);
}
@Override
@ -94,7 +84,7 @@ public class DropShadowBorder implements Border, Serializable {
final BufferedImage[] images = getImages();
final Graphics2D g2 = (Graphics2D) graphics.create();
try (Disposable d = g2::dispose) {
try (Disposable ignored = g2::dispose) {
// 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,
@ -102,115 +92,57 @@ public class DropShadowBorder implements Border, Serializable {
// 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) {
topLeftShadowPoint.setLocation(x, y);
} else {
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) {
bottomLeftShadowPoint.setLocation(x, y + height - shadowSize);
} else {
bottomLeftShadowPoint.setLocation(x + shadowSize, y + height - shadowSize);
}
}
int cornerImageSize = cornerImageSize();
Point bottomRightShadowPoint = null;
if (showRightShadow || showBottomShadow) {
bottomRightShadowPoint = new Point();
if (showRightShadow && !showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize - shadowSize);
} else if (showRightShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize);
} else {
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) {
topRightShadowPoint.setLocation(x + width - shadowSize, y);
} else {
topRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y);
}
}
Point topLeftShadowPoint = new Point(x, y);
Point bottomLeftShadowPoint = new Point(x, y + height - cornerImageSize);
Point bottomRightShadowPoint = new Point(x + width - cornerImageSize, y + height - cornerImageSize);
Point topRightShadowPoint = new Point(x + width - cornerImageSize, y);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
if (showLeftShadow) {
assert topLeftShadowPoint != null && bottomLeftShadowPoint != null;
drawImage(g2, images[Position.LEFT.ordinal()], x, topLeftShadowPoint.y + shadowSize, shadowSize,
bottomLeftShadowPoint.y - topLeftShadowPoint.y - shadowSize, c);
}
if (showBottomShadow) {
assert bottomLeftShadowPoint != null && bottomRightShadowPoint != null;
drawImage(g2, images[Position.BOTTOM.ordinal()], bottomLeftShadowPoint.x + shadowSize,
y + height - shadowSize, bottomRightShadowPoint.x - bottomLeftShadowPoint.x - shadowSize,
shadowSize, c);
}
if (showRightShadow) {
assert topRightShadowPoint != null && bottomRightShadowPoint != null;
drawImage(g2, images[Position.RIGHT.ordinal()], x + width - shadowSize,
topRightShadowPoint.y + shadowSize, shadowSize,
bottomRightShadowPoint.y - topRightShadowPoint.y - shadowSize, c);
}
if (showTopShadow) {
assert topLeftShadowPoint != null && topRightShadowPoint != null;
drawImage(g2, images[Position.TOP.ordinal()], topLeftShadowPoint.x + shadowSize, y,
topRightShadowPoint.x - topLeftShadowPoint.x - shadowSize, shadowSize, c);
}
if (showLeftShadow || showTopShadow) {
drawImage(g2, images[Position.TOP_LEFT.ordinal()], topLeftShadowPoint, c);
}
if (showLeftShadow || showBottomShadow) {
drawImage(g2, images[Position.BOTTOM_LEFT.ordinal()], bottomLeftShadowPoint, c);
}
if (showRightShadow || showBottomShadow) {
drawImage(g2, images[Position.BOTTOM_RIGHT.ordinal()], bottomRightShadowPoint, c);
}
if (showRightShadow || showTopShadow) {
drawImage(g2, images[Position.TOP_RIGHT.ordinal()], topRightShadowPoint, c);
}
drawImage(g2, images[Position.LEFT.ordinal()],
topLeftShadowPoint.x, topLeftShadowPoint.y + cornerImageSize,
shadowSize, bottomLeftShadowPoint.y - topLeftShadowPoint.y - cornerImageSize, c);
drawImage(g2, images[Position.BOTTOM.ordinal()],
bottomLeftShadowPoint.x + cornerImageSize, y + height - shadowSize,
bottomRightShadowPoint.x - bottomLeftShadowPoint.x - cornerImageSize, shadowSize, c);
drawImage(g2, images[Position.RIGHT.ordinal()],
x + width - shadowSize, topRightShadowPoint.y + cornerImageSize, shadowSize,
bottomRightShadowPoint.y - topRightShadowPoint.y - cornerImageSize, c);
drawImage(g2, images[Position.TOP.ordinal()],
topLeftShadowPoint.x + cornerImageSize, topLeftShadowPoint.y,
topRightShadowPoint.x - topLeftShadowPoint.x - cornerImageSize, shadowSize, c);
drawImage(g2, images[Position.TOP_LEFT.ordinal()],
topLeftShadowPoint.x, topLeftShadowPoint.y,
cornerImageSize, cornerImageSize, c);
drawImage(g2, images[Position.BOTTOM_LEFT.ordinal()],
bottomLeftShadowPoint.x, bottomLeftShadowPoint.y,
cornerImageSize, cornerImageSize, c);
drawImage(g2, images[Position.BOTTOM_RIGHT.ordinal()],
bottomRightShadowPoint.x, bottomRightShadowPoint.y,
cornerImageSize, cornerImageSize, c);
drawImage(g2, images[Position.TOP_RIGHT.ordinal()],
topRightShadowPoint.x, topRightShadowPoint.y,
cornerImageSize, cornerImageSize, c);
}
}
protected void drawImage(final Graphics g, final Image image, final Point p, final ImageObserver observer) {
if (p == null) return;
g.drawImage(image, p.x, p.y, observer);
}
protected void drawImage(final Graphics g, final Image image, final int x, final int y, final int w, final int h,
final ImageObserver observer) {
g.drawImage(image, x, y, w, h, observer);
}
@SuppressWarnings("SuspiciousNameCombination")
private int cornerImageSize() {
return getCornerSize() + getShadowSize();
}
private BufferedImage[] getImages() {
// first, check to see if an image for this size has already been rendered
// if so, use the cache. Else, draw and save
BufferedImage[] images = CACHE.get(getBorderHash(shadowSize, shadowOpacity, shadowColor));
BufferedImage[] images = CACHE.get(new ImageHashKey(shadowSize, cornerSize, shadowColor, shadowOpacity));
if (images == null) {
images = new BufferedImage[Position.count()];
@ -233,179 +165,121 @@ public class DropShadowBorder implements Border, Serializable {
*
* 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 = ImageUtil.createCompatibleTranslucentImage(imageWidth, imageWidth);
int rectArc = shadowSize + cornerSize;
int rectWidth = 2 * rectArc + 1;
RoundRectangle2D rect = new RoundRectangle2D.Double(
0, 0, rectWidth, rectWidth, rectArc, rectArc);
final int kernelSize = (3 * shadowSize) / 4;
final int imageSize = rectWidth + 2 * kernelSize;
BufferedImage image = ImageUtil.createCompatibleTranslucentImage(imageSize, imageSize);
Graphics2D buffer = (Graphics2D) image.getGraphics();
try (Disposable d = buffer::dispose) {
buffer.setPaint(new Color(shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(),
(int) (shadowOpacity * 255)));
buffer.translate(shadowSize, shadowSize);
try (Disposable ignored = buffer::dispose) {
buffer.setPaint(ColorUtil.toAlpha(shadowColor, shadowOpacity));
double offset = (imageSize - rectWidth) / 2.0;
buffer.translate(offset, offset);
buffer.fill(rect);
}
float blurry = 1.0f / (float) (shadowSize * shadowSize);
float[] blurKernel = new float[shadowSize * shadowSize];
float blurry = 1.0f / (float) (kernelSize * kernelSize);
float[] blurKernel = new float[kernelSize * kernelSize];
Arrays.fill(blurKernel, blurry);
ConvolveOp blur = new ConvolveOp(new Kernel(shadowSize, shadowSize, blurKernel));
BufferedImage targetImage = ImageUtil.createCompatibleTranslucentImage(imageWidth, imageWidth);
Graphics2D targetGraphics = (Graphics2D) targetImage.getGraphics();
try (Disposable d = targetGraphics::dispose) {
targetGraphics.drawImage(image, blur, -(shadowSize / 2), -(shadowSize / 2));
}
int x = 1;
int y = 1;
int w = shadowSize;
int h = shadowSize;
images[Position.TOP_LEFT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = 1;
y = h;
w = shadowSize;
h = 1;
images[Position.LEFT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = 1;
y = rectWidth;
w = shadowSize;
h = shadowSize;
images[Position.BOTTOM_LEFT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = cornerSize + 1;
y = rectWidth;
w = 1;
h = shadowSize;
images[Position.BOTTOM.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = rectWidth;
y = x;
w = shadowSize;
h = shadowSize;
images[Position.BOTTOM_RIGHT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = rectWidth;
y = cornerSize + 1;
w = shadowSize;
h = 1;
images[Position.RIGHT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = rectWidth;
y = 1;
w = shadowSize;
h = shadowSize;
images[Position.TOP_RIGHT.ordinal()] = getSubImage(targetImage, x, y, w, h);
x = shadowSize;
y = 1;
w = 1;
h = shadowSize;
images[Position.TOP.ordinal()] = getSubImage(targetImage, x, y, w, h);
ConvolveOp blur = new ConvolveOp(new Kernel(kernelSize, kernelSize, blurKernel));
BufferedImage targetImage = blur.filter(image, null);
int cornerImageSize = cornerImageSize();
int sideImageSize = shadowSize;
int cornerOppositeOffset = imageSize - cornerImageSize;
int sideOppositeOffset = imageSize - sideImageSize;
images[Position.TOP_LEFT.ordinal()] = getSubImage(targetImage,
0, 0, cornerImageSize, cornerImageSize);
images[Position.BOTTOM_LEFT.ordinal()] = getSubImage(targetImage,
0, cornerOppositeOffset, cornerImageSize, cornerImageSize);
images[Position.BOTTOM_RIGHT.ordinal()] = getSubImage(targetImage,
cornerOppositeOffset, cornerOppositeOffset, cornerImageSize, cornerImageSize);
images[Position.TOP_RIGHT.ordinal()] = getSubImage(targetImage,
cornerOppositeOffset, 0, cornerImageSize, cornerImageSize);
images[Position.LEFT.ordinal()] = getSubImage(targetImage,
0, cornerImageSize, sideImageSize, 1);
images[Position.BOTTOM.ordinal()] = getSubImage(targetImage,
cornerImageSize, sideOppositeOffset, 1, sideImageSize);
images[Position.RIGHT.ordinal()] = getSubImage(targetImage,
sideOppositeOffset, cornerImageSize, sideImageSize, 1);
images[Position.TOP.ordinal()] = getSubImage(targetImage,
cornerImageSize, 0, 1, sideImageSize);
image.flush();
CACHE.put(getBorderHash(shadowSize, shadowOpacity, shadowColor), images);
targetImage.flush();
CACHE.put(new ImageHashKey(shadowSize, cornerSize, shadowColor, shadowOpacity), images);
}
return images;
}
private static int getBorderHash(final int shadowSize, final float opacity, final Color shadowColor) {
int result = shadowSize;
result = 31 * result + (opacity != +0.0f ? Float.floatToIntBits(opacity) : 0);
result = 31 * result + (shadowColor != null ? shadowColor.hashCode() : 0);
return result;
}
/**
* 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 BufferedImage getSubImage(final BufferedImage img, final int x, final int y, final int w, final int h) {
return img.getSubimage(x, y, w, h);
BufferedImage ret = ImageUtil.createCompatibleTranslucentImage(w, h);
Graphics2D g = ret.createGraphics();
try (Disposable ignored = g::dispose) {
g.drawImage(img, 0, 0, w, h, x, y, x + w, y + h, null);
}
return ret;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public Insets getBorderInsets(final 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);
return new Insets(shadowSize, shadowSize, shadowSize, shadowSize);
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public boolean isBorderOpaque() {
return false;
}
public boolean isShowTopShadow() {
return showTopShadow;
}
public DropShadowBorder setShowTopShadow(final boolean showTopShadow) {
this.showTopShadow = showTopShadow;
return this;
}
public boolean isShowLeftShadow() {
return showLeftShadow;
}
public DropShadowBorder setShowLeftShadow(final boolean showLeftShadow) {
this.showLeftShadow = showLeftShadow;
return this;
}
public boolean isShowRightShadow() {
return showRightShadow;
}
public DropShadowBorder setShowRightShadow(final boolean showRightShadow) {
this.showRightShadow = showRightShadow;
return this;
}
public boolean isShowBottomShadow() {
return showBottomShadow;
}
public DropShadowBorder setShowBottomShadow(final boolean showBottomShadow) {
this.showBottomShadow = showBottomShadow;
return this;
}
public int getShadowSize() {
return shadowSize;
}
public DropShadowBorder setShadowSize(final int shadowSize) {
public void setShadowSize(final int shadowSize) {
this.shadowSize = shadowSize;
return this;
}
public Color getShadowColor() {
return shadowColor;
}
public DropShadowBorder setShadowColor(final Color shadowColor) {
public void setShadowColor(final Color shadowColor) {
this.shadowColor = shadowColor;
if (shadowColor == null) this.shadowColor = Color.BLACK;
return this;
}
public float getShadowOpacity() {
return shadowOpacity;
}
public DropShadowBorder setShadowOpacity(final float shadowOpacity) {
public void setShadowOpacity(final float shadowOpacity) {
this.shadowOpacity = shadowOpacity;
return this;
}
public int getCornerSize() {
return cornerSize;
}
public DropShadowBorder setCornerSize(final int cornerSize) {
this.cornerSize = cornerSize;
return this;
public void setCornerSize(final int cornerSize) {
this.cornerSize = Math.min(shadowSize, cornerSize);
}
private enum Position {
@ -422,4 +296,34 @@ public class DropShadowBorder implements Border, Serializable {
return 8;
}
}
private static final class ImageHashKey {
private final int shadowSize;
private final int cornerSize;
private final Color shadowColor;
private final float opacity;
private ImageHashKey(final int shadowSize, final int cornerSize, final Color shadowColor, final float opacity) {
this.shadowSize = shadowSize;
this.cornerSize = cornerSize;
this.shadowColor = shadowColor;
this.opacity = opacity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ImageHashKey)) return false;
ImageHashKey that = (ImageHashKey) o;
return shadowSize == that.shadowSize
&& cornerSize == that.cornerSize
&& Float.compare(that.opacity, opacity) == 0
&& Objects.equals(shadowColor, that.shadowColor);
}
@Override
public int hashCode() {
return Objects.hash(shadowSize, cornerSize, shadowColor, opacity);
}
}
}

8
core/src/main/java/com/github/weisj/darklaf/ui/internalframe/DarkInternalFrameBorder.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
@ -39,13 +39,9 @@ public class DarkInternalFrameBorder extends DropShadowBorder implements UIResou
inactiveOpacity = UIManager.getInt("InternalFrame.inactiveShadowOpacity") / 100f;
int shadowSize = UIManager.getInt("InternalFrame.shadowSize");
setShadowSize(shadowSize);
setCornerSize(2 * shadowSize);
setCornerSize(shadowSize);
setShadowOpacity(activeOpacity);
setShadowColor(UIManager.getColor("InternalFrame.borderShadowColor"));
setShowTopShadow(true);
setShowBottomShadow(true);
setShowLeftShadow(true);
setShowRightShadow(true);
}
@Override

6
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
@ -55,8 +55,8 @@ public class DarkTooltipBorder implements Border, AlignableTooltipBorder {
bubbleBorder.setPointerSide(Alignment.CENTER);
int shadowSize = UIManager.getInt("ToolTip.shadowSize");
float opacity = UIManager.getInt("ToolTip.shadowOpacity") / 100.0f;
shadowBorder = new DropShadowBorder(UIManager.getColor("ToolTip.borderShadowColor"), shadowSize, opacity,
2 * shadowSize, true, true, true, true);
Color shadowColor = UIManager.getColor("ToolTip.borderShadowColor");
shadowBorder = new DropShadowBorder(shadowColor, shadowSize, opacity, borderRadius);
paintShadow = UIManager.getBoolean("ToolTip.paintShadow");
}

Loading…
Cancel
Save