0

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();
}
}
BenGe89
  • 141
  • 1
  • 7
  • Swing doesn't know how to deal with translucent colors on an opaque surface. Either a component is opaque or transparent, so using something like `semiTransPanel.setBackground(new Color(0.0f, 0.0f, 0.0f, 0.6f));` is ill advised. If you want this effect, you will need to do the same thing as you've done for `TransparentDrawTest` panel, fake it... – MadProgrammer Nov 27 '13 at 20:39

2 Answers2

2

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();
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks for your answer, but it does not really help me. When I try your suggestions the result doesn't change or is even worse. The fillRect was meant to clear the panel again. If I don't call it, I can see both polygons. But I guess I know what your thinking, painting something transparent on something else shouldn't clear the panel, but it does. I don't know why tbh, I'm pretty new to this whole paintComponent stuff ... You're right with the setOpaque for my semiTransPanel, I don't need it as well as I don't need it in TransparentDrawTest. Thanks for the explanation! – BenGe89 Nov 28 '13 at 15:42
  • *"The fillRect was meant to clear the panel again. If I don't call it, I can see both polygons. But I guess I know what your thinking, painting something transparent on something else shouldn't clear the panel,"* - I missed it yesterday, but you should be calling `super.paintComponent`, this is one of the jobs that this method does, clears the `Graphics` context – MadProgrammer Nov 28 '13 at 19:04
  • You have saved my day! Thank you very much. I've already tried calling super.paintComponent() but it did not work. Seems like I did something wrong :) I'd like to upvote your answer but I can't since I do not have enough reputation, hope someone else does ... – BenGe89 Nov 28 '13 at 20:48
  • If it helped solve you problem, you could accept the answer (or the answer that helped you the most) by clicking the little ghost of a tick at the top of the answers ;) – MadProgrammer Nov 28 '13 at 21:33
0

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

//testFrame.add(semiTransPanel);
testFrame.add( new AlphaContainer(semiTransPanel) );
camickr
  • 321,443
  • 19
  • 166
  • 288