java 从包含透明像素的图像创建自定义 JButton

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6735891/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 17:04:13  来源:igfitidea点击:

Creating custom JButton from images containing transparent pixels

javaswingiconsjbuttonimageicon

提问by Adam Smith

Read edit 2 for what I'm actually missing to make it work

阅读编辑 2 了解我实际上缺少的内容以使其正常工作

I'm currently trying to create some custom JButtons using images created in photoshop that have an alpha parameter.

我目前正在尝试使用在 photoshop 中创建的具有 alpha 参数的图像创建一些自定义 JButton。

So far, overriding the paint() method to draw the image has worked in the sense that the button is drawn showing the correct image. I'd like to improve it, though, by making its shape (clickable area) the same as the visible pixels on the image (right now if I draw the button's border, it's a square).

到目前为止,覆盖paint() 方法来绘制图像的工作原理是绘制按钮显示正确的图像。不过,我想通过使其形状(可点击区域)与图像上的可见像素相同来改进它(现在如果我绘制按钮的边框,它是一个正方形)。

Is there an easy way to do that or do I have to parse the image and find the alpha pixels to make a custom border?

有没有一种简单的方法可以做到这一点,或者我是否必须解析图像并找到 alpha 像素来制作自定义边框?

Which methods would I have to override to make it work the way I want?

我必须重写哪些方法才能使其按我想要的方式工作?

Also, another question I'm going to have later: would it be better to use some kind of algorithm to change the images' colors to make it seem like it is being clicked when people click on it or am I better off creating a second image and drawing that one while the button is active?

另外,我稍后要问的另一个问题是:使用某种算法来更改图像的颜色以使其看起来在人们点击它时被点击会更好,还是我最好创建第二个图像并在按钮处于活动状态时绘制那个?

Edit:I just read on some other question that I should redefine paintComponent() instead of paint(), I'd like to know why since redefining paint() works fine?

编辑:我刚刚阅读了一些其他问题,我应该重新定义paintComponent()而不是paint(),我想知道为什么因为重新定义paint()工作正常?

Edit 2:I changed everything to make sure my JButtons are created using the default constructor with an icon. What I'm trying to do is get the X and Y position of where the click was registered and grab the icon's pixel at that position and check its alpha channel to see if it is 0 (if it is, do nothing, else do the action it is supposed to do).

编辑 2:我更改了所有内容以确保我的 JButton 是使用带有图标的默认构造函数创建的。我想要做的是获取点击注册位置的 X 和 Y 位置并在该位置获取图标的像素并检查其 alpha 通道以查看它是否为 0(如果是,则什么都不做,否则执行它应该做的动作)。

The thing is, the alpha channel always returns 255 (and blue, red and green are at 238 on transparent pixels). On other pixels, everything returns the value it should be returning.

问题是,alpha 通道总是返回 255(蓝色、红色和绿色在透明像素上为 238)。在其他像素上,一切都返回它应该返回的值。

Here's an example (try it with another image if you want) that recreates my problem:

这是一个重新创建我的问题的示例(如果需要,请尝试使用其他图像):

public class TestAlphaPixels extends JFrame
{
  private final File FILECLOSEBUTTON = new File("img\boutonrondX.png");  //My round button with transparent corners
  private JButton closeButton = new JButton(); //Creating it empty to be able to place it and resize the image after the button size is known


  public TestAlphaPixels() throws IOException
  {
    setLayout(null);
    setSize(150, 150);

    closeButton.setSize(100, 100);
    closeButton.setContentAreaFilled(false);
    closeButton.setBorderPainted(false);

    add(closeButton);

    closeButton.addMouseListener(new MouseListener()
      {

        public void mouseClicked(MouseEvent e)
        {
        }

        public void mousePressed(MouseEvent e)
        {
        }

        public void mouseReleased(MouseEvent e)
        {
          System.out.println("Alpha value of pixel (" + e.getX() + ", " + e.getY() + ") is: " + clickAlphaValue(closeButton.getIcon(), e.getX(), e.getY()));
        }

        public void mouseEntered(MouseEvent e)
        {
        }

        public void mouseExited(MouseEvent e)
        {
        }
      });
    Image imgCloseButton = ImageIO.read(FILECLOSEBUTTON);

    //Resize the image to fit the button
    Image newImg = imgCloseButton.getScaledInstance((int)closeButton.getSize().getWidth(), (int)closeButton.getSize().getHeight(), java.awt.Image.SCALE_SMOOTH);
    closeButton.setIcon(new ImageIcon(newImg));


  }

