Создание пользовательского JButton из изображений, содержащих прозрачные пиксели

Прочитайте редактирование 2, чтобы понять, чего мне не хватает, чтобы заставить его работать

В настоящее время я пытаюсь создать несколько пользовательских JButtons, используя изображения, созданные в фотошопе, которые имеют альфа-параметр.

До сих пор переопределение метода paint() для отрисовки изображения работало в том смысле, что кнопка рисовалась с правильным изображением. Однако я хотел бы улучшить его, сделав его форму (область клика) такой же, как видимые пиксели на изображении (прямо сейчас, если я нарисую границу кнопки, это будет квадрат).

Есть ли простой способ сделать это или мне нужно проанализировать изображение и найти альфа-пиксели, чтобы создать пользовательскую границу?

Какие методы я должен переопределить, чтобы заставить его работать так, как я хочу?

Кроме того, еще один вопрос, который у меня возникнет позже: было бы лучше использовать какой-то алгоритм для изменения цветов изображений, чтобы казалось, что на него нажимают, когда люди нажимают на него, или мне лучше создать второй изображение и нарисовать его, пока кнопка активна?

Редактировать: я только что прочитал по другому вопросу, что мне следует переопределить paintComponent() вместо paint(), я хотел бы знать, почему, поскольку переопределение paint() работает нормально?

Изменить 2: я изменил все, чтобы убедиться, что мои JButtons создаются с использованием конструктора по умолчанию со значком. Что я пытаюсь сделать, так это получить положение X и Y, где был зарегистрирован щелчок, и захватить пиксель значка в этом положении и проверить его альфа-канал, чтобы увидеть, равен ли он 0 (если это так, ничего не делайте, иначе выполните действие, которое он должен совершить).

Дело в том, что альфа-канал всегда возвращает 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();
    }
  }
}

Что на самом деле показывает этот пример

Это всего лишь дикая догадка, но возможно ли, что когда мое изображение преобразуется в Icon, оно теряет свое свойство Alpha и, таким образом, не возвращает правильное значение? В любом случае, я был бы очень признателен, если бы кто-нибудь действительно помог мне и сказал, что я должен изменить, чтобы получить правильное значение.

Я предполагаю, что когда я пробую это с исходным изображением, значение альфа-канала в порядке, но я на самом деле не могу использовать это BufferedImage, потому что я изменяю его размер, поэтому я фактически получаю значения канала изображения с исходным размером ...


person Adam Smith    schedule 18.07.2011    source источник
comment
Если вы хотите, чтобы кнопка была просто изображением, почему бы не использовать JLabel и не установить изображение в качестве значка?   -  person Moonbeam    schedule 18.07.2011
comment
Первая причина заключается в том, что у меня будут кнопки с текстом поверх изображения, сгенерированного из массива. Во-вторых, я не знал, что при создании JLabel будут отображаться только непрозрачные пиксели (и, следовательно, альфа-пиксели не будут рассматриваться как щелчок). Я мог бы попробовать это, но мне все равно пришлось бы кодировать методы, чтобы нарисовать его, чтобы он выглядел так, как будто на него нажимают.   -  person Adam Smith    schedule 18.07.2011
comment
Что касается вашего второго редактирования, почему вы тестируете именно альфу? Если это нужно для того, чтобы увидеть, нажали ли вы круглую кнопку, есть гораздо более приятные способы сделать это, и если это так, я опубликую это как ответ. Тестирование на альфа-канал может оказаться полезным, только если формы не являются типичными. (Вижу, твой круглый?)   -  person rtheunissen    schedule 19.07.2011
comment
@paranoid-android Ваше решение отлично работает и обязательно пригодится. Дело в том, что позже в этой программе у меня будут другие кнопки необычной формы, я просто не мог использовать их в качестве примера, потому что я их еще не создал, поэтому круглые кнопки не будут работать на них. Я, вероятно, воспользуюсь вашим методом для круглых кнопок, так как он намного проще, но спасибо!   -  person Adam Smith    schedule 19.07.2011
comment
@ Адам Смит: Рад это слышать. :) Если вы возьмете это расширение JButton и начнете интегрировать Shapes, вы на правильном пути. Надеюсь, ваше приложение работает для вас.   -  person rtheunissen    schedule 19.07.2011


Ответы (6)


Я думаю, что вы на неправильном пути. Вам не нужно переопределять ни метод 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

Более того, качели полностью настраиваются. Вы можете управлять непрозрачностью, границей, цветом и т. д. Вероятно, вам следует переопределить некоторые упомянутые методы для изменения функциональности. Но в большинстве случаев есть лучшее и простое решение.

person AlexR    schedule 18.07.2011

Поскольку в нескольких ответах были хорошие элементы, но ни один из ответов не был полным сам по себе, я отвечу на свой вопрос, чтобы другие люди, у которых есть такая же проблема, могли попробовать что-то подобное.

