1

I'm making an app and I've come to the conclusion that I want to make panels have both a box shadow and round corners, I've looked through a bunch of already answered questions but I've had no success.

I have:

            JPanel panel = new JPanel() {
                @Override
                protected void paintComponent(Graphics g) {
                    super.setBorder(new DropShadowBorder());
                    super.paintComponent(g);
                    Graphics2D graphics = (Graphics2D) g;
                    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                    graphics.setColor(Color.white);
                    graphics.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 10, 10);
                }
            };

which results in: https://i.stack.imgur.com/spK4E.png

and what I want is: https://i.stack.imgur.com/JE2io.png

Please help

camickr
  • 321,443
  • 19
  • 166
  • 288
Jake
  • 25
  • 7
  • 1
    Don't call `setBorder(...)` or do any other direct mutational changes on component state within a painting method. Consider creating your own Border class and setting a component's border with it. – Hovercraft Full Of Eels May 10 '23 at 00:21
  • *"and what I want is"* which seems to come from https://stackoverflow.com/questions/13368103/jpanel-drop-shadow/13369038#13369038 – MadProgrammer May 10 '23 at 00:56

1 Answers1

2

This is, actually, very complicated, at least if you want it to look "cool".

Before I get started, I wouldn't use Border for this, you can have a look at Is there a way to make JTextField for my address bar larger and curvier for some reasons why.

Normally, I make use of JH Labs image filters to produced a blurred shadow, for example, but for the following, I've removed it so you can run it without dependencies.

First, you're going to need some "support" functionality...

public class ImageUtilities {

    public static void applyQualityRenderingHints(Graphics2D g2d) {
        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);
    }

    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 createCompatibleImage(BufferedImage image) {
        return createCompatibleImage(image, image.getWidth(), image.getHeight());
    }

    public static BufferedImage createCompatibleImage(BufferedImage image,
            int width, int height) {
        return getGraphicsConfiguration().createCompatibleImage(width, height, image.getTransparency());
    }

    public static GraphicsConfiguration getGraphicsConfiguration() {
        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
    }

    public static BufferedImage generateMask(BufferedImage imgSource, Color color, float alpha) {
        int imgWidth = imgSource.getWidth();
        int imgHeight = imgSource.getHeight();

        BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight);
        Graphics2D g2 = imgBlur.createGraphics();
        applyQualityRenderingHints(g2);

        g2.drawImage(imgSource, 0, 0, null);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha));
        g2.setColor(color);

        g2.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight());
        g2.dispose();

        return imgBlur;
    }

    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 g2 = imgBlur.createGraphics();
        applyQualityRenderingHints(g2);

        g2.drawImage(imgSource, 0, 0, null);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha));
        g2.setColor(color);

        g2.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight());
        g2.dispose();

        //imgBlur = filter.filter(imgBlur, null);

        return imgBlur;
    }

    public static BufferedImage applyShadow(Shape shape, int size, Color backgroundColor, Color shadowColor, float alpha) {
        BufferedImage sourceImage = createCompatibleImage(shape.getBounds().width, shape.getBounds().height);
        Graphics2D g2d = sourceImage.createGraphics();
        applyQualityRenderingHints(g2d);
        g2d.translate(-shape.getBounds().getX(), -shape.getBounds().getY());
        g2d.setColor(backgroundColor);
        g2d.fill(shape);
        g2d.dispose();
        return applyShadow(sourceImage, size, shadowColor, alpha);
    }
    
    public static BufferedImage applyShadow(BufferedImage imgSource, int size, Color color, float alpha) {
        BufferedImage result = createCompatibleImage(imgSource, imgSource.getWidth() + (size * 2), imgSource.getHeight() + (size * 2));
        Graphics2D g2d = result.createGraphics();
        g2d.drawImage(generateShadow(imgSource, size, color, alpha), size, size, null);
        g2d.drawImage(imgSource, 0, 0, null);
        g2d.dispose();

        return result;
    }

    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();
        applyQualityRenderingHints(g2);

        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 * 2), color, alpha);
        return imgGlow;
    }

    public static Image applyMask(BufferedImage sourceImage, BufferedImage maskImage) {
        return applyMask(sourceImage, maskImage, AlphaComposite.DST_IN);
    }

    public static BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage, int method) {
        BufferedImage maskedImage = null;
        if (sourceImage != null) {

            int width = maskImage.getWidth(null);
            int height = maskImage.getHeight(null);

            maskedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D mg = maskedImage.createGraphics();

            int x = (width - sourceImage.getWidth(null)) / 2;
            int y = (height - sourceImage.getHeight(null)) / 2;

            mg.drawImage(sourceImage, x, y, null);
            mg.setComposite(AlphaComposite.getInstance(method));

            mg.drawImage(maskImage, 0, 0, null);

            mg.dispose();
        }
        return maskedImage;
    }
}

This is a lot of functionality which does a relatively simple job - it produces a drop shadow from a supplied image or shape. It does this through a process of image masking.

Let's run a quick test...

public class TestPane extends JPanel {

    public TestPane() {
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        
        int size = Math.min(getWidth(), getHeight()) / 2;
        
        RoundRectangle2D border = new RoundRectangle2D.Double(0, 0, size, size, 20, 20);
        ImageUtilities.applyQualityRenderingHints(g2d);
        g2d.drawImage(ImageUtilities.applyShadow(border, 2, getBackground(), Color.BLACK, 0.25f), 5, 5, this);
        g2d.setColor(Color.DARK_GRAY);
        g2d.translate(5, 5);
        g2d.draw(border);
        g2d.dispose();
    }

}

