我有一个JPanel,我想添加我在飞行中生成的JPEG和PNG图像。

到目前为止,我在Swing教程中看到的所有示例,特别是在Swing示例中,都使用了ImageIcons。

我将这些图像生成为字节数组,它们通常比示例中使用的普通图标大,为640x480。

在使用ImageIcon类在JPanel中显示这样大小的图像时,是否存在任何(性能或其他)问题? 通常的做法是什么? 如何添加一个图像到JPanel而不使用ImageIcon类?

编辑:对教程和API进行更仔细的检查可以发现,您不能将ImageIcon直接添加到JPanel中。相反,它们通过将图像设置为JLabel的图标来实现相同的效果。这感觉不对……


当前回答

我可以看到很多答案,但并没有真正解决OP的三个问题。

1)关于性能的一个词:字节数组可能是低效的,除非你可以使用与显示适配器当前分辨率和颜色深度匹配的精确像素字节排序。

要获得最佳的绘图性能,只需将图像转换为BufferedImage,该BufferedImage生成的类型与当前图形配置相对应。参见https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html上的createCompatibleImage

在绘制几次之后,这些图像将自动缓存到显卡内存中,而不需要任何编程工作(这是Java 6以来Swing中的标准),因此实际绘制所花费的时间可以忽略不计——如果您没有更改图像的话。

改变图像将带来主存和GPU内存之间的额外内存传输,这是缓慢的。避免将图像“重绘”到BufferedImage中,因此,避免使用getPixel和setPixel。

例如,如果你正在开发一个游戏,而不是将所有的游戏角色绘制到一个BufferedImage,然后再绘制到一个JPanel,它会更快地将所有的角色加载为更小的BufferedImage,并在你的JPanel代码中一个一个地在它们适当的位置绘制它们——这样在主存和GPU内存之间就没有额外的数据传输,除了用于缓存的图像的初始传输。

ImageIcon将在底层使用BufferedImage -但基本上分配一个具有适当图形模式的BufferedImage是关键,并且没有努力做到这一点。

2)通常的方法是在JPanel的一个重写的paintComponent方法中绘制一个BufferedImage。虽然Java支持大量额外的功能,比如控制GPU内存中缓存的volatile image的缓冲链,但是没有必要使用这些功能,因为Java 6在没有暴露GPU加速的所有细节的情况下做得相当不错。

请注意,GPU加速可能不适用于某些操作,例如拉伸半透明图像。

3)不添加,如上所述直接涂上即可:

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(image, 0, 0, this); 
}

如果图像是布局的一部分,“添加”是有意义的。如果你需要它作为背景或前景图像填充JPanel,只需在paintComponent中绘制。如果您更喜欢制作一个通用的Swing组件来显示您的图像,那么情况是一样的(您可以使用JComponent并重写它的paintComponent方法)——然后将其添加到GUI组件的布局中。

4)如何将数组转换为Bufferedimage

将字节数组转换为PNG,然后加载它相当耗费资源。更好的方法是将现有的字节数组转换为BufferedImage。

为此:不要使用For循环和复制像素。这是非常非常慢的。而不是:

learn the preferred byte structure of the BufferedImage (nowadays it is safe to assume RGB or RGBA, which is 4 bytes per pixel) learn the scanline and scansize in use (e.g. you might have a 142 pixels wide image - but in the real life that will be stored as a 256 pixel wide byte array since it is faster to process that and mask the unused pixes by the GPU hardware) then once you have an array build according to these principles, the setRGB array method of the BufferedImage can copy your array to the BufferedImage.

其他回答

弗雷德·哈斯拉姆的方法很有效。但是我在文件路径上遇到了麻烦,因为我想引用jar中的图像。为了做到这一点,我使用:

BufferedImage wPic = ImageIO.read(this.getClass().getResource("snow.png"));
JLabel wIcon = new JLabel(new ImageIcon(wPic));

因为我只有有限的数量(大约10)的图像,我需要使用这种方法加载,它工作得很好。它不需要有正确的相对文件路径就可以获取文件。

