【问题标题】:Creating custom JButton from images containing transparent pixels从包含透明像素的图像创建自定义 JButton
【发布时间】:2011-10-07 19:57:32
【问题描述】:

阅读编辑 2,了解我实际上缺少什么以使其正常工作

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

到目前为止,重写paint() 方法来绘制图像在按钮被绘制以显示正确图像的意义上是有效的。不过,我想改进它,使其形状(可点击区域)与图像上的可见像素相同(现在如果我绘制按钮的边框,它是一个正方形)。

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

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

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

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

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

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

这是一个重现我的问题的示例(如果需要,可以尝试使用另一张图片):

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

这只是一个疯狂的猜测,但是当我的图像被投射到一个图标时,它是否有可能失去它的 Alpha 属性,因此不会返回正确的值?无论如何,如果有人能真正帮助我并告诉我应该改变什么以获得正确的值,我将不胜感激。

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

【问题讨论】:

  • 如果您希望按钮只是图像,为什么不使用JLabel 并将图像设置为其图标?
  • 第一个原因是我将有一些按钮在从数组生成的图像上显示文本。第二个是我不知道将其设为 JLabel 只会绘制非透明像素(因此不会将 alpha 像素视为点击)。我可能会尝试这样做,但我仍然需要编写方法来绘制它,让它看起来像是被点击了。
  • 关于您的第二次编辑,您为什么要准确地测试 alpha?如果这是为了查看您是否单击了圆形按钮,则有更好的方法可以做到这一点,如果是这种情况,我会将其发布为答案。仅当形状不典型时,测试 alpha 才可能派上用场。 (我看到你的是圆的?)
  • @paranoid-android 您的解决方案运行良好,肯定会派上用场。问题是我稍后将在这个程序中使用其他形状奇特的按钮,我只是不能将它们用作示例,因为我还没有创建它们,所以圆形按钮不适用于这些按钮。我可能会使用你的方法来制作圆形按钮,因为它更简单,不过谢谢!
  • @Adam Smith:很高兴听到这个消息。 :) 如果您采用 JButton 的扩展并开始集成 Shapes,那么您就走在了正确的轨道上。希望您的应用适合您。

标签: java swing icons jbutton imageicon


【解决方案1】:

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

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

【讨论】:

  • 这种方法似乎过于复杂。
  • 不多,在 java.awt.image 中有一些类允许访问图像中像素的 RGB 和 alpha 值。
  • @Moonbeam 什么是好的替代解决方案?纪尧姆所说的似乎与我正在寻找的完全一样。我知道你提到了 JLabels,但那些会记录对 alpha 像素的点击吗?如果他们这样做了,我认为它不会比纪尧姆的方法更简单。
  • 这是我现在正在尝试实施的方式,但有些事情并没有按照我想要的方式工作。如果您能帮我解决它,我很乐意将您的答案标记为已接受。
【解决方案2】:

paintComponent() 而不是 paint() 取决于您是在 XxxButtonUI 内使用 paint() 还是只是覆盖 paintComponent(),但存在选项 JButton#setIcon

【讨论】:

    【解决方案3】:

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

    至于自定义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)); 
      }
    }
    


    如果您想这样加载 ImageIcon,请按以下步骤操作。

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

    这将为您提供一个至少看起来像您的图像的按钮。 我问了一个类似的关于基于图像的点击事件的问题,Shapes 帮助创造了奇迹。 我想这取决于您的按钮图像有多复杂。 无论如何,这是参考:
    How can you detect a mouse-click event on an Image object in Java?

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

    【讨论】:

    • 您实际上可以在不使用 Shape 的情况下做到这一点。看看设置一个空边框是否只会让图片上的点击触发一个 ActionEvent。
    【解决方案4】:

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

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

    例如看下面的教程:http://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-JButton.html

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

    【讨论】:

      【解决方案5】:

      由于多个答案中有很好的元素,但没有一个答案本身是完整的,我会回答我自己的问题,以便其他有相同问题的人可以尝试类似的问题。

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

      所以我为构造函数做了这样的事情:

      public class MyButton extends JButton
      {
         private BufferedImage bufImg;
      
         public MyButton(BufferedImage bufImg)
         {
            super(new ImageIcon(bufImg));
            this.bufImg = bufImg;
         }
       }
      

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

      有点像这样的东西会以正确的大小返回图像:

       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;
          }
      

      使用该缓冲图像,您可以编写如下方法:

        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;
        }
      

      在实现 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)
          {
          }
        });
      

      瞧!只有在您单击不透明像素时,该按钮才会执行该操作。

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

      【讨论】:

        【解决方案6】:

        如果你有一个圆形按钮,这正是你所需要的:

          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 有一个contains() 方法。覆盖它并在 mouseReleased() 上调用它;

        【讨论】:

        • 为什么在你的例子中使用 EmtpyBorder,顺便说一句 +1
        • @mKorbel:因为您可能喜欢的任何边框都应该包含在用于按钮的图像中。而且我认为它与跨平台 ActionEvent 处理有关(当您单击按钮“激活”时),但我可能错了。真的不确定,所以不要相信我的话。
        • 为什么不是 LineBorder ??? stackoverflow.com/questions/5751311/…,这不是攻击你的人,我对...的好奇心......
        • 我们想要创建一个边框,但通过将其设为 0 宽等使其有效地不可见。 EmptyBorder 是显而易见的选择。来自 Java API:EmptyBorder: A class which provides an empty, transparent border which takes up space but does no drawing.
        • 这并没有说服我,任何使用 EmptyBorder 的好主意,谢谢
        猜你喜欢
        • 2013-03-26
        • 1970-01-01
        • 2012-11-29
        • 2014-03-30
        • 2012-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-30
        相关资源
        最近更新 更多