Now, this will produce...

enter image description here

Alright, job done!

Ok, not entirely. What we really need is a "composite" component. That is one, which will contain and manage the rounded border and drop shadow, but also allow use to add components to it, which won't "over flow" the border, maybe something like...

public class RoundedShadowPane extends JPanel {

    private double cornerRadius = 20;
    private int shadowSize = 5;
    private Color shadowColor = Color.BLACK;
    private float shadowAlpha = 0.25f;

    private JComponent contentPane;

    public RoundedShadowPane() {
        setOpaque(false);
        int shadowSize = getShadowSize();
        int cornerRadius = (int) getCornerRadius() / 4;
        setBorder(new EmptyBorder(
                shadowSize + cornerRadius, 
                shadowSize + cornerRadius, 
                shadowSize + cornerRadius + shadowSize, 
                shadowSize + cornerRadius + shadowSize
        ));
        setLayout(new BorderLayout());
        add(getContentPane());
    }

    public JComponent getContentPane() {
        if (contentPane == null) {
            contentPane = new JPanel();
        }
        return contentPane;
    }

    public void setContentPane(JComponent contentPane) {
        if (this.contentPane != null) {
            remove(this.contentPane);
        }
        this.contentPane = contentPane;
        this.setBackground(this.contentPane.getBackground());
        add(this.contentPane);
    }

    public double getCornerRadius() {
        return cornerRadius;
    }

    public int getShadowSize() {
        return shadowSize;
    }

    public Color getShadowColor() {
        return shadowColor;
    }

    public float getShadowAlpha() {
        return shadowAlpha;
    }
   
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setColor(Color.MAGENTA);
        g2d.fillRect(0, 0, getWidth(), getHeight());

        double cornerRadius = getCornerRadius();
        double cornerInsets = cornerRadius / 4d;

        int width = getWidth();
        int height = getHeight();
        Insets insets = getInsets();
        insets.left -= cornerInsets;
        insets.right -= cornerInsets;
        insets.top -= cornerInsets;
        insets.bottom -= cornerInsets;

        width -= insets.left + insets.right;
        height -= insets.top + insets.bottom;

        RoundRectangle2D border = new RoundRectangle2D.Double(0, 0, width, height, cornerRadius, cornerRadius);
        ImageUtilities.applyQualityRenderingHints(g2d);
        g2d.drawImage(
                ImageUtilities.applyShadow(
                        border, getShadowSize(), 
                        getBackground(), 
                        getShadowColor(), 
                        getShadowAlpha()
                ), 
                insets.left, 
                insets.top, 
                this
        );
        g2d.setColor(Color.DARK_GRAY);
        g2d.translate(insets.left, insets.top);
        g2d.draw(border);
        g2d.dispose();
    }

}  

This is a little complicated and it's important to note the their is a seperate contentPane onto which all you other components should be added.

For example...

enter image description here

public class TestPane extends JPanel {
    public TestPane() {
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        
        RoundedShadowPane shadowPane = new RoundedShadowPane();
        shadowPane.getContentPane().add(new JLabel("Everything is normal here"));
        
        gbc.gridx = 0;
        gbc.gridy = 0;
        add(shadowPane, gbc);
        
        shadowPane = new RoundedShadowPane();
        JPanel contentPane = new JPanel();
        contentPane.setBackground(Color.GREEN);
        shadowPane.setContentPane(contentPane);
        shadowPane.getContentPane().add(new JLabel("Envy with green"));
        
        gbc.gridx = 1;
        add(shadowPane, gbc);

        shadowPane = new RoundedShadowPane();
        contentPane = new JPanel();
        contentPane.setBackground(Color.BLUE);
        shadowPane.setContentPane(contentPane);
        shadowPane.getContentPane().add(new JLabel("Why so blue?"));
        
        gbc.gridy = 1;
        gbc.gridx = 0;
        add(shadowPane, gbc);
        
        shadowPane = new RoundedShadowPane();
        contentPane = new JPanel();
        contentPane.setBackground(Color.RED);
        shadowPane.setContentPane(contentPane);
        shadowPane.getContentPane().add(new JLabel("Roses are red"));
        
        gbc.gridx = 1;
        add(shadowPane, gbc);
    }
}

Now, a clever person should realise that you could actually paint the RoundedRectangle twice, once for the shadow and once for the fill and this would be an entirely valid approach, except, I'd include blurring the shadow, because it "looks" nicer (IMHO)

Is there any way that the drop shadow could be blurred?

As stated, the answer is based on JPanel Drop Shadow which makes use of JH Labs image filters to produced the blurring.

If you want to re-enable the blurring, then you will need to download the Filters.jar from JH Labs and link it to your project (no going to describe how to do that).

Once you've done that, modify the ImageUtilities class's generateBlur method to reinstate

GaussianFilter filter = new GaussianFilter(size);

and

imgBlur = filter.filter(imgBlur, null);

lines and blurring will once again be implemented.

enter image description here

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • This is very close to what I want, is there any way that the drop shadow could be blurred? The second picture doesn't really show that but i want the shadow to be like in the first picture, sorry if I wasn't clear. – Jake May 10 '23 at 17:32
  • So, the solution is based on https://stackoverflow.com/questions/13368103/jpanel-drop-shadow/13369038#13369038 and in the ImageUtilities has a couple of lines commented out which are directly related to adding a blur – MadProgrammer May 10 '23 at 21:55
  • I've done some testing with what's in the original post for this exact question, works perfectly fine, its stupidly easy, thanks a lot! – Jake May 12 '23 at 09:10