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

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

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

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

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


你可以子类化JPanel -这里是我的ImagePanel的一个提取,它把一个图像放在5个位置中的任何一个,上/左,上/右,中/中,下/左或下/右:

protected void paintComponent(Graphics gc) {
    super.paintComponent(gc);

    Dimension                           cs=getSize();                           // component size

    gc=gc.create();
    gc.clipRect(insets.left,insets.top,(cs.width-insets.left-insets.right),(cs.height-insets.top-insets.bottom));
    if(mmImage!=null) { gc.drawImage(mmImage,(((cs.width-mmSize.width)/2)       +mmHrzShift),(((cs.height-mmSize.height)/2)        +mmVrtShift),null); }
    if(tlImage!=null) { gc.drawImage(tlImage,(insets.left                       +tlHrzShift),(insets.top                           +tlVrtShift),null); }
    if(trImage!=null) { gc.drawImage(trImage,(cs.width-insets.right-trSize.width+trHrzShift),(insets.top                           +trVrtShift),null); }
    if(blImage!=null) { gc.drawImage(blImage,(insets.left                       +blHrzShift),(cs.height-insets.bottom-blSize.height+blVrtShift),null); }
    if(brImage!=null) { gc.drawImage(brImage,(cs.width-insets.right-brSize.width+brHrzShift),(cs.height-insets.bottom-brSize.height+brVrtShift),null); }
    }

不应该有任何问题(除了使用非常大的图像时可能遇到的任何一般性问题)。 如果您正在讨论将多个图像添加到单个面板,我会使用ImageIcons。对于单个图像,我会考虑创建JPanel的自定义子类,并覆盖其paintComponent方法来绘制图像。 (见2)


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

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            
    }

}

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

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


我在自己的一个私人项目中也在做类似的事情。到目前为止,我已经生成了高达1024x1024的图像,没有任何问题(除了内存),并且可以非常快速地显示它们,没有任何性能问题。

重写JPanel子类的paint方法是多余的,并且需要比您需要做的更多的工作。

我的做法是:

Class MapIcon implements Icon {...}

OR

Class MapIcon extends ImageIcon {...}

用于生成图像的代码将在该类中。我使用BufferedImage来绘制然后当paintIcon()被调用时,使用g.drawImvge(BufferedImage);这样可以减少生成图像时的闪烁次数,并且可以使用线程。

接下来我扩展JLabel:

Class MapLabel extends Scrollable, MouseMotionListener {...}

这是因为我想把我的图像放在滚动窗格上,即显示图像的一部分,并让用户根据需要滚动。

因此,我使用JScrollPane来保存只包含MapIcon的MapLabel。

MapIcon map = new MapIcon (); 
MapLabel mapLabel = new MapLabel (map);
JScrollPane scrollPane = new JScrollPane();

scrollPane.getViewport ().add (mapLabel);

但对于你的场景(每次只显示整个图像)。您需要将MapLabel添加到顶部JPanel,并确保将它们全部设置为图像的完整大小(通过覆盖GetPreferredSize())。


通过使用来自免费SwingX库的JXImagePanel类,可以避免完全滚动自己的Component子类。

下载


我认为没有必要为任何东西创建子类。只需使用Jlabel。您可以将图像设置为Jlabel。因此,调整Jlabel的大小,然后用图像填充它。它的好。我就是这么做的。


如果您正在使用jpanel,那么您可能正在使用Swing。试试这个:

BufferedImage myPicture = ImageIO.read(new File("path-to-file"));
JLabel picLabel = new JLabel(new ImageIcon(myPicture));
add(picLabel);

图像现在是一个摆动组件。它像其他组件一样受到布局条件的制约。


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

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

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


JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));

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

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

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

在项目目录中创建一个源文件夹,在本例中我称之为Images。

JFrame snakeFrame = new JFrame();
snakeFrame.setBounds(100, 200, 800, 800);
snakeFrame.setVisible(true);
snakeFrame.add(new JLabel(new ImageIcon("Images/Snake.png")));
snakeFrame.pack();

你可以避免使用自己的组件和SwingX库和ImageIO类:

File f = new File("hello.jpg");
JLabel imgLabel = new JLabel(new ImageIcon(file.getName()));

我可以看到很多答案,但并没有真正解决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.