【问题标题】:How to add an image to a JPanel?如何将图像添加到 JPanel?
【发布时间】:2010-09-22 21:43:52
【问题描述】:

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

到目前为止我在Swing Tutorials 中看到的所有示例,特别是在Swing examples 中使用ImageIcons。

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

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

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

【问题讨论】:

  • 根据您生成字节数组的方式,使用MemoryImageSource 可能比将它们转换为JPEG 或PNG 格式然后使用ImageIO 读取更有效,正如大多数答案所建议的那样。您可以使用createImage 从使用您的图像数据构造的MemoryImageSource 中获取Image,并按照其中一个答案的建议显示。

标签: java image swing jpanel


【解决方案1】:

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

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

图像现在是一个摆动组件。它会像任何其他组件一样受到布局条件的影响。

【讨论】:

  • 如何根据JLabel的大小缩放图片?
  • 不错的代码!我对 Swing 没有多少经验,但我无法让它发挥作用。有人在 jdk 1.6.0_16 中尝试过吗?
  • @ATorras 我知道你之前问过这个问题,但如果其他新手有我的问题,请记住 picLabel.setBounds();
  • @coding_idiot new JLabel(new ImageIcon(backgroundImage.getScaledInstance(width, height, Image.SCALE_FAST)));
  • @ATorras 你问这个问题已经有一段时间了。但也许这会在未来帮助其他人。它对我也不起作用。我使用了 .ico 图像。然后我尝试了一个 .png 图像,它对我有用。 :)
【解决方案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            
    }

}

【讨论】:

  • -1 表示paintComponent 的无效实现(@Dogmatixed 很可能这就是您遇到这些重绘问题的原因) - 如果报告不透明,它必须保证覆盖其整个区域(这是默认设置),最简单的方法是调用 super.paintComponent
  • @kleopatra,谢谢,我没有意识到......根据 javadoc:“此外,如果你不调用 super 的实现,你必须尊重 opaque 属性,也就是说,如果这个组件是不透明,您必须以非透明颜色完全填充背景。如果您不遵守不透明属性,您可能会看到视觉伪影。”我现在会更新答案。
  • 在重写超类的方法时请始终尊重Principle of EncapsulationpaintComponent(...) 方法的访问说明符是protected 而不是public :-)
  • 这不好,它对调整面板大小以适应图像没有任何作用。稍后将不得不添加更多代码来处理这个问题。使用 JLabel 答案要简单得多。
  • @Jonathan:关于Principle of Encapsulation 的一些来源,我真的很抱歉。请记住,在编程时,应该对其余代码隐藏的内容需要以这种方式进行维护,而不是对代码中的所有内容都可见。就像一个人每天都在学习的那样,它似乎是一种伴随着经验而来的东西。任何一本书都可以给出封装是什么的基本概念,但我们如何实现代码决定了我们对这个概念的坚持程度。
【解决方案3】:

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

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

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

【讨论】:

  • 我将第一行替换为 BufferedImage previewImage = ImageIO.read(new URL(imageURL)); 以从服务器获取我的图像。
  • 要从 jar 中引用图片,试试这个stackoverflow.com/a/44844922/3211801
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    您可以使用免费的SwingX 库中的JXImagePanel 类来完全避免滚动您自己的组件子类。

    Download

    【讨论】:

      【解决方案6】:
      JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));
      

      【讨论】:

        【解决方案7】:

        您可以继承 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)

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

              【解决方案10】:

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

              重写 JPanel 子类的 paint 方法太过分了,而且需要做的工作比你需要做的要多。

              我的做法是:

              Class MapIcon implements Icon {...}
              

              Class MapIcon extends ImageIcon {...}
              

              您用于生成图像的代码将在此类中。我使用 BufferedImage 进行绘制,然后在调用 paintIcon() 时,使用 g.drawImvge(bufferedImage);这减少了生成图像时完成的闪烁量,并且您可以将其线程化。

              接下来我扩展 JLabel:

              Class MapLabel extends Scrollable, MouseMotionListener {...}
              

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

              然后我使用 JScrollPane 来保存 MapLabel,它只包含 MapIcon。

              MapIcon map = new MapIcon (); 
              MapLabel mapLabel = new MapLabel (map);
              JScrollPane scrollPane = new JScrollPane();
              
              scrollPane.getViewport ().add (mapLabel);
              

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

              【讨论】:

                【解决方案11】:

                这个答案是对@shawalli 答案的补充......

                我也想在我的 jar 中引用一个图像,但我没有使用 BufferedImage,而是这样做了:

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

                【讨论】:

                  【解决方案12】:

                  在您的项目目录中创建一个源文件夹,在本例中我将其命名为 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();
                  

                  【讨论】:

                    【解决方案13】:

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

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

                    【讨论】:

                      【解决方案14】:

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

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

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

                      这些图像在绘制几次后会自动缓存在显卡内存中,无需任何编程工作(这是自 Java 6 以来 Swing 中的标准),因此实际绘制所需的时间可以忽略不计 - 如果 你没有改变图像。

                      更改图像会带来主内存和 GPU 内存之间的额外内存传输 - 这很慢。因此,避免将图像“重绘”到 BufferedImage 中,尽量避免执行 getPixel 和 setPixel 。

                      例如,如果您正在开发一个游戏,而不是将所有游戏 actor 绘制到 BufferedImage 然后再绘制到 JPanel,将所有 actor 加载为较小的 BufferedImage 并在您的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 方法) - 然后将 this 添加到您的 GUI 组件布局中.

                      4)如何将数组转换为Bufferedimage

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

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

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

                      【讨论】:

                        猜你喜欢
                        • 2017-09-07
                        • 2012-07-12
                        • 2013-08-25
                        • 2016-02-15
                        • 1970-01-01
                        • 2018-09-02
                        相关资源
                        最近更新 更多