Как я выяснил, 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.*
с скомпилированным кодом и без документации. Так что я не мог идти в ногу.
Я надеюсь, что мои усилия по отладке не сбивают с толку.
Так что мои вопросы:
- Почему изображение не был нарисован в MRE?
- Как предотвратить возникновение этой ошибки без необходимости делать произвольные вызовы (например,
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.