Java Swing ImageIcon не отображается в JLabel - PullRequest
0 голосов
/ 13 марта 2020

Как я выяснил, MRE воспроизводит каждый раз, когда выполняется одна и та же ошибка:

import java.awt.Component;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.image.BufferedImage;
import java.util.Objects;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class IconDelegate implements Icon {
    private final Icon delegated;

    public IconDelegate(final Icon delegated) {
        this.delegated = Objects.requireNonNull(delegated);
    }

    @Override
    public int getIconWidth() {
        return delegated.getIconWidth();
    }

    @Override
    public int getIconHeight() {
        return delegated.getIconHeight();
    }

    @Override
    public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
        delegated.paintIcon(c, g, x, y);
        //Some other drawing operations would be here...
    }

    public static void main(final String[] args) {
        final ImageIcon ii = new ImageIcon();
        final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
        final GraphicsDevice gdev = genv.getDefaultScreenDevice();
        final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
        final BufferedImage bimg = gcnf.createCompatibleImage(300, 400);
        final Image img = bimg.getScaledInstance(150, 200, Image.SCALE_SMOOTH);
        //img.getWidth(null); //Uncomment this line and the image will be drawn normally!
        ////img.flush(); //Uncomment this line and the image will not be drawn.
        ii.setImage(img);
        System.out.println(ii.getImageLoadStatus() == MediaTracker.ABORTED);
        final JLabel lbl = new JLabel(new IconDelegate(ii));
        final JFrame frame = new JFrame("ImageIcon delegate.");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(lbl);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

Ошибка, которую, я надеюсь, любой может воспроизвести, заключается в том, что ImageIcon изображение никогда не рисуется в показанной метке / рамке.

Когда я пытался его отладить, я обнаружил, что состояние загрузки изображения (из ImageIcon) равен MediaTracker.ABORTED, который делегирует ImageObserver.ABORT цели Component (которая реализует ImageObserver) MediaTracker экземпляра ImageIcon, который в свою очередь (согласно документации ImageObserver) имеет делать с его imageUpdate методом.

Я также заметил, что если я вызываю img.getWidth(null); непосредственно перед установкой изображения на ImageIcon, то изображение рисуется нормально (ie ошибка перестает воспроизводиться) и состояние загрузки изображения больше равно , а не и равно MediaTracker.ABORTED. После прочтения документации метода ImageObserver.imageUpdate, который я видел, говорится, что метод Image.getWidth(ImageObserver) является асинхронным ? ...

. Я также заметил, что если я вызову img.flush(); после вызов img.getWidth(null);, затем ошибка воспроизводится. Вызов img.getWidth(null); после очистки изображения останавливает воспроизведение ошибки. Этот шаблон можно повторить. Таким образом, я перехожу к, вероятно, самой важной части информации, которую я нашел в документации MediaTracker:

Если ImageObserver с не наблюдают изображение, когда первый кадр завершил загрузку , изображение может быть sh для сохранения ресурсов (см. Image.flush()).

Я должен отметить, что меня волнует последовательность событий. Например, мне нужно, чтобы ImageIcon был создан пустым, а затем использовать setImage для обновления своего изображения. Я говорю это потому, что, как я заметил, создание ImageIcon и предоставление его изображению во время построения, похоже, правильно рисует изображение, а не воспроизводит ошибку. Посмотрев на различия в конструкторе ImageIcon, который принимает метод Image и ImageIcon.setImage, я увидел, что конструктор также запрашивает свойство ' comment ' из изображения, предоставляя Image.getProperty метод с ImageObserver ... Так что я также предполагаю, что это асинхронный ? ...

Я также вызвал System.out.println(img.getClass());, но это закончилось в класс в пакете sun.* с скомпилированным кодом и без документации. Так что я не мог идти в ногу.

Я надеюсь, что мои усилия по отладке не сбивают с толку.

Так что мои вопросы:

  1. Почему изображение не был нарисован в MRE?
  2. Как предотвратить возникновение этой ошибки без необходимости делать произвольные вызовы (например, img.getWidth(null); перед setImage)?

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

Edit 1

A more- realisti c и менее минимальное MRE, которое все еще воспроизводит ошибку, выглядит следующим образом:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Objects;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ImageThumbnail implements Icon {
    private final ImageIcon delegated;
    private final int w, h;
    private String text;

    public ImageThumbnail(final int width, final int height, final String text) {
        w = width;
        h = height;
        this.text = Objects.toString(text).trim();
        delegated = new ImageIcon();
    }

    @Override
    public int getIconWidth() {
        return w;
    }

    @Override
    public int getIconHeight() {
        return h;
    }

    public void setText(final String text) {
        this.text = Objects.toString(text).trim();
    }

    //Scales the given image to fit this icon's width and height maintaining aspect ratio:
    public void setImage(final BufferedImage bimg) {
        final int bimgw = bimg.getWidth(),
                  bimgh = bimg.getHeight();
        if (bimgw > bimgh)
            delegated.setImage(bimg.getScaledInstance(Math.min(w, bimgw), -1, Image.SCALE_SMOOTH));
        else
            delegated.setImage(bimg.getScaledInstance(-1, Math.min(h, bimgh), Image.SCALE_SMOOTH));
        //Alternatively you can try the following code if you don't trust the -1 arguments above:
        //delegated.setImage(bimg.getScaledInstance(w, h, Image.SCALE_SMOOTH));
    }

    @Override
    public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
        delegated.paintIcon(c, g, x, y);
        //Other drawing operations here, like drawing a String, at the center, over the ImageIcon:
        g.drawString(text, x + w / 2 - g.getFontMetrics().stringWidth(text) / 2, y + h / 2);
    }

    private static JPanel centered(final Component c) {
        /*Creating a JPanel with GridBagLayout and a single component
        in it, will layout the component in the center of the JPanel.*/
        final JPanel panel = new JPanel(new GridBagLayout());
        panel.add(c);
        return panel;
    }

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(() -> {

            final ImageThumbnail thumb = new ImageThumbnail(200, 200, "Select an image...");

            final JLabel lbl = new JLabel(thumb);

            final JFileChooser chooser = new JFileChooser();
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            chooser.setMultiSelectionEnabled(false);

            final JButton load = new JButton("Load image");
            load.addActionListener(e -> {
                if (chooser.showOpenDialog(load) == JFileChooser.APPROVE_OPTION) {
                    try {
                        thumb.setImage(ImageIO.read(chooser.getSelectedFile()));
                        thumb.setText("Image-001");
                        lbl.setForeground(Color.GREEN.darker());
                        //lbl.repaint(); //Not needed, as I call 'lbl.setForeground(Color.GREEN.darker());' above...
                    }
                    catch (final IOException iox) {
                        iox.printStackTrace();
                    }
                }
            });

            final JPanel contents = new JPanel(new BorderLayout());
            contents.add(centered(lbl), BorderLayout.CENTER);
            contents.add(centered(load), BorderLayout.PAGE_END);

            final JFrame frame = new JFrame("Thumbnail of images.");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(contents);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

Я знаю, что мог бы использовать SwingWorker на кнопке для загрузки изображения, чтобы пользователь не испытывал задержку , но для простоты я предпочел немного заблокировать EDT.

1 Ответ

1 голос
/ 13 марта 2020

Как предотвратить возникновение этой ошибки без необходимости произвольных вызовов

С вашим исходным кодом я получил NPE.

Кажется, что работает следующее. Теперь я вижу черное изображение:

//final ImageIcon ii = new ImageIcon();
…
//ii.setImage(img);
ImageIcon ii = new ImageIcon( img );

Редактировать:

Я попытался поиграть с разными подходами, в том числе, убедившись, что на этикетке есть ImageObserver. У меня ничего не получалось.

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

public class IconMRE extends JPanel
{
    private ImageIcon icon = new ImageIcon( new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB) );
    private JLabel label1 = new JLabel( icon );

    public IconMRE()
    {
        setLayout( new BorderLayout(20, 20) );

        label1 = new JLabel( icon );
        add(label1, BorderLayout.LINE_START);

        JLabel label2 = new JLabel();
        icon.setImageObserver( label2 );
        label2.setIcon( icon );
        add(label2, BorderLayout.LINE_END);


        JComboBox<Color>comboBox = new JComboBox<Color>();
        comboBox.addItem( Color.RED );
        comboBox.addItem( Color.GREEN );
        comboBox.addItem( Color.BLUE );
        add(comboBox, BorderLayout.PAGE_START);

        comboBox.addActionListener( (e) ->
        {
            Color background = (Color)comboBox.getSelectedItem();
            BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = image.createGraphics();
            g2d.setColor( background );
            g2d.fillRect(0, 0, 200, 200);
            g2d.dispose();
//          image.getWidth(null);
            icon.setImage( image );
//          label1.repaint();
        });

        comboBox.setSelectedIndex(2);
    }

    private static void createAndShowUI()
    {
        JFrame frame = new JFrame("IconMRE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( new IconMRE() );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}

Единственное, что сработало, было принудительно перерисовать () на этикетке.

...