BenGe89
BenGe89

Reputation: 141

Painting different non-rectangle polygons on semi-transparent JPanel causes ugly edges

I'm working on a custom menu for a game and a problem occured to me by trying to draw the components. The idea is, to have either a picture or a video running in the background with a panel on top of it containing some custom menu-items.

The panel which contains the menu-items shall be semi-transparent and thats the problem I guess.

Since it's a bit hard to explain I've build an example wich demonstrates the issue. Redrawing the menu-item on mouseover works without any problems but it appears that the redraw also kinda affects the parts of the panel which should not be drawn since the shape of the actual menu-item doesn't fit the panels rectangle shape. This results in my menu-item having ugly dark edges which should not be there (top-right and bottom-left).

Hope you guys know a solution for that cause I start getting really annoyed. I tried everything I found here or somewhere else in the web, but it appears all the other problems only had to deal with redrawing one shape which does not change.

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class TransparentDrawTest extends JPanel implements MouseListener {

 private final static Dimension SIZE = new Dimension(400, 50);

 private boolean isSelected;
 private boolean isClicked;

 public static void main(String[] args) {

    JFrame testFrame = new JFrame();
    testFrame.setSize(500, 100);
    testFrame.setResizable(false);
    testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    testFrame.setLocationRelativeTo(null);
    testFrame.setBackground(Color.WHITE);

    JPanel semiTransPanel = new JPanel();
    semiTransPanel.setBackground(new Color(0.0f, 0.0f, 0.0f, 0.6f));
    semiTransPanel.setOpaque(true);
    semiTransPanel.add(new TransparentDrawTest());

    testFrame.add(semiTransPanel);
    testFrame.setVisible(true);
}

public TransparentDrawTest() {

    this.addMouseListener(this);

    this.setSize(SIZE);
    this.setMinimumSize(SIZE);
    this.setMaximumSize(SIZE);
    this.setPreferredSize(SIZE);

    this.setOpaque(false);
}

@Override
public void paintComponent(Graphics g) {

    Graphics2D g2d = (Graphics2D) g;

    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
    g2d.fillRect(0, 0, 400, 50);

    int[] pointsX;
    int[] pointsY;

    if (this.isSelected) {
        pointsX = new int[] { 2, 348, 398, 48 };
        pointsY = new int[] { 2, 2, 48, 48 };
    }
    else {
        pointsX = new int[] { 2, 298, 348, 48 };
        pointsY = new int[] { 2, 2, 48, 48 };
    }

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setStroke(new BasicStroke(5));
    g2d.setColor(Color.WHITE);
    g2d.drawPolygon(pointsX, pointsY, 4);

    if (this.isClicked)
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f));
    else
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));

    g.fillPolygon(pointsX, pointsY, 4);
}

@Override
public void mouseClicked(MouseEvent e) {
}

@Override
public void mouseEntered(MouseEvent e) {
    this.isSelected = true;
    this.repaint();
}

@Override
public void mouseExited(MouseEvent e) {
    this.isSelected = false;
    this.repaint();
}

@Override
public void mousePressed(MouseEvent e) {
    this.isClicked = true;
    this.repaint();
}

@Override
public void mouseReleased(MouseEvent e) {
    this.isClicked = false;
    this.repaint();
}
}

Upvotes: 0

Views: 361

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347314

There are at least two things I can see which will give you trouble...

Firstly, you are changing the alpha composite of the Graphics context, but you are not resting it....

Graphics2D g2d = (Graphics2D) g;
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));

The Graphics context is a shared resource, all components being painted in the current paint cycle will be given the same instance of Graphics, meaning that any changes you make to it will be passed to the other components being painted.

Instead, you should create a temporary copy of the Graphics context and use that instead...

Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
//...
// If you create it, you must dispose of it...
g2d.dispose();

Second of all, you are setting the background color of a opaque component to a semi transparent value...

semiTransPanel.setBackground(new Color(0.0f, 0.0f, 0.0f, 0.6f));

The problem is, Swing only treats components and opaque or transparent. By doing this, Swing won't know that it should also update the components below this one. Basically it will think that the component is opaque (because it is) and will not update the components below...

Basically, you will need to do something simular to what you've done with TransparentDrawTest panel, and fake it, for example...

JPanel semiTransPanel = new JPanel() {
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); 
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f));
        g2d.setColor(getBackground());
        g2d.fillRect(0, 0, getWidth(), getHeight());
        g2d.dispose();
    }
};
semiTransPanel.setBackground(new Color(0.0f, 0.0f, 0.0f));
semiTransPanel.setOpaque(false);

I also can't figure out why you are doing this...

g2d.fillRect(0, 0, 400, 50);

In your TransparentDrawTest pane...

You should be calling super.paintComponent(g) first, this is actually what this method does anyway, it clears the Graphics context for painting...amongst other things ;)

Updated with example code...

This is the example code I've been using to test you code with...

Slide

Ps: I changed the background color of the semi transparent panel so I can see the difference...

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class TransparentDrawTest extends JPanel implements MouseListener {

    private final static Dimension SIZE = new Dimension(400, 50);

    private boolean isSelected;
    private boolean isClicked;

    public static void main(String[] args) {

        JFrame testFrame = new JFrame();
        testFrame.setSize(500, 100);
        testFrame.setResizable(false);
        testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        testFrame.setLocationRelativeTo(null);
        testFrame.setBackground(Color.WHITE);

        JPanel semiTransPanel = new JPanel() {

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g); 
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setColor(getBackground());
                g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f));
                g2d.fillRect(0, 0, getWidth(), getHeight());
                g2d.dispose();
            }

        };
        semiTransPanel.setOpaque(false);
        semiTransPanel.setBackground(Color.RED);
        semiTransPanel.add(new TransparentDrawTest());

        testFrame.add(semiTransPanel);
        testFrame.setVisible(true);
    }

    public TransparentDrawTest() {

        this.addMouseListener(this);

        this.setSize(SIZE);
        this.setMinimumSize(SIZE);
        this.setMaximumSize(SIZE);
        this.setPreferredSize(SIZE);

        this.setOpaque(false);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();

        int[] pointsX;
        int[] pointsY;

        if (this.isSelected) {
            pointsX = new int[]{2, 348, 398, 48};
            pointsY = new int[]{2, 2, 48, 48};
        } else {
            pointsX = new int[]{2, 298, 348, 48};
            pointsY = new int[]{2, 2, 48, 48};
        }

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(new BasicStroke(5));
        g2d.setColor(Color.WHITE);
        Composite comp = g2d.getComposite();

        if (this.isClicked) {
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f));
        } else {
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
        }

        g2d.fillPolygon(pointsX, pointsY, 4);
        g2d.setComposite(comp);
        g2d.drawPolygon(pointsX, pointsY, 4);
        g2d.dispose();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        this.isSelected = true;
        this.repaint();
    }

    @Override
    public void mouseExited(MouseEvent e) {
        this.isSelected = false;
        this.repaint();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        this.isClicked = true;
        this.repaint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        this.isClicked = false;
        this.repaint();
    }
}

Upvotes: 2

camickr
camickr

Reputation: 324157

See Backgrounds With Transparency for the probable problem and a simple solution:

//testFrame.add(semiTransPanel);
testFrame.add( new AlphaContainer(semiTransPanel) );

Upvotes: 0

Related Questions