【发布时间】:2020-03-13 14:29:02
【问题描述】:
我发现以下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,它委托给目标@987654328 的ImageObserver.ABORT ImageIcon 实例的MediaTracker 的@(实现ImageObserver),而这又与imageUpdate 方法有关(根据ImageObserver 的文档)。
我还注意到,如果我在将图像设置为ImageIcon 之前调用img.getWidth(null);,则图像会正常绘制(即错误停止再现)并且图像加载状态是不是等于MediaTracker.ABORTED了。
在阅读了ImageObserver.imageUpdate 方法的文档后,我看到它指出Image.getWidth(ImageObserver) 方法是异步?...
我还注意到,如果我在调用 img.getWidth(null); 之后调用 img.flush();,则会重现错误。
刷新图像后调用img.getWidth(null); 会阻止错误再现。
这种模式可以重复。
因此,我归结为我在MediaTracker 的文档中找到的可能最重要的信息:
如果在第一帧完成加载后没有
ImageObservers 正在观察图像,则图像可能会自行刷新以节省资源(请参阅Image.flush())。
我必须注意,我确实关心事件的顺序。例如,我需要将ImageIcon 创建为空,然后使用setImage 更新其图像。
我这么说是因为,正如我所注意到的,创建ImageIcon 并在构建时为其提供图像似乎可以正确绘制图像而不是重现错误。
在查看了接受Image 和ImageIcon.setImage 方法的ImageIcon 构造函数的区别后,我看到构造函数还从图像中请求属性'comment',为Image.getProperty 方法提供ImageObserver...所以我也猜它是异步?...
我也调用了System.out.println(img.getClass());,但它最终出现在sun.* 包中的一个类中,带有编译代码并且没有文档。所以我跟不上。
我希望我的调试努力不会误导。
所以我的问题是:
- 是什么原因导致图像未在 MRE 中绘制?
- 如何在不进行任意调用(例如
img.getWidth(null);之前的setImage)的情况下防止发生此错误?
我还发现了this 相关问题,但我看到ImageIcon 类在内部使用MediaTracker,那么重复代码有什么意义?
ImageIcon 中的MediaTracker 中止加载图像(根据其加载状态)的原因我不知道。
编辑 1
仍然重现错误的更现实和更少最小化的 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 一段时间。
【问题讨论】:
标签: java image swing jlabel imageicon