1

I want to add a dropshadow border around an undecorated JFrame. I found this on stackoverflow: Undecorated JFrame shadow

However this is causing issues with a heavyweight component in my frame from this known issue when I'm setting the JFrame backgound color to translucent.

Is there any other way to accomplish this? I am thinking that I would need a special LAF for my JFrames that has no Title area and no window border, but I have no experience writing LAFs.

JxBrowser is the heavyweight component in my example that renders transparent. They have an option to render in lightweight rendering mode but my memory and cpu skyrocket and is unacceptable. There should be an alternative way to draw a shadow border around my undecorated jframe.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
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.util.HashMap;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;

import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.swing.BrowserView;

public class ShadowWindowBrowser {

    private static enum Position {
        TOP, TOP_LEFT, LEFT, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT
    };
    private static final Map<Integer, Map<Position, BufferedImage>> CACHE = new HashMap<Integer, Map<Position, BufferedImage>>();

    public static void main(String[] args) {
        new ShadowWindowBrowser();
    }

    public ShadowWindowBrowser() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setUndecorated(true);
                frame.setBackground(new Color(0, 0, 0, 0));
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setContentPane(new ShadowPane());

                Browser browser = new Browser();
                BrowserView browserView = new BrowserView(browser);
                browser.loadURL("http://google.com");

                JPanel panel = new JPanel(new BorderLayout());
                panel.add(browserView, BorderLayout.CENTER);

                frame.add(panel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ShadowPane extends JPanel {

        private Border dropShadowBorder = new DropShadowBorder(UIManager.getColor("Control"), 1, 5, .5f, 12, true, true, true, true);

        public ShadowPane() {
            setLayout(new BorderLayout());
            setOpaque(false);
            setBackground(Color.BLACK);
            setBorder(dropShadowBorder);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500, 500);
        }
    }

    class DropShadowBorder implements Border {

        private Color lineColor;
        private int lineWidth;
        private int shadowSize;
        private float shadowOpacity;
        private int cornerSize;
        private boolean showTopShadow;
        private boolean showLeftShadow;
        private boolean showBottomShadow;
        private boolean showRightShadow;

        public DropShadowBorder() {
            this(UIManager.getColor("Control"), 1, 5);
        }

        public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize) {
            this(lineColor, lineWidth, shadowSize, .5f, 12, false, false, true, true);
        }

        public DropShadowBorder(Color lineColor, int lineWidth, boolean showLeftShadow) {
            this(lineColor, lineWidth, 5, .5f, 12, false, showLeftShadow, true, true);
        }

