JPanel Drop Shadow

2022-09-01 22:01:12

我有一个元素,我想添加一个投影到它,我怎么能添加一个漂亮的褪色的投影到元素?我是否需要使用外部库,或者是否有内置的、我可以使用的内容?JPanel

例:

enter image description here


答案 1

因此,我研究了扩展JPanel的swingx,并能够通过以下代码实现我正在寻找的结果:

public class Canvas extends JXPanel{

    public Canvas(){
        DropShadowBorder shadow = new DropShadowBorder();
        shadow.setShadowColor(Color.BLACK);
        shadow.setShowLeftShadow(true);
        shadow.setShowRightShadow(true);
        shadow.setShowBottomShadow(true);
        shadow.setShowTopShadow(true);
        this.setBorder(shadow);
    }
}

结果:

Dropshadow


答案 2

这是一个完整的黑客

这将要求您拥有用于模糊实现的 JH-Labs 滤镜副本

这是一个昂贵的操作,因为它使用模糊操作,我使用它的原因是将考虑它所阴影的组件的形状。

enter image description here

你遇到的主要问题是,边框不是它们本身,而是透明的,没有办法真正拥有不透明的组件和透明的边框。斩钉截铁

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<Component, DropShadowBorder.CachedBorder> BORDER_CACHE = new WeakHashMap<Component, CachedBorder>(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() && 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() && 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;

            }
        }
    }
}

使用其他示例进行了更新

投影边框有局限性,它不能考虑组件的形状,因为绘制边框的时间,组件还没有开始,所以我们没有参考点。

enter image description hereenter image description here

为了能够生成考虑组件形状的投影,我们需要创建自定义组件并将边框直接注入到绘画过程中。

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;

    }
}

推荐