javaswingjbuttonsetbackground

Java ImageIcon on JButton duplicates image


The goal of this post is to figure out why it is duplicating both images on both buttons. It is VERY odd and should not be happening. That is the main goal. Then it would be finding a solution. Thank you!

Image of what it looks like

enter image description here

I've made an MRE

It outputs both images on both buttons and I don't know why.

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class GameManager extends JFrame{

    private final   Map <String, String> images = new HashMap<>(2);

    GameManager()
    {
        images.put("Articuno", "https://i.ya-webdesign.com/images/articuno-transparent-pokemon-xy-17.gif");
        images.put("Rayquaza", "https://play.pokemonshowdown.com/sprites/ani-back-shiny/rayquaza.gif");

        JPanel pnlPokemonInParty = new JPanel(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        //Why does it put both images on both buttons? It actually sets the images on top of one another.
        //You can tell which image is in the front and which is behind the other.
        //I'm setting the buttons to be transparent. Setting the buttons to not be transparent will cover the the image below it,
        //that's how I know they're being stacked on top of one another.
        JButton btn1 = gifBtn("Articuno");
        JButton btn2 = gifBtn("Rayquaza");
        c.gridx = 0;
        pnlPokemonInParty.add(btn1, c);
        c.gridx = 1;
        pnlPokemonInParty.add(btn2, c);
        this.add(pnlPokemonInParty);
        this.pack();
        this.setVisible(true);
    }
    public JButton gifBtn(String name)
    {
        final JButton btn = new JButton();
        URL url = null;
        try {
            url = new URL(images.get(name));
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        }
        Icon icon = new ImageIcon(url);
        btn.setIcon(icon);
        btn.setBackground(new Color(50,50,50,0));
        return btn;
    }

    public static void main(String[] args)
    {
        GameManager gameManager = new GameManager();
    }
}

I can hide the problem by not setting the background color of the Jbuttons to be transparent but that doesn't solve the problem.

Why does this happen? I'm more so worried about the two images being on the same JButton, but there is another issue that is easily noticeable when looking at the image that I don't really know how to explain...I think on each repaint cycle it is drawing the current frame of the gif onto the same exact image for every frame of the gif, so you see the current frame of the gif and all of the previous drawn frames. If a new BufferedImage were created on each repaint cycle and the current frame were drawn to that, I think it would resolve the problem.


Solution

  • When posting a question it is recommended to post an MRE like the following:

    import java.awt.Color;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.HashMap;
    import java.util.Map;
    import javax.swing.Icon;
    import javax.swing.ImageIcon;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    public class GameManager extends JFrame{
    
        private final   Map <String, String> images = new HashMap<>(2);
    
            GameManager()
            {
                images.put("Articuno", "https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/256x256/Box_Green.png");
                images.put("Rayquaza", "https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/256x256/Box_Red.png");
    
                JPanel pnlPokemonInParty = new JPanel(new GridBagLayout());
                    GridBagConstraints c = new GridBagConstraints();
                    JButton btn1 = gifBtn("Articuno");
                    JButton btn2 = gifBtn("Rayquaza");
                    c.gridx = 0;
                    pnlPokemonInParty.add(btn1, c);
                    c.gridx = 1;
                    pnlPokemonInParty.add(btn2, c);
                    this.add(pnlPokemonInParty);
                    this.pack();
                    this.setVisible(true);
            }
             public JButton gifBtn(String name)
             {
                    final JButton btn = new JButton();
                    URL url = null;
                    try {
                        url = new URL(images.get(name));
                    } catch (MalformedURLException ex) {
                        ex.printStackTrace();
                    }
                    Icon icon = new ImageIcon(url);
                    btn.setIcon(icon);
                    btn.setBackground(new Color(50,50,50,0));
                    return btn;
             }
    
        public static void main(String[] args)
        {
                GameManager gameManager = new GameManager();
        }
     }
    

    The code works fine using publicly available images so it suggests that there is a problem with the local resource.
    MRE makes helping much easier and it is a powerful debugging tool. It many case, while preparing one, you are likely to find the problem.


    Edit 1: With the newly add mre the problem is clear now: each button shows the two images one on top of the other.
    The problem indeed disappears when removing btn.setBackground(new Color(50,50,50,0));
    This may be explained by "setBackground() doesn't read well on some platforms" taken from @trashgod answer.
    The problem can be eliminated by setting LAF as explained in this answer by @Andrew Thompsom.
    Here is an mre demonstrating it.

    It is up to the look and feel to honor this property, some may choose to ignore it.

    (Quoted from JComponent#setBackground(Color) documentation.)

    Edit 2:

    A custom JButton which overrides paintComponent works properly (with transparent color where alfa is to 0 like new Color(50,50,50,0) or any other color):

    class Main extends JFrame{
    
        private final Map <String, String> images = new HashMap<>();
    
        Main()
        {
            images.put("Articuno", "https://66.media.tumblr.com/d9105814c15295196a3dbe75c32ba1a0/tumblr_oagpklvBGf1scncwdo1_400.gif");
            images.put("Rayquaza", "https://play.pokemonshowdown.com/sprites/ani-back-shiny/rayquaza.gif");
            images.put("GreenCircle", "https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/128x128/Circle_Green.png");
            images.put("RedBox", "https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/256x256/Box_Red.png");
    
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            getContentPane().setBackground(Color.WHITE);
            this.setLayout(new FlowLayout());
            this.add(gifBtn("GreenCircle"));
            this.add(gifBtn("RedBox"));
            this.add(gifBtn("Articuno"));
            this.add(gifBtn("Rayquaza"));
            this.pack();
            this.setVisible(true);
        }
    
        public JButton gifBtn(String name)
        {
           JButton btn = new CustomButton();
            try {
                URL url = new URL(images.get(name));
                btn.setIcon(new ImageIcon(url));
            } catch (MalformedURLException ex) { ex.printStackTrace();   }
    
            return btn;
        }
    
        public static void main(String[] args) throws Exception
        {
            new Main();
        }
    }
    
    class CustomButton extends JButton{
    
        private final Color bgColor = new Color(255,192,203,0);
    
        public CustomButton() {
            //setBorderPainted(false);  //optioal
            setContentAreaFilled(false);
            setOpaque(false);
        }
    
        @Override
        public void paintComponent(Graphics g){
            g.setColor(bgColor);
            Rectangle r = g.getClipBounds();
            g.fillRect(r.x, r.y, r.width, r.height);
            super.paintComponent(g);
        }
    }
    

    JComponent#setBackground(Color) documentation states:

    Direct subclasses of JComponent must override paintComponent to honor this property. It is up to the look and feel to honor this property, some may choose to ignore it.

    from some reason JButton does not.