Search code examples
javaswinggraphicsawttransparency

Java Swing Graphical Glitches Dealing with Transparency and Images


So I have this login form and I have a "user photo." I'm trying to make it so that when you hover the mouse over the photo area, a transparent label with a colored background will appear (to give the effect of "selecting the photo"). It looks like this:

image1

And once you move your mouse off it, it goes back to being "deselected."

Now my problem is, if you hover your mouse over the login button first then move your mouse over the photo, a "ghost login button" appears. It looks like this:

image2

I don't know why this is happening. Can someone help? Here is the relevant code:

package com.stats;

public class Stats extends JFrame implements Serializable {

    private JLabel fader;

    public Stats() {

    try {
        Image image = ImageIO.read(new File(System.getenv("APPDATA")
                                   + "\\Stats\\Renekton_Cleave.png"));
        JLabel labelUserPhoto = new JLabel(new ImageIcon(image));
        fader = new JLabel();
        fader.setBounds(97, 44, 100, 100);
        fader.setOpaque(true);
        fader.setBackground(new Color(0, 0, 0, 0));
        labelUserPhoto.setBounds(97, 44, 100, 100);
        PicHandler ph = new PicHandler();
        contentPane.add(fader);
        contentPane.add(labelUserPhoto);
        fader.addMouseMotionListener(ph);
    } catch(Exception e) {
        e.printStackTrace();
    }
}

private class PicHandler implements MouseMotionListener {
    public void mouseDragged(MouseEvent e) { }
    public void mouseMoved(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();

        System.out.println("x: " + x + ", y: " + y);

        if ((x > 16 && x < 80) && (y > 16 && y < 80)) {
            if (!fader.isOpaque()) {
                fader.setOpaque(true);
                fader.setBackground(new Color(0, 0, 0, 40));
                fader.repaint();
            }
        } else {
            if (fader.isOpaque()) {
                fader.setOpaque(false);
                fader.repaint();
            }
        }
    }
}

Solution

  • I can see a number of issues with your example, but the most significant is the use of a color with an alpha value.

    fader.setBackground(new Color(0, 0, 0, 40));
    

    Swing doesn't render components with alpha based colors well (within this context). By making component opaque and then setting the background color to use an alpha value, you are telling Swing that it doesn't need to worry about painting what's underneath your component, which isn't true...

    The Graphics context is also a shared resource, meaning that anything that was painted before your component is still "painted", you need to clear the Graphics context before painting.

    enter image description hereenter image description here

    This example uses a rather nasty trick to get it's work done. Because all the painting occurs within the UI delegate, if we were simply to allow the default paint chain to continue, we wouldn't be able to render underneath the icon. Instead, we take over control of the "dirty" details and paint the background on the behalf the parent.

    This would be simpler to achieve if we simple extended from something like JPanel and painted the image ourselves

    import java.awt.AlphaComposite;
    import java.awt.Color;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagLayout;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import javax.swing.Icon;
    import javax.swing.ImageIcon;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class FadingIcon {
    
      public static void main(String[] args) {
        new FadingIcon();
      }
    
      public FadingIcon() {
        startUI();
      }
    
      public void startUI() {
        EventQueue.invokeLater(new Runnable() {
          @Override
          public void run() {
            try {
              UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
              ex.printStackTrace();
            }
    
            BufferedImage img = null;
            try {
              img = ImageIO.read(new File("C:\\Users\\swhitehead\\Documents\\My Dropbox\\Ponies\\SmallPony.png"));
            } catch (IOException ex) {
              ex.printStackTrace();
            }
    
            JFrame frame = new JFrame("Testing");
            frame.setLayout(new GridBagLayout());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new FadingLabel(new ImageIcon(img)));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
          }
        });
      }
    
      public class FadingLabel extends JLabel {
    
        private boolean mouseIn = false;
        private MouseHandler mouseHandler;
    
        public FadingLabel(Icon icon) {
          super(icon);
          setBackground(Color.RED);
          super.setOpaque(false)(
        }
    
        @Override
        public void setOpaque(boolean opaque) {
        }
    
        @Override
        public final boolean isOpaque() {
            return false;
        }
    
        protected MouseHandler getMouseHandler() {
          if (mouseHandler == null) {
            mouseHandler = new MouseHandler();
          }
          return mouseHandler;
        }
    
        @Override
        public void addNotify() {
          super.addNotify();
          addMouseListener(getMouseHandler());
        }
    
        @Override
        public void removeNotify() {
          removeMouseListener(getMouseHandler());
          super.removeNotify();
        }
    
        @Override
        protected void paintComponent(Graphics g) {
          if (mouseIn) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
            g2d.setColor(getBackground());
            g2d.fillRect(0, 0, getWidth(), getHeight());
            g2d.dispose();
          }
          getUI().paint(g, this);
        }
    
        public class MouseHandler extends MouseAdapter {
    
          @Override
          public void mouseEntered(MouseEvent e) {
            mouseIn = true;
            repaint();
          }
    
          @Override
          public void mouseExited(MouseEvent e) {
            mouseIn = false;
            repaint();
          }
    
        }
    
      }
    
    }
    

    I would also recommend that you take the time to learn how to use appropriate layout managers, they will save you a lot of hair pulling later

    Check out A Visual Guide to Layout Managers and Laying Out Components Within a Container