Я создал свои кнопки, используя новый класс, который расширяет JButton, с новым конструктором, который принимает BufferedImage в качестве параметра вместо значка. Причина этого в том, что когда я делал что-то вроде myButton.getIcon(), он возвращал значок, затем мне приходилось выполнять различные манипуляции с ним, чтобы сделать его BufferedImage нужного размера, и в итоге он не работал. в любом случае, потому что кажется, что первое приведение к Icon привело к потере альфа-данных в пикселях, поэтому я не мог проверить, нажимал ли пользователь на прозрачные пиксели или нет.

Итак, я сделал что-то вроде этого для конструктора:

public class MyButton extends JButton
{
   private BufferedImage bufImg;

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

Затем я создал метод доступа для моего bufImg, который изменил размер изображения, чтобы он соответствовал JButton, используя метод getSize(), а затем вернул изображение, измененное до нужного размера. Я выполняю преобразования в методе доступа 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)
    {
    }
  });

И вуаля! Кнопка будет выполнять действие только в том случае, если вы нажали на непрозрачные пиксели.

Спасибо за помощь всем, я не мог бы придумать это решение самостоятельно.

person Adam Smith    schedule 19.07.2011

Если вы хотите иметь точки щелчка для конкретных фигур, вам лучше использовать 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);
  }

Это даст вам кнопку, которая, по крайней мере, будет похожа на ваше изображение. Я задал аналогичный вопрос о событиях на основе изображений при клике, и формы помогли чудесам. Я предполагаю, что это сводится к тому, насколько сложны ваши изображения кнопок. В любом случае, вот ссылка:
Как вы можете обнаружить событие щелчка мыши на объекте изображения в Java?

PS: Возможно, стоит подумать о создании фигур из изображений, которые обходят все непрозрачные пиксели. Не знаю, возможно ли это, но это будет означать, что кнопка будет «нажата», только если пользователь нажмет на ее часть изображения. Просто мысль.

person rtheunissen    schedule 18.07.2011
comment
На самом деле вы можете сделать это, вообще не используя Shape. Посмотрите, вызывает ли установка пустой границы только клики по изображению, вызывающие событие ActionEvent. - person rtheunissen; 18.07.2011

Если вы хотите, чтобы макет вашей кнопки соответствовал непрозрачным пикселям изображения, вам следует переопределить метод paintComponent(). Это самый правильный способ сделать это (переопределение paint() работало в старые времена, но теперь не рекомендуется).

Однако я думаю, что это не совсем то, что вам нужно: вы хотите, чтобы щелчок по кнопке обнаруживался только в том случае, если он находится на непрозрачном пикселе, верно? В этом случае вы должны проанализировать свое изображение и при нажатии сравнить координаты мыши с альфа-каналом пикселя вашего изображения, поскольку JButton не имеет такой функции.

person Guillaume    schedule 18.07.2011
comment
Такой подход кажется слишком сложным. - person Moonbeam; 18.07.2011
comment
Не так много, в java.awt.image есть классы, которые позволяют получить доступ к значениям RGB и альфа для пикселя в изображении. - person Guillaume; 18.07.2011
comment
@Moonbeam Что было бы хорошим альтернативным решением? То, что сказал Гийом, похоже на то, что я ищу. Я знаю, что вы упомянули JLabels, но будут ли они регистрировать клики по альфа-пикселям? Если они это сделают, я не понимаю, как это может быть менее сложным, чем подход Гийома. - person Adam Smith; 18.07.2011
comment
Это то, как я пытаюсь реализовать прямо сейчас, но некоторые вещи не работают так, как я хочу. Если вы можете помочь мне решить эту проблему, я с радостью отмечу ваш ответ как принятый. - person Adam Smith; 19.07.2011

Если у вас есть круглая кнопка, это именно то, что вам нужно:

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

person rtheunissen    schedule 19.07.2011
comment
почему EmtpyBorder в ваших примерах, кстати +1 - person mKorbel; 19.07.2011
comment
@mKorbel: Потому что любая рамка, которая вам может понравиться, должна быть включена как часть вашего изображения, которое будет использоваться для кнопки. И я думаю, что это как-то связано с кросс-платформенной обработкой ActionEvent (когда кнопка активируется при нажатии на нее), но я могу ошибаться в этом. На самом деле не уверен, так что не верьте мне на слово. - person rtheunissen; 19.07.2011
comment
почему не LineBorder??? stackoverflow.com/questions/5751311/, это не нападение на вашу личность, мое любопытство... - person mKorbel; 19.07.2011
comment
Мы хотим создать рамку, но сделать ее практически невидимой, сделав ее шириной 0 и т. д. EmptyBorder — просто очевидный выбор. Из API Java: EmptyBorder: A class which provides an empty, transparent border which takes up space but does no drawing. - person rtheunissen; 19.07.2011
comment
это меня не убедило, в любом случае хорошая идея с EmptyBorder, спасибо - person mKorbel; 19.07.2011

paintComponent() вместо paint() зависит от того, paint() вы находитесь внутри XxxButtonUI или просто переопределяете < a href="https://stackoverflow.com/questions/6725618/how-can-i-make-this-jbutton-visible-when-i-have-progressive-scan-background-jwin/6726789#6726789">paintComponent() , но существует опция JButton#setIcon.

person mKorbel    schedule 18.07.2011