如何将图像添加到JPanel?

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

到目前为止,我在Swing教程中看到的所有例子,特别是在摇摆不定的例子中,都使用了__abc0。

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

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

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

1124430 次浏览

你可以子类化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); }
}
  1. 不应该有任何问题(除了使用非常大的图像时可能遇到的任何一般性问题)。
  2. 如果你要在一个面板上添加多张图片,我会使用ImageIcons。对于单个图像,我会考虑创建JPanel的自定义子类,并覆盖其paintComponent方法来绘制图像。
  3. (见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时代有很多使用MediaTrackerImageObserver等的例子,甚至在JDK演示中也是如此。

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

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

我的做法是:

Class MapIcon implements Icon {...}

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();

你可以避免使用自己的Components和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与适当的图形模式是关键,没有努力做到这一点。

通常的方法是在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组件布局中。

如何将数组转换为buffereimage

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

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

  • 学习BufferedImage的首选字节结构(现在假设RGB或RGBA是安全的,每像素4个字节)
  • 学习使用中的扫描线和扫描大小(例如,你可能有一个142像素宽的图像-但在现实生活中,它将存储为256像素宽的字节数组,因为它可以更快地处理,并由GPU硬件屏蔽未使用的像素)
  • 那么一旦你根据这些原则构建了一个数组,BufferedImage的setRGB数组方法可以将你的数组复制到BufferedImage。