JPanel几乎总是要子类化的错误类。为什么不子类化JComponent呢?

ImageIcon有一个小问题,构造函数阻塞读取图像。当从应用程序jar中加载时,这并不是真正的问题,但如果您可能通过网络连接进行读取,则可能存在问题。在awt时代有很多使用MediaTracker、ImageObserver及其朋友的例子,甚至在JDK演示中也是如此。

我可以看到很多答案,但并没有真正解决OP的三个问题。

1)关于性能的一个词:字节数组可能是低效的,除非你可以使用与显示适配器当前分辨率和颜色深度匹配的精确像素字节排序。

要获得最佳的绘图性能,只需将图像转换为BufferedImage,该BufferedImage生成的类型与当前图形配置相对应。参见https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html上的createCompatibleImage

在绘制几次之后,这些图像将自动缓存到显卡内存中,而不需要任何编程工作(这是Java 6以来Swing中的标准),因此实际绘制所花费的时间可以忽略不计——如果您没有更改图像的话。

改变图像将带来主存和GPU内存之间的额外内存传输,这是缓慢的。避免将图像“重绘”到BufferedImage中,因此,避免使用getPixel和setPixel。

例如,如果你正在开发一个游戏,而不是将所有的游戏角色绘制到一个BufferedImage,然后再绘制到一个JPanel,它会更快地将所有的角色加载为更小的BufferedImage,并在你的JPanel代码中一个一个地在它们适当的位置绘制它们——这样在主存和GPU内存之间就没有额外的数据传输,除了用于缓存的图像的初始传输。

ImageIcon将在底层使用BufferedImage -但基本上分配一个具有适当图形模式的BufferedImage是关键,并且没有努力做到这一点。

2)通常的方法是在JPanel的一个重写的paintComponent方法中绘制一个BufferedImage。虽然Java支持大量额外的功能,比如控制GPU内存中缓存的volatile image的缓冲链,但是没有必要使用这些功能,因为Java 6在没有暴露GPU加速的所有细节的情况下做得相当不错。

请注意,GPU加速可能不适用于某些操作,例如拉伸半透明图像。

3)不添加,如上所述直接涂上即可:

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(image, 0, 0, this); 
}

如果图像是布局的一部分,“添加”是有意义的。如果你需要它作为背景或前景图像填充JPanel,只需在paintComponent中绘制。如果您更喜欢制作一个通用的Swing组件来显示您的图像,那么情况是一样的(您可以使用JComponent并重写它的paintComponent方法)——然后将其添加到GUI组件的布局中。

4)如何将数组转换为Bufferedimage

将字节数组转换为PNG,然后加载它相当耗费资源。更好的方法是将现有的字节数组转换为BufferedImage。

为此:不要使用For循环和复制像素。这是非常非常慢的。而不是:

learn the preferred byte structure of the BufferedImage (nowadays it is safe to assume RGB or RGBA, which is 4 bytes per pixel) learn the scanline and scansize in use (e.g. you might have a 142 pixels wide image - but in the real life that will be stored as a 256 pixel wide byte array since it is faster to process that and mask the unused pixes by the GPU hardware) then once you have an array build according to these principles, the setRGB array method of the BufferedImage can copy your array to the BufferedImage.

这个答案是对@shawalli的回答的补充…

我想在我的罐子里引用一个图像,但不是有一个BufferedImage,我简单地这样做:

 JPanel jPanel = new JPanel();      
 jPanel.add(new JLabel(new ImageIcon(getClass().getClassLoader().getResource("resource/images/polygon.jpg"))));

下面是我怎么做的(有更多关于如何加载图像的信息):

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class ImagePanel extends JPanel{

    private BufferedImage image;

    public ImagePanel() {
       try {                
          image = ImageIO.read(new File("image name and path"));
       } catch (IOException ex) {
            // handle exception...
       }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, 0, 0, this); // see javadoc for more info on the parameters            
    }

}