Creating custom JButton from images containing transparent pixels

匆匆过客 提交于 2019-11-27 09:30:30

I think you are on the wrong way. You do not have to override neither paint() nor paintComponent() methods. JButton already "knows" to be shown with image only:

ImageIcon cup = new ImageIcon("images/cup.gif");
JButton button2 = new JButton(cup);

See the following tutorial for example: http://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-JButton.html

Moreover swing is fully customized. You can control opacity, border, color etc. You probably should override some mentioned methods to change functionality. But in most cases there is better and simpler solution.

Since there were good elements in multiple answers, but none of the answers were complete on their own, I'll answer my own question so other people that have the same problem can try something similar.

I created my buttons using a new class which extends JButton, with a new constructor that takes a BufferedImage as parameter instead of an icon. The reason for that is that when I did something like myButton.getIcon(), it would return an Icon, then I'd have to make various manipulations on it to make it a BufferedImage of the right size, and it ended up not working anyway because it seems like the first cast to Icon made it lose the alpha data in the pixels, so I couldn't check to see if the user was clicking on transparent pixels or not.

So I did something like this for the constructor:

public class MyButton extends JButton
{
   private BufferedImage bufImg;

   public MyButton(BufferedImage bufImg)
   {
      super(new ImageIcon(bufImg));
      this.bufImg = bufImg;
   }
 }

Then I created an accessor for my bufImg that resized the image to fit the JButton using the getSize() method and then returned an image resized at the right size. I do the transformations in the getBufImg() accessor because the image size might change when the window gets resized. When you call the getBufImg(), it's usually because you clicked on the button and thus you're not currently resizing the window.

Something a little bit like this will return the image at the right size:

 public BufferedImage getBufImg()
    {
      BufferedImage newImg = new BufferedImage(getSize().getWidth(), getSize().getHeight(), BufferedImage.TYPE_INT_ARGB); //Create a new buffered image the right size
      Graphics2D g2d = newImg.createGraphics();
      g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

      g2d.drawImage(bufImg, 0, 0, getSize().getWidth(), getSize().getHeight(), null);
      g2d.dispose();

      return newImg;
    }

With that buffered image, you can then code a method like this:

  private int clickAlphaValue(BufferedImage bufImg, int posX, int posY) 
  {
    int alpha;

    alpha = (bufImg.getRGB(posX, posY) >>24) & 0x000000FF; //Gets the bit that contains alpha information

    return alpha;
  }

That you call on the button that implements a MouseListener, like this:

myButton.addMouseListener(new MouseListener()
    {

    public void mouseClicked(MouseEvent e)
    {
    }

    public void mousePressed(MouseEvent e)
    {
    }

    public void mouseReleased(MouseEvent e)
    {
      if(clickAlphaValue(((myButton)e.getSource()).getBufImg(), e.getX(), e.getY()) != 0) //If alpha is not set to 0
        System.exit(0); //Or other things you want your button to do
    }

    public void mouseEntered(MouseEvent e)
    {
    }

    public void mouseExited(MouseEvent e)
    {
    }
  });

And voila! The button will only do the action if you clicked on non-transparent pixels.

Thanks for the help everyone, I couldn't have come up with this solutions on my own.

rtheunissen

If you want to have shape-specific click points, you're better off using Shape and their contains method. If you want, you can create a shape when creating your custom button class as part of it, and implement a contains method by wrapping around the shape's contains method.

As for the custom JButton, create a class that extends JButton, like this:

import java.awt.*;
import javax.swing.*;

public class CustomButton extends JButton{

    /** Filename of the image to be used as the button's icon. */
    private String fileName;
    /** The width of the button */
    private int width;
    /** The height of the button. */
    private int height;

 public CustomButton(String fileName, int width, int height){
    this.fileName = fileName;
    this.width = width;
    this.height = height;
    createButton();
}

/**
 * Creates the button according to the fields set by the constructor.
 */
private void createButton(){
    this.setIcon(getImageIcon(filename));
    this.setPreferredSize(new Dimension(width, height));
    this.setMaximumSize(new Dimension(width, height));
    this.setFocusPainted(false);
    this.setRolloverEnabled(false);
    this.setOpaque(false);
    this.setContentAreaFilled(false);
    this.setBorderPainted(false);
    this.setBorder(BorderFactory.createEmptyBorder(0,0,0,0)); 
  }
}


Here's how you can load the ImageIcon, if you want to do it like this.

  public ImageIcon getImageIcon(String fileName){
    String imageDirectory = "images/"; //relative to classpath
    URL imgURL = getClass().getResource(imageDirectory + fileName);
    return new ImageIcon(imgURL);
  }

This will give you a button that will at least look like your image. I asked a similar question regarding Image-based events on click, and Shapes helped wonders. I guess it comes down to how complex your button images are. Here's reference anyway:
How can you detect a mouse-click event on an Image object in Java?

PS: Maybe look into generating shapes from images, that go around all the pixels that aren't transparent. No idea if this is possible, but it would mean that a button would only be "pressed" if the user clicks on the image part of it. Just a thought.

If you want your button layout to be that of the non-transparent pixels in your image, then you should redefine the paintComponent() method. It is the most correct way of doing it (overriding paint() worked in old times but is now discouraged).

However I think it is not exactly what you want: you want a click on the button to be detected only if it is on a non-transparent pixel, right? In that case you have to parse your image and when clicked compare mouse coordinates to the pixel alpha channel of your image as JButton does not have such a feature.

If you have a round button, this is exactly what you need:

  public class RoundButton extends JButton {

       public RoundButton() {
         this(null, null);
      }
       public RoundButton(Icon icon) {
         this(null, icon);
      }
       public RoundButton(String text) {
         this(text, null);
      }
       public RoundButton(Action a) {
         this();
         setAction(a);
      }

       public RoundButton(String text, Icon icon) {
         setModel(new DefaultButtonModel());
         init(text, icon);
         if(icon==null) return;
         setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
         setContentAreaFilled(false);
         setFocusPainted(false);
         initShape();
      }

    protected Shape shape, base;
    protected void initShape() {
      if(!getBounds().equals(base)) {
        Dimension s = getPreferredSize();
        base = getBounds();
        shape = new Ellipse2D.Float(0, 0, s.width, s.height);
      }
    }
    @Override public Dimension getPreferredSize() {
      Icon icon = getIcon();
      Insets i = getInsets();
      int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
      return new Dimension(iw+i.right+i.left, iw+i.top+i.bottom);
    }

    @Override public boolean contains(int x, int y) {
      initShape();
      return shape.contains(x, y);
      //or return super.contains(x, y) && ((image.getRGB(x, y) >> 24) & 0xff) > 0;
    }
  }

JButton has a contains() method. Override it and call it on mouseReleased();

mKorbel

paintComponent() instead of paint() depends if you paint() inside XxxButtonUI or just override paintComponent(), but there exists the option JButton#setIcon.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!