        public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize, float shadowOpacity, int cornerSize,
                boolean showTopShadow, boolean showLeftShadow, boolean showBottomShadow, boolean showRightShadow) {
            this.lineColor = lineColor;
            this.lineWidth = lineWidth;
            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<Position, BufferedImage> images = getImages(null);

            // compute the edges of the component -- not including the border
            Insets borderInsets = getBorderInsets(c);
            int leftEdge = x + borderInsets.left - lineWidth;
            int rightEdge = x + width - borderInsets.right;
            int topEdge = y + borderInsets.top - lineWidth;
            int bottomEdge = y + height - borderInsets.bottom;
            Graphics2D g2 = (Graphics2D) graphics;
            g2.setColor(lineColor);

            // 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.

            Point topLeftShadowPoint = null;
            if (showLeftShadow || showTopShadow) {
                topLeftShadowPoint = new Point();
                if (showLeftShadow && !showTopShadow) {
                    topLeftShadowPoint.setLocation(x, y + shadowSize);
                } 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 + shadowSize);
                } else if (showRightShadow && showTopShadow) {
                    topRightShadowPoint.setLocation(x + width - shadowSize, y);
                } else if (!showRightShadow && showTopShadow) {
                    topRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y);
                }
            }

            if (showLeftShadow) {
                Rectangle leftShadowRect = new Rectangle(x, (int) (topLeftShadowPoint.getY() + shadowSize), shadowSize,
                        (int) (bottomLeftShadowPoint.getY() - topLeftShadowPoint.getY() - shadowSize));
                g2.drawImage(images.get(Position.LEFT).getScaledInstance(leftShadowRect.width, leftShadowRect.height,
                        Image.SCALE_FAST), leftShadowRect.x, leftShadowRect.y, null);
            }

            if (showBottomShadow) {
                Rectangle bottomShadowRect = new Rectangle((int) (bottomLeftShadowPoint.getX() + shadowSize),
                        y + height - shadowSize,
                        (int) (bottomRightShadowPoint.getX() - bottomLeftShadowPoint.getX() - shadowSize), shadowSize);
                g2.drawImage(images.get(Position.BOTTOM).getScaledInstance(bottomShadowRect.width, bottomShadowRect.height,
                        Image.SCALE_FAST), bottomShadowRect.x, bottomShadowRect.y, null);
            }

            if (showRightShadow) {
                Rectangle rightShadowRect = new Rectangle(x + width - shadowSize,
                        (int) (topRightShadowPoint.getY() + shadowSize), shadowSize,
                        (int) (bottomRightShadowPoint.getY() - topRightShadowPoint.getY() - shadowSize));
                g2.drawImage(images.get(Position.RIGHT).getScaledInstance(rightShadowRect.width, rightShadowRect.height,
                        Image.SCALE_FAST), rightShadowRect.x, rightShadowRect.y, null);
            }

            if (showTopShadow) {
                Rectangle topShadowRect = new Rectangle((int) topLeftShadowPoint.getX() + shadowSize, y,
                        (int) (topRightShadowPoint.getX() - topLeftShadowPoint.getX() - shadowSize), shadowSize);
                g2.drawImage(images.get(Position.TOP).getScaledInstance(topShadowRect.width, topShadowRect.height,
                        Image.SCALE_FAST), topShadowRect.x, topShadowRect.y, null);
            }

            if (showLeftShadow || showTopShadow) {
                g2.drawImage(images.get(Position.TOP_LEFT), null, (int) topLeftShadowPoint.getX(),
                        (int) topLeftShadowPoint.getY());
            }
            if (showLeftShadow || showBottomShadow) {
                g2.drawImage(images.get(Position.BOTTOM_LEFT), null, (int) bottomLeftShadowPoint.getX(),
                        (int) bottomLeftShadowPoint.getY());
            }
            if (showRightShadow || showBottomShadow) {
                g2.drawImage(images.get(Position.BOTTOM_RIGHT), null, (int) bottomRightShadowPoint.getX(),
                        (int) bottomRightShadowPoint.getY());
            }
            if (showRightShadow || showTopShadow) {
                g2.drawImage(images.get(Position.TOP_RIGHT), null, (int) topRightShadowPoint.getX(),
                        (int) topRightShadowPoint.getY());
            }
        }

        private Map<Position, BufferedImage> 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
            Map<Position, BufferedImage> images = CACHE.get(shadowSize);
            if (images == null) {
                images = new HashMap<Position, BufferedImage>();

                /*
                 * Do 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 black, with an
                 * opacity of 50% 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 = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
                Graphics2D buffer = (Graphics2D) image.getGraphics();
                buffer.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                buffer.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                buffer.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                buffer.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
                buffer.setColor(new Color(0.0f, 0.0f, 0.0f, shadowOpacity));
                buffer.translate(shadowSize, shadowSize);
                buffer.fill(rect);
                float blurry = 1.0f / (shadowSize * shadowSize);
                float[] blurKernel = new float[shadowSize * shadowSize];
                for (int i = 0; i < blurKernel.length; i++) {
                    blurKernel[i] = blurry;
                }
                ConvolveOp blur = new ConvolveOp(new Kernel(shadowSize, shadowSize, blurKernel));
                BufferedImage targetImage = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
                ((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, targetImage.getSubimage(x, y, w, h));
                x = 1;
                y = h;
                w = shadowSize;
                h = 1;
                images.put(Position.LEFT, targetImage.getSubimage(x, y, w, h));
                x = 1;
                y = rectWidth;
                w = shadowSize;
                h = shadowSize;
                images.put(Position.BOTTOM_LEFT, targetImage.getSubimage(x, y, w, h));
                x = cornerSize + 1;
                y = rectWidth;
                w = 1;
                h = shadowSize;
                images.put(Position.BOTTOM, targetImage.getSubimage(x, y, w, h));
                x = rectWidth;
                y = x;
                w = shadowSize;
                h = shadowSize;
                images.put(Position.BOTTOM_RIGHT, targetImage.getSubimage(x, y, w, h));
                x = rectWidth;
                y = cornerSize + 1;
                w = shadowSize;
                h = 1;
                images.put(Position.RIGHT, targetImage.getSubimage(x, y, w, h));
                x = rectWidth;
                y = 1;
                w = shadowSize;
                h = shadowSize;
                images.put(Position.TOP_RIGHT, targetImage.getSubimage(x, y, w, h));
                x = shadowSize;
                y = 1;
                w = 1;
                h = shadowSize;
                images.put(Position.TOP, targetImage.getSubimage(x, y, w, h));

                buffer.dispose();
                image.flush();
            }
            return images;
        }

        /**
         * @inheritDoc
         */
        @Override
        public Insets getBorderInsets(Component c) {
            int top = showTopShadow ? lineWidth + shadowSize : lineWidth;
            int left = showLeftShadow ? lineWidth + shadowSize : lineWidth;
            int bottom = showBottomShadow ? lineWidth + shadowSize : lineWidth;
            int right = showRightShadow ? lineWidth + shadowSize : lineWidth;
            return new Insets(top - 1, left - 1, bottom - 1, right - 1);
        }

        /**
         * @inheritDoc
         */
        @Override
        public boolean isBorderOpaque() {
            return true;
        }

        public boolean isShowTopShadow() {
            return showTopShadow;
        }

        public boolean isShowLeftShadow() {
            return showLeftShadow;
        }

        public boolean isShowRightShadow() {
            return showRightShadow;
        }

        public boolean isShowBottomShadow() {
            return showBottomShadow;
        }

        public int getLineWidth() {
            return lineWidth;
        }

        public Color getLineColor() {
            return lineColor;
        }

        public int getShadowSize() {
            return shadowSize;
        }

        public float getShadowOpacity() {
            return shadowOpacity;
        }

        public int getCornerSize() {
            return cornerSize;
        }
    }
}
rcantrel
  • 177
  • 11
  • Show us your code. – Kalle Richter Jul 29 '17 at 23:25
  • I don't really have any relevant code examples. I am looking for any alternate solutions to MadProgrammer's accepted answer from the first link. Heavyweight components render transparent with his solution because of the known issue. – rcantrel Jul 29 '17 at 23:35
  • Your question would be more helpful if you'd explain what the linked issue is in detail and which lines of your code (if you don't have any, produce it) are causing which issue (expected vs. actual outcome). Otherwise your question is too broad. – Kalle Richter Jul 29 '17 at 23:39
  • *"Is there any other way to accomplish this? "* - Nope – MadProgrammer Jul 29 '17 at 23:55
  • @MadProgrammer Not even a custom look and feel? – rcantrel Jul 29 '17 at 23:58
  • Your issue has nothing to do with the look and feel and everything to do with how light and heavy weight components interact – MadProgrammer Jul 30 '17 at 00:02
  • My idea for a custom look and feel was to make a fake undecorated jframe that would use the system's drop shadow around it. – rcantrel Jul 30 '17 at 00:14
  • So, if a normal undecorated frame doesn't support the OS drop shadow, because it's removed the OS specific frame, how would a custom version do it? I'd consider maybe having a look at a `JWindow` - I think it's already undecorated - but you might find the drop shadow is an artifact of the OS's decorations – MadProgrammer Jul 30 '17 at 04:38
  • JWindow does not have setState/setExtendedState, otherwise that is exactly what I am looking for. I would rather have the OS's drop shadow rather than the custom dropshadow border I added. – rcantrel Jul 30 '17 at 14:06
  • @rcantrel "but my memory and cpu skyrocket and is unacceptable" - have you tried the accelerated lightweight rendering mode as described in the following article: https://jxbrowser.support.teamdev.com/support/solutions/articles/9000104965-accelerated-lightweight-rendering ? Lightweight mode should work for most cases except for heavy animation, etc. "Is there any other way to accomplish this? " - the known issue makes this impossible until it is completely fixed in Java without any restrictions. – Nikita Shvinagir Jul 31 '17 at 09:48

0 Answers0