2

I found 2 different solutions that don't work for me. The first one deals with transparency. The second one I would prefer because it's cleaner looking. The goal is to create custom shaped buttons using images that have transparency. I want to ignore the transparent sections and only take action when I click on the non-transparent part of the image.

First code comes from here http://java.macteki.com/2012/07/custom-shape-buttons.html. By the way, what is the point of 0x00ffffff in the code below?

//********** BEGIN OF IMAGE BUTTON CODE ****************
      // The following is the actual code to create a image button
      // you may use any transparent image file, just change "a.png" below
      final BufferedImage image = ImageIO.read(new File("a.png"));
      ImageIcon icon = new ImageIcon(image);

      jLabel1 = new javax.swing.JLabel(icon);
      jLabel1.addMouseListener(new MouseAdapter()
      {
      public void mouseClicked(MouseEvent e)
      {
      boolean transparent = (image.getRGB(e.getX(),e.getY()) & 0x00ffffff)!=0;
     if (!transparent) {
      // write your button handler here
       System.out.println("button pressed");
      } else {
        System.out.println("else");
      }
      }
      }

      );
      //********** END OF IMAGE BUTTON CODE ****************
      // add the button to the panel so that it becomes visible

It works with his test image, but does not work with any of mine. I even took his and just changed the color of the A in gimp, exported to a2.png and it does not work. It registers all areas as else and if (!transparent) never triggers.

This image works: This image works This image does not work: This image does not work

The second code that I would prefer comes from here Creating a custom button in Java with JButton. The image applies to the button, but a click anywhere in the box, including the transparent areas will register as a click. I want it to only work when you click on the non-transparent areas (or the A in this example).

BufferedImage startButton = ImageIO.read(new File("a2.png"));       
        jButton1 = new javax.swing.JButton(new ImageIcon(startButton));  
        jButton1.setBorder(BorderFactory.createEmptyBorder());
        jButton1.setContentAreaFilled(false);
        jButton1.setFocusable(false);

Here is the code I am trying to use based on the example from Guillame

private void initComponents() throws IOException {

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        final BufferedImage image = ImageIO.read(getClass().getResource("/images/a.png"));
        jButton1 = new JButton(new ImageIcon(image)) {
             @Override
             public boolean contains(int x, int y) {
                 Rectangle viewRect = getBounds();
                 Insets insets = getInsets();
                 viewRect.x += insets.left;
                 viewRect.y += insets.top;
                 viewRect.width -= insets.left + insets.right;
                 viewRect.height -= insets.top + insets.bottom;
                 Rectangle iconR = new Rectangle();
                 SwingUtilities.layoutCompoundLabel(this, this.getFontMetrics(this.getFont()), this.getText(), this.getIcon(),
                         this.getVerticalAlignment(), this.getHorizontalAlignment(), this.getVerticalTextPosition(),
                         this.getHorizontalTextPosition(), viewRect, iconR, new Rectangle(), this.getIconTextGap());
                 if (!iconR.contains(x, y)) {
                     return false;
                 }
                 x -= iconR.x;
                 y -= iconR.y;
                 Color c = new Color(image.getRGB(x, y), true);
                 return c.getAlpha() != 0 && (c.getRed() < 255 || c.getGreen() < 255 || c.getBlue() < 255);
             }
         };

        jButton1.setContentAreaFilled(false); 
        jButton1.setFocusPainted(false);            
        jButton1.setRolloverEnabled(false);       
        jButton1.setBorderPainted(false);

        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(72, 72, 72)
                .addComponent(jButton1)
                .addContainerGap(255, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(193, Short.MAX_VALUE)
                .addComponent(jButton1)
                .addGap(84, 84, 84))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents
Community
  • 1
  • 1
user1753429
  • 89
  • 1
  • 7
  • possible duplicate of [Event detection on opaque pixels in JButton](http://stackoverflow.com/questions/13825123/event-detection-on-opaque-pixels-in-jbutton) – David Kroukamp Jan 16 '13 at 17:36

1 Answers1

4

You need to override the method contains(int,int) of JButton for your second method to work. Now the hard part is to figure out if you are on a relevant part of the image or not. In this case I made the assumption that fully transparent pixels are not relevant and that white pixels are not relevant either. Up to you to choose a different implementation:

import java.awt.Color;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

public class TestTransparentButton {

    protected void createAndShowGUI() throws MalformedURLException, IOException {
        JFrame frame = new JFrame("Test button");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final BufferedImage image = ImageIO.read(new URL("https://i.stack.imgur.com/7D547.png"));
        final JButton button = new JButton(new ImageIcon(image)) {
            @Override
            public boolean contains(int x, int y) {
                Rectangle viewRect = getBounds();
                Insets insets = getInsets();
                viewRect.x = insets.left;
                viewRect.y = insets.top;
                viewRect.width -= insets.left + insets.right;
                viewRect.height -= insets.top + insets.bottom;
                Rectangle iconR = new Rectangle();
                SwingUtilities.layoutCompoundLabel(this, this.getFontMetrics(this.getFont()), this.getText(), this.getIcon(),
                        this.getVerticalAlignment(), this.getHorizontalAlignment(), this.getVerticalTextPosition(),
                        this.getHorizontalTextPosition(), viewRect, iconR, new Rectangle(), this.getIconTextGap());
                if (!iconR.contains(x, y)) {
                    return false;
                }
                x -= iconR.x;
                y -= iconR.y;
                Color c = new Color(image.getRGB(x, y), true);
                return c.getAlpha() != 0 && (c.getRed() < 255 || c.getGreen() < 255 || c.getBlue() < 255);
            }
        };
        button.setContentAreaFilled(false);
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(button, "You clicked on the button");
            }
        });
        frame.add(button);
        frame.setSize(200, 200);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    new TestTransparentButton().createAndShowGUI();
                } catch (MalformedURLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });

    }

}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • can you tell me why this stops working when i use getContentPane().setLayout(null); and getContentPane().add(jButton1); jbutton1 replaced button – user1753429 Jan 16 '13 at 20:59
  • @user1753429 probably because you are not setting bounds on `jbutton1`. Anyway, you should almost never use null-layout, so you should never have these kind of issues. – Guillaume Polet Jan 16 '13 at 22:54
  • I have tried settings the bounds and it didn't work. I wasn't originally using a null layout either. Originally I was using a layout created by netbeans. Maybe I am settings the bounds wrong. Any chance you could edit your example to include the setbounds? – user1753429 Jan 16 '13 at 23:09
  • 1
    @user1753429 OK, there was a bug in my code. See the updated version that properly handles an object which is not in (0,0) (`viewRect.x += insets.left;` becomes `viewRect.x = insets.left;`, note the '+' that disappear before the '=' and same for the next line) – Guillaume Polet Jan 16 '13 at 23:43