如何将图像添加到JPanel?


341

我有一个JPanel,我想向其中添加即时生成的JPEG和PNG图像。

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

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

  1. 使用ImageIcon类在JPanel中显示该大小的图像时是否存在任何(性能或其他)问题?
  2. 什么是平常做的呢?
  3. 如何不使用ImageIcon类将图像添加到JPanel?

编辑:对教程和API的更仔细的检查显示,您不能将ImageIcon直接添加到JPanel。而是通过将图像设置为JLabel的图标来实现相同的效果。只是感觉不对...


1
根据生成字节数组的方式,使用a可能MemoryImageSource比将它们转换为JPEG或PNG格式然后以ImageIO大多数答案所建议的方式读取更为有效。您可以使用来ImageMemoryImageSource包含您的图像数据的结构中获取createImage,并按照答案之一的建议进行显示。
Alden 2015年

Answers:


252

这是我的操作方法(有关如何加载图像的更多信息):

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            
    }

}

17
-1用于paintComponent的无效实现(@Dogmatixed最有可能是您遇到这些重画问题的原因)- 如果它报告为不透明(默认值),则必须保证覆盖其整个区域,最简单的方法是调用super.paintComponent
kleopatra 2012年

5
@kleopatra,谢谢,我没有意识到……根据javadoc:“此外,如果您不调用super的实现,则必须使用opaque属性,也就是说,如果该组件是不透明的,则必须完全填写不透明颜色的背景。如果您不尊重不透明属性,则可能会看到视觉伪像。” 我现在将更新答案。
布伦丹·卡什曼

8
请永远尊重Principle of Encapsulation父类的同时覆盖方法,在的访问说明符paintComponent(...)方法protected,而不是public:-)
尼斯牛

1
这不好,它对将面板调整为图像大小没有任何作用。稍后必须添加更多代码来处理此问题。使用JLabel答案要简单得多。
Keilly,2015年

2
@Jonathan:关于的某些消息来源,我真的很抱歉Principle of Encapsulation。只需记住,在编程时,应该以这种方式维护应该隐藏在其余代码中的内容,而不是对代码中的所有内容都可见。就像每天学习一样,它是一种伴随经验的东西。任何书籍都可以给出一个基本的概念,即封装是什么,但是正是我们如何实现代码决定了我们对这一概念的坚持程度。
尼斯牛

520

如果使用的是JPanels,则可能正在使用Swing。尝试这个:

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

该图像现在是一个摆动组件。与其他组件一样,它也会受到布局条件的约束。


16
如何根据JLabel的大小缩放图像?
2011年

1
好的代码!我对Swing经验不足,但是无法正常工作。有人在jdk 1.6.0_16中尝试过吗?
ATorras

3
@ATorras我知道您前不久曾问过这个问题,但是如果有其他新手遇到我的问题,请记住要picLabel.setBounds();。
凯尔2014年

每次重新加载照片时都必须创建一个新的JLabel对象吗?
0x6B6F77616C74 2015年

2
@coding_idiot new JLabel(new ImageIcon(backgroundImage.getScaledInstance(width,height,Image.SCALE_FAST)));;
戴维德(Davide)

37

Fred Haslam的方法很好用。但是我在文件路径上遇到了麻烦,因为我想在jar中引用一个图像。为此,我使用了:

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

由于我只有有限数量(约10张)的图像需要使用此方法加载,因此效果很好。它获取文件而不必具有正确的相对文件路径。


1
我将第一行替换BufferedImage previewImage = ImageIO.read(new URL(imageURL));为从服务器获取我的图像。
intcreator 2015年

要从jar中引用图像,请尝试以下stackoverflow.com/a/44844922/3211801
Nandha

33

我认为没有必要继承任何东西。只需使用Jlabel。您可以将图像设置为Jlabel。因此,调整Jlabel的大小,然后用图像填充它。没关系。这就是我的方法。



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

8

您可以将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); }
    }

8
  1. 不应有任何问题(对于非常大的图像,可能会遇到任何一般性问题)。
  2. 如果您正在谈论将多个图像添加到一个面板中,则可以使用ImageIcons。对于单个图像,我会考虑创建一个自定义子类,JPanel并重写其paintComponent绘制图像的方法。
  3. (请参阅2)

6

JPanel几乎总是错误的类来子类化。你为什么不继承JComponent

有一个小问题,ImageIcon那就是构造函数阻止读取图像。从应用程序jar加载时并不是真正的问题,但是如果您可能正在通过网络连接进行读取。有大量的使用AWT时代的例子MediaTrackerImageObserver和朋友,甚至在JDK演示。


6

我正在从事的私人项目中做的事情非常相似。到目前为止,我已经生成了高达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())。


5

在您的项目目录中创建一个源文件夹,在这种情况下,我将其称为“图像”。

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

4

此答案是对@shawalli答案的补充...

我也想在jar中引用一个图像,但是我没有做BufferedImage,而是简单地做到了:

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

4

您可以避免使用自己Component的和SwingX库和ImageIO类:

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

1

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

1)关于性能的一个字眼:字节阵列可能效率不高,除非您可以使用与显示适配器当前分辨率和颜色深度相匹配的精确像素字节顺序。

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

绘制几次后,这些图像将自动缓存在显示卡的内存中,而无需进行任何编程工作(这是Java 6之后在Swing中的标准操作),因此,实际的绘制将花费很少的时间- 如果您不更改图像。

更改图像会在主内存和GPU内存之间进行额外的内存传输-这很慢。避免将图像“重绘”到BufferedImage中,因此,绝对不要执行getPixel和setPixel。

例如,如果您正在开发游戏,而不是将所有游戏角色都先绘制到BufferedImage然后再绘制到JPanel,则将所有角色加载为较小的BufferedImages并在JPanel代码中逐个绘制它们的速度要快得多。它们的正确位置-这样,除了用于缓存的图像的初始传输外,在主内存和GPU内存之间没有其他数据传输。

ImageIcon将在幕后使用BufferedImage-但是从根本上分配具有适当图形模式的BufferedImage 是关键,并且无需为此做任何努力。

2)这样做的通常方法是在JPanel的重写paintComponent方法中绘制BufferedImage。尽管Java支持大量其他功能,例如控制在GPU内存中缓存的VolatileImages的缓冲区链,但由于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。

为此:请勿用于循环和复制像素。那非常非常慢。代替:

  • 了解BufferedImage的首选字节结构(现在可以安全地假设RGB或RGBA,每个像素4个字节)
  • 了解使用中的扫描线和scansize(例如,您可能具有142像素宽的图像-但在现实生活中将以256像素宽的字节数组存储,因为这样可以更快地处理该图像并通过GPU硬件掩盖未使用的像素)
  • 然后,一旦根据这些原理构建了数组,则BufferedImage的setRGB array方法可以将您的数组复制到BufferedImage中。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.