  private int clickAlphaValue(Icon icon, int posX, int posY) 
  {
    int width = icon.getIconWidth();
    int height = icon.getIconHeight();

    BufferedImage tempImage = (BufferedImage)createImage(width, height);
    Graphics2D g = tempImage.createGraphics();

    icon.paintIcon(null, g, 0, 0);

    g.dispose();

    int alpha = (tempImage.getRGB(posX, posY) >> 24) & 0x000000FF;

    return alpha;
  } 
  public static void main(String[] args)
  {
    try
    {
      TestAlphaPixels testAlphaPixels = new TestAlphaPixels();
      testAlphaPixels.setVisible(true);
      testAlphaPixels.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    catch(IOException ioe)
    {
      ioe.printStackTrace();
    }
  }
}

What this sample actually displays

此示例实际显示的内容

This is just a wild guess, but is it possible that when my image gets cast to an Icon, it loses its Alpha property and thus doesn't return the correct value? Anyway, I'd really appreciate it if someone could actually help me out and tell me what I should be changing to get the correct value.

这只是一个疯狂的猜测,但是当我的图像被转换为​​ Icon 时,它是否可能会丢失其 Alpha 属性并因此不会返回正确的值?无论如何,如果有人能真正帮助我并告诉我我应该改变什么以获得正确的价值,我真的很感激。

I'm guessing that because when I try it with the original image, the alpha channel's value is fine, but I can't actually use that BufferedImage because I resize it, so I actually get the channel values of the image with the original size...

我猜是因为当我用原始图像尝试它时,alpha 通道的值很好,但我实际上不能使用那个 BufferedImage 因为我调整了它的大小,所以我实际上得到了原始大小的图像的通道值...

采纳答案by Adam Smith

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.

我使用一个扩展 JButton 的新类创建了我的按钮,并使用一个新的构造函数将 BufferedImage 作为参数而不是图标。这样做的原因是,当我执行类似 myButton.getIcon() 之类的操作时,它会返回一个 Icon,然后我必须对其进行各种操作以使其成为大小合适的 BufferedImage,但最终无法正常工作无论如何,因为似乎第一次投射到 Icon 使它丢失了像素中的 alpha 数据,所以我无法检查用户是否点击了透明像素。

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.

然后我为我的 bufImg 创建了一个访问器,它使用 getSize() 方法调整图像大小以适合 JButton,然后返回一个调整为正确大小的图像。我在 getBufImg() 访问器中进行转换,因为当窗口调整大小时图像大小可能会改变。当您调用 getBufImg() 时,通常是因为您单击了按钮,因此您当前没有调整窗口大小。

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:

您调用实现 MouseListener 的按钮,如下所示:

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.

感谢大家的帮助,我不可能自己想出这个解决方案。

回答by AlexR

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:

我认为你走错了路。您不必覆盖paint() 和paintComponent() 方法。JButton 已经“知道”只显示图像:

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

例如,请参阅以下教程: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.

此外,swing 是完全定制的。您可以控制不透明度、边框、颜色等。您可能应该覆盖一些提到的方法来更改功能。但在大多数情况下,有更好、更简单的解决方案。

回答by rtheunissen

If you want to have shape-specific click points, you're better off using Shape and their containsmethod. 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 containsmethod.

如果您想拥有特定于形状的点击点,最好使用 Shape 及其contains方法。如果需要,您可以在创建自定义按钮类时创建一个形状作为它的一部分,并通过环绕形状的contains方法来实现 contains方法。

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

至于自定义JButton,创建一个扩展JButton的类,像这样:

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.


如果您想像这样加载 ImageIcon,请按以下方法加载。

  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?

这将为您提供一个至少看起来像您的图像的按钮。我问了一个关于点击时基于图像的事件的类似问题,形状帮助了奇迹。我想这归结为按钮图像的复杂程度。无论如何,这是参考:
如何在 Java 中检测 Image 对象上的鼠标单击事件?

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.

PS:也许可以考虑从图像生成形状,围绕所有不透明的像素。不知道这是否可行,但这意味着只有在用户单击按钮的图像部分时才会“按下”按钮。只是一个想法。

回答by Guillaume

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).

如果您希望您的按钮布局是图像中不透明像素的布局,那么您应该重新定义该paintComponent()方法。这是最正确的方法(覆盖paint()在过去有效,但现在不鼓励)。

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.

但是,我认为这并不完全是您想要的:您希望仅当按钮位于非透明像素上时才能检测到单击,对吗?在这种情况下,您必须解析您的图像,并在单击时将鼠标坐标与图像的像素 alpha 通道进行比较,因为 JButton 没有这样的功能。

回答by mKorbel

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

paintComponent()而不是paint()取决于您是paint()XxxButtonUI 中还是只是 override paintComponent(),但存在选项JButton#setIcon

回答by rtheunissen

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();

JButton 有一个contains()方法。覆盖它并在 mouseReleased() 上调用它;