Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>This is a complete <strong>HACK</strong></p> <p>This will require you to have a copy of <a href="http://www.jhlabs.com/ip/filters/index.html" rel="noreferrer">JH-Labs Filters</a> for the blur implementation</p> <p>This is an expensive operation as it uses a blur operation, the reason I use it is that will take into account the the shape of the component it is shadowing.</p> <p><img src="https://i.stack.imgur.com/LFJg1.png" alt="enter image description here"></p> <p>The main problem you have is that borders aren't them selves, transparent, there's no way to really have an opaque component and a transparent border. Hench the hack</p> <pre><code>public class TestDropShadowBorder { public static void main(String[] args) { new TestDropShadowBorder(); } public TestDropShadowBorder() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException ex) { } catch (InstantiationException ex) { } catch (IllegalAccessException ex) { } catch (UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TestPane extends JPanel { public TestPane() { setBackground(Color.RED); setBorder(new EmptyBorder(20, 20, 20, 20)); setLayout(new BorderLayout()); JPanel drop = new JPanel(); drop.setOpaque(false); DropShadowBorder border = new DropShadowBorder(); border.setFillContentArea(true); drop.setBorder(new CompoundBorder(border, new LineBorder(Color.BLACK))); add(drop); } } public static class DropShadowBorder implements Border { protected static final int SHADOW_SIZE = 4; protected static final Map&lt;Component, DropShadowBorder.CachedBorder&gt; BORDER_CACHE = new WeakHashMap&lt;Component, CachedBorder&gt;(5); private boolean fillContentArea; private int shadowSize; private float shadowOpacity; private Color shadowColor; public DropShadowBorder() { this(SHADOW_SIZE, Color.BLACK, 0.5f, true); } public DropShadowBorder(boolean paintContentArea) { this(SHADOW_SIZE, Color.BLACK, 0.5f, paintContentArea); } public DropShadowBorder(int shadowSize) { this(shadowSize, Color.BLACK, 0.5f, true); } public DropShadowBorder(Color shadowColor) { this(SHADOW_SIZE, shadowColor, 0.5f, true); } public DropShadowBorder(int shadowSize, Color showColor) { this(shadowSize, showColor, 0.5f, true); } public DropShadowBorder(int shadowSize, float opacity) { this(shadowSize, Color.BLACK, opacity, true); } public DropShadowBorder(Color shadowColor, float opacity) { this(SHADOW_SIZE, shadowColor, opacity, true); } public DropShadowBorder(int shadowSize, Color shadowColor, float opacity) { this(shadowSize, shadowColor, opacity, true); } public DropShadowBorder(int shadowSize, boolean paintContentArea) { this(shadowSize, Color.BLACK, 0.5f, paintContentArea); } public DropShadowBorder(Color shadowColor, boolean paintContentArea) { this(SHADOW_SIZE, shadowColor, 0.5f, paintContentArea); } public DropShadowBorder(int shadowSize, Color showColor, boolean paintContentArea) { this(shadowSize, showColor, 0.5f, paintContentArea); } public DropShadowBorder(int shadowSize, float opacity, boolean paintContentArea) { this(shadowSize, Color.BLACK, opacity, paintContentArea); } public DropShadowBorder(Color shadowColor, float opacity, boolean paintContentArea) { this(SHADOW_SIZE, shadowColor, opacity, paintContentArea); } public DropShadowBorder(int shadowSize, Color showColor, float opacity, boolean paintContentArea) { setShadowSize(shadowSize); setShadowColor(showColor); setShadowOpacity(opacity); setFillContentArea(paintContentArea); } public void setShadowColor(Color shadowColor) { this.shadowColor = shadowColor; } public void setShadowOpacity(float shadowOpacity) { this.shadowOpacity = shadowOpacity; } public Color getShadowColor() { return shadowColor; } public float getShadowOpacity() { return shadowOpacity; } public void setShadowSize(int size) { shadowSize = size; } public int getShadowSize() { return shadowSize; } public static GraphicsConfiguration getGraphicsConfiguration() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); } public static BufferedImage createCompatibleImage(int width, int height) { return createCompatibleImage(width, height, Transparency.TRANSLUCENT); } public static BufferedImage createCompatibleImage(int width, int height, int transparency) { BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency); image.coerceData(true); return image; } public static BufferedImage generateShadow(BufferedImage imgSource, int size, Color color, float alpha) { int imgWidth = imgSource.getWidth() + (size * 2); int imgHeight = imgSource.getHeight() + (size * 2); BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2 = imgMask.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); int x = Math.round((imgWidth - imgSource.getWidth()) / 2f); int y = Math.round((imgHeight - imgSource.getHeight()) / 2f); g2.drawImage(imgSource, x, y, null); g2.dispose(); // ---- Blur here --- BufferedImage imgGlow = generateBlur(imgMask, size, color, alpha); // // BufferedImage imgGlow = ImageUtilities.createCompatibleImage(imgWidth, imgHeight); // g2 = imgGlow.createGraphics(); // // g2.drawImage(imgMask, 0, 0, null); // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha)); // g2.setColor(color); // // g2.fillRect(x, y, imgSource.getWidth(), imgSource.getHeight()); // g2.dispose(); // // imgGlow = filter.filter(imgGlow, null); // ---- Blur here ---- // imgGlow = ImageUtilities.applyMask(imgGlow, imgMask, AlphaComposite.DST_OUT); return imgGlow; } public static BufferedImage generateBlur(BufferedImage imgSource, int size, Color color, float alpha) { GaussianFilter filter = new GaussianFilter(size); int imgWidth = imgSource.getWidth(); int imgHeight = imgSource.getHeight(); BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2d = imgBlur.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g2d.drawImage(imgSource, 0, 0, null); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha)); g2d.setColor(color); g2d.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight()); g2d.dispose(); imgBlur = filter.filter(imgBlur, null); return imgBlur; } public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { /* * Because of the amount of time it can take to render the drop shadow, * we cache the results in a static cache, based on the component * and the components size. * * This allows the shadows to repainted quickly so long as the component * hasn't changed in size. */ BufferedImage dropShadow = null; DropShadowBorder.CachedBorder cached = BORDER_CACHE.get(c); if (cached != null) { dropShadow = cached.getImage(c); } if (dropShadow == null) { int shadowSize = getShadowSize(); float opacity = getShadowOpacity(); Color color = getShadowColor(); // Create a blank canvas, from which the actually border can be generated // from... // The ahadow routine can actually generate a non-rectangular border, but // because we don't have a suitable template to run from, we need to // set this up our selves... // It would be nice to be able to user the component itself, but this will // have to BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); g2d.fillRect(0, 0, width - (shadowSize * 2), height - (shadowSize * 2)); g2d.dispose(); // Generate the shadow BufferedImage shadow = generateShadow(img, shadowSize, getShadowColor(), getShadowOpacity()); // We need to produce a clipping result, cause the border is painted ontop // of the base component... BufferedImage clipedShadow = createCompatibleImage(width, height, Transparency.TRANSLUCENT); g2d = clipedShadow.createGraphics(); Shape clip = g2d.getClip(); // First we create a "area" filling the avaliable space... Area area = new Area(new Rectangle(width, height)); // Then we subtract the space left over for the component area.subtract(new Area(new Rectangle(width - (shadowSize * 2), height - (shadowSize * 2)))); // And then apply the clip g2d.setClip(area); // Then draw the shadow image // g2d.drawImage(shadow, -(shadowSize / 2), -(shadowSize / 2), c); g2d.drawImage(shadow, 0, 0, c); g2d.setClip(clip); if (!c.isOpaque() &amp;&amp; isFillContentArea()) { area = new Area(new Rectangle(width - (shadowSize * 2), height - (shadowSize * 2))); g2d.setColor(c.getBackground()); g2d.fill(area); } // g2d.setColor(Color.RED); // g2d.drawRect(x, y, width - 1, height - 1); // // g2d.setColor(Color.GREEN); // g2d.drawRect(x, y, width - (shadowSize * 2), height - (shadowSize * 2)); g2d.dispose(); dropShadow = clipedShadow; BORDER_CACHE.put(c, new CachedBorder(dropShadow, c.getSize())); } g.drawImage(dropShadow, x, y, c); // if (!c.isOpaque() &amp;&amp; isFillContentArea()) { // // Graphics2D g2d = (Graphics2D) g; // // Area area = new Area(new Rectangle(width - (shadowSize * 2), height - (shadowSize * 2))); // g2d.setColor(c.getBackground()); // g2d.fill(area); // // } // g.setColor(Color.MAGENTA); // g.drawRect(x + 1, y + 1, width - (shadowSize * 2) - 1, height - (shadowSize * 2) - 1); } public Insets getBorderInsets(Component cmpnt) { return new Insets(0, 0, getShadowSize() * 2, getShadowSize() * 2); } public boolean isBorderOpaque() { return false; } /** * Returns if the content area should be painted by this border when the * parent component is opaque... * * The problem is, the paintComponent method will paint the WHOLE component * background, including the border area. This is a reasonable assumption to * make, but it makes the shadow border really show up when the parent * component is a different color. * * This allows the border to take control of that fact. * * When using it, you will need to try and make this the first border to get * painted though :P * * @return */ public boolean isFillContentArea() { return fillContentArea; } public void setFillContentArea(boolean fill) { fillContentArea = fill; } protected class CachedBorder { private BufferedImage image; private Dimension size; public CachedBorder(BufferedImage border, Dimension size) { this.image = border; this.size = size; } public BufferedImage getImage(Component comp) { BufferedImage dropShadow = null; if (comp.getSize().equals(size)) { dropShadow = image; } return dropShadow; } } } } </code></pre> <p><strong>UPDATED with additional Example</strong></p> <p>The drop shadow border has limitations, it can't take into consideration the shape of the component, as the time the border is painted, the component hasn't begin, so we have no reference point.</p> <p><img src="https://i.stack.imgur.com/9NFJd.png" alt="enter image description here"><img src="https://i.stack.imgur.com/GW2JF.png" alt="enter image description here"></p> <p>In order to be able to generate a drop shadow which takes into consideration the shape of the component, we need to create a custom component and inject our border directly into the paint process.</p> <pre><code>public class TestDropShadowBorder { public static void main(String[] args) { new TestDropShadowBorder(); } public TestDropShadowBorder() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException ex) { } catch (InstantiationException ex) { } catch (IllegalAccessException ex) { } catch (UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TestPane extends JPanel { public TestPane() { setBackground(Color.RED); setBorder(new EmptyBorder(20, 20, 20, 20)); setLayout(new BorderLayout()); add(new RoundedPane()); } } public class RoundedPane extends JPanel { private int shadowSize = 5; public RoundedPane() { // This is very important, as part of the panel is going to be transparent setOpaque(false); } @Override public Insets getInsets() { return new Insets(0, 0, 10, 10); } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } @Override protected void paintComponent(Graphics g) { int width = getWidth() - 1; int height = getHeight() - 1; Graphics2D g2d = (Graphics2D) g.create(); applyQualityProperties(g2d); Insets insets = getInsets(); Rectangle bounds = getBounds(); bounds.x = insets.left; bounds.y = insets.top; bounds.width = width - (insets.left + insets.right); bounds.height = height - (insets.top + insets.bottom); RoundRectangle2D shape = new RoundRectangle2D.Float(bounds.x, bounds.y, bounds.width, bounds.height, 20, 20); /** * * THIS SHOULD BE CAHCED AND ONLY UPDATED WHEN THE SIZE OF THE * COMPONENT CHANGES ** */ BufferedImage img = createCompatibleImage(bounds.width, bounds.height); Graphics2D tg2d = img.createGraphics(); applyQualityProperties(g2d); tg2d.setColor(Color.BLACK); tg2d.translate(-bounds.x, -bounds.y); tg2d.fill(shape); tg2d.dispose(); BufferedImage shadow = generateShadow(img, shadowSize, Color.BLACK, 0.5f); g2d.drawImage(shadow, shadowSize, shadowSize, this); g2d.setColor(getBackground()); g2d.fill(shape); /** * THIS ONE OF THE ONLY OCCASIONS THAT I WOULDN'T CALL * super.paintComponent * */ getUI().paint(g2d, this); g2d.setColor(Color.GRAY); g2d.draw(shape); g2d.dispose(); } } public static GraphicsConfiguration getGraphicsConfiguration() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); } public static BufferedImage createCompatibleImage(int width, int height) { return createCompatibleImage(width, height, Transparency.TRANSLUCENT); } public static void applyQualityProperties(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); } public static BufferedImage createCompatibleImage(int width, int height, int transparency) { BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency); image.coerceData(true); return image; } public static BufferedImage generateShadow(BufferedImage imgSource, int size, Color color, float alpha) { int imgWidth = imgSource.getWidth() + (size * 2); int imgHeight = imgSource.getHeight() + (size * 2); BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2 = imgMask.createGraphics(); applyQualityProperties(g2); int x = Math.round((imgWidth - imgSource.getWidth()) / 2f); int y = Math.round((imgHeight - imgSource.getHeight()) / 2f); // g2.drawImage(imgSource, x, y, null); g2.drawImage(imgSource, 0, 0, null); g2.dispose(); // ---- Blur here --- BufferedImage imgShadow = generateBlur(imgMask, size, color, alpha); return imgShadow; } public static BufferedImage generateBlur(BufferedImage imgSource, int size, Color color, float alpha) { GaussianFilter filter = new GaussianFilter(size); int imgWidth = imgSource.getWidth(); int imgHeight = imgSource.getHeight(); BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2d = imgBlur.createGraphics(); applyQualityProperties(g2d); g2d.drawImage(imgSource, 0, 0, null); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha)); g2d.setColor(color); g2d.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight()); g2d.dispose(); imgBlur = filter.filter(imgBlur, null); return imgBlur; } } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload