5

I've just recently extended JPanel for use in a project which we want to appear to be more "3D". That's my bosses' way of requiring shadowing and rounded corners on components. That's been accomplished as shown on many online examples. I did it like this:

public class RoundedPanel extends JPanel
{
    protected int _strokeSize = 1;
    protected Color _shadowColor = Color.BLACK;
    protected boolean _shadowed = true;
    protected boolean _highQuality = true;
    protected Dimension _arcs = new Dimension(30, 30);
    protected int _shadowGap = 5;
    protected int _shadowOffset = 4;
    protected int _shadowAlpha = 150;

    protected Color _backgroundColor = Color.LIGHT_GRAY;

    public RoundedPanel()
    {
        super();
        setOpaque(false);
    }

    @Override
    public void setBackground(Color c)
    {
        _backgroundColor = c;
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        int width = getWidth();
        int height = getHeight();
        int shadowGap = this._shadowGap;
        Color shadowColorA = new Color(_shadowColor.getRed(), _shadowColor.getGreen(), _shadowColor.getBlue(), _shadowAlpha);
        Graphics2D graphics = (Graphics2D) g;

        if(_highQuality)
        {
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        }

        if(_shadowed)
        {
            graphics.setColor(shadowColorA);
            graphics.fillRoundRect(_shadowOffset, _shadowOffset, width - _strokeSize - _shadowOffset,
                    height - _strokeSize - _shadowOffset, _arcs.width, _arcs.height);
        }
        else
        {
            _shadowGap = 1;
        }

        graphics.setColor(_backgroundColor);
        graphics.fillRoundRect(0,  0, width - shadowGap, height - shadowGap, _arcs.width, _arcs.height);
        graphics.setStroke(new BasicStroke(_strokeSize));
        graphics.setColor(getForeground());
        graphics.drawRoundRect(0,  0, width - shadowGap, height - shadowGap, _arcs.width, _arcs.height);
        graphics.setStroke(new BasicStroke());
    }
}

I am creating a test frame with the following code:

public class UITest
{
    private static JFrame mainFrame;
    private static ImagePanel mainPanel;

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                mainFrame = new JFrame();
                mainFrame.setVisible(true);

                try
                {
                    mainPanel = new ImagePanel(ImageIO.read(this.getClass().getResource("/content/diamondPlate_Light.jpg")));
                    //mainPanel.setBounds(0, 0, 800, 600);
                }
                catch(IOException e)
                {

                }
                mainPanel.setLayout(null);

                RoundedPanel rPanel = new RoundedPanel();
                rPanel.setBounds(10, 10, 200, 200);
                rPanel.setBackground(new Color(168, 181, 224));

                mainPanel.add(rPanel);

                rPanel = new RoundedPanel();
                rPanel.setBounds(220, 10, 560, 200);
                rPanel.setBackground(new Color(168, 224, 168));

                mainPanel.add(rPanel);

                rPanel = new RoundedPanel();
                rPanel.setBounds(10, 220, 770, 300);
                rPanel.setBackground(new Color(224, 168, 168));

                mainPanel.add(rPanel);

                mainFrame.setSize(800, 600);
                mainFrame.getContentPane().add(mainPanel);
            }
        });
    }
}

And it results in this (sans the background image of the JFrame's contentPane:

Panels

What I would really like to do is generate the red, green, and blue panels with the rounded corners, but filled by a different image instead of the Color. I still want the properly rounded corners, but I'm unsure of how to do this.

If I've got a large texture, can I simply "clip" a piece of it out in the size and shape of the RoundedPanel? I need to evaluate this, since it just occurred to me as I typed, but if I can create a piece of geometry like what is used in graphics.fillRoundRect(...) and then clip the image, this could work.

Are there any other ways of doing this that I'm missing? I'd appreciate any feedback you might be able to offer. Thanks.

Edit:

Based upon the idea in the selected solution below, I've got the following results:

Panels

It needs to be whipped into shape for production and the background images are poorly chosen, but as a demo, the following RoundedPanel code gets us to the above results:

public class RoundedPanel extends JPanel
{
    protected int strokeSize = 1;
    protected Color _shadowColor = Color.BLACK;
    protected boolean shadowed = true;
    protected boolean _highQuality = true;
    protected Dimension _arcs = new Dimension(30, 30);
    protected int _shadowGap = 5;
    protected int _shadowOffset = 4;
    protected int _shadowAlpha = 150;

    protected Color _backgroundColor = Color.LIGHT_GRAY;
    protected BufferedImage image = null;

    public RoundedPanel(BufferedImage img)
    {
        super();
        setOpaque(false);

        if(img != null)
        {
            image = img;
        }
    }

    @Override
    public void setBackground(Color c)
    {
        _backgroundColor = c;
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        int width = getWidth();
        int height = getHeight();
        int shadowGap = this._shadowGap;
        Color shadowColorA = new Color(_shadowColor.getRed(), _shadowColor.getGreen(), _shadowColor.getBlue(), _shadowAlpha);
        Graphics2D graphics = (Graphics2D) g;

        if(_highQuality)
        {
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        }

        if(shadowed)
        {
            graphics.setColor(shadowColorA);
            graphics.fillRoundRect(_shadowOffset, _shadowOffset, width - strokeSize - _shadowOffset,
                    height - strokeSize - _shadowOffset, _arcs.width, _arcs.height);
        }
        else
        {
            _shadowGap = 1;
        }

        RoundRectangle2D.Float rr = new RoundRectangle2D.Float(0, 0, (width - shadowGap), (height - shadowGap), _arcs.width, _arcs.height);

        Shape clipShape = graphics.getClip();

        if(image == null)
        {
            graphics.setColor(_backgroundColor);
            graphics.fill(rr);
        }
        else
        {
            RoundRectangle2D.Float rr2 =  new RoundRectangle2D.Float(0, 0, (width - strokeSize - shadowGap), (height - strokeSize - shadowGap), _arcs.width, _arcs.height);

            graphics.setClip(rr2);
            graphics.drawImage(this.image, 0, 0, null);
            graphics.setClip(clipShape);
        }

        graphics.setColor(getForeground());
        graphics.setStroke(new BasicStroke(strokeSize));
        graphics.draw(rr);
        graphics.setStroke(new BasicStroke());
    }
}

Thanks for the help.

Rich Hoffman
  • 742
  • 1
  • 7
  • 23
  • 1
    [maybe](http://stackoverflow.com/questions/8416295/component-painting-outside-custom-border) or [maybe](http://stackoverflow.com/questions/9382426/how-to-remove-stretch-marks-on-custom-button-border) – mKorbel Jul 02 '12 at 21:02
  • @mKorbel: The first link is pretty good. I was searching on the wrong terminology, apparently. Thanks. – Rich Hoffman Jul 02 '12 at 21:51

1 Answers1

3

Try "clipping area" (see the g.setClip() call):

public static void main(String[] args) {
    JFrame f = new JFrame();
    f.setSize(new Dimension(600, 400));
    f.getContentPane().setLayout(null);
    RoundPanel rp = new RoundPanel();
    rp.setBounds(100, 50, 400, 300);
    f.getContentPane().add(rp);
    f.setVisible(true);
}

static class RoundPanel extends JPanel {
    @Override
    protected void paintComponent(Graphics g) {
        // Prepare a red rectangle
        BufferedImage bi = new BufferedImage(400, 300, BufferedImage.TYPE_INT_ARGB);
        Graphics2D gb = bi.createGraphics();
        gb.setPaint(Color.RED);
        gb.fillRect(0, 0, 400, 300);
        gb.dispose();

        // Set a rounded clipping region:
        RoundRectangle2D r = new RoundRectangle2D.Float(0, 0, 400, 300, 20, 20);
        g.setClip(r);

        // Draw the rectangle (and see whether it has round corners)
        g.drawImage(bi, 0, 0, null);
    }
}

Beware of the restrictions mentioned in the API doc for Graphics.setClip:

Sets the current clipping area to an arbitrary clip shape. Not all objects that implement the Shape interface can be used to set the clip. The only Shape objects that are guaranteed to be supported are Shape objects that are obtained via the getClip method and via Rectangle objects.

Rainer Schwarze
  • 4,725
  • 1
  • 27
  • 49