题
我有一个 J面板 我想向其中添加即时生成的 JPEG 和 PNG 图像。
到目前为止我见过的所有例子 摇摆教程, ,特别是在 摇摆示例 使用 ImageIcon
s。
我将这些图像生成为字节数组,它们通常比示例中使用的常见图标(640x480)大。
- 使用 ImageIcon 类在 JPanel 中显示该大小的图像是否存在任何(性能或其他)问题?
- 什么是 通常 这样做的方法?
- 如何在不使用 ImageIcon 类的情况下将图像添加到 JPanel?
编辑: :对教程和 API 进行更仔细的检查表明您无法将 ImageIcon 直接添加到 JPanel。相反,他们通过将图像设置为 JLabel 的图标来实现相同的效果。这感觉不太对劲...
解决方案
以下是我的工作方式(有关如何加载图片的更多信息):
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,那么可能正在使用Swing。试试这个:
BufferedImage myPicture = ImageIO.read(new File("path-to-file"));
JLabel picLabel = new JLabel(new ImageIcon(myPicture));
add(picLabel);
图像现在是摆动组件。它会像任何其他组件一样受制于布局条件。
BufferedImage wPic = ImageIO.read(this.getClass().getResource("snow.png"));
JLabel wIcon = new JLabel(new ImageIcon(wPic));
由于我只需要使用此方法加载有限数量(约10个)图像,因此效果非常好。它获取文件而不必拥有正确的相对文件路径。
我认为不需要任何子类。只需使用Jlabel。您可以将图像设置为Jlabel。因此,调整Jlabel的大小,然后用图像填充它。没关系。这就是我的方式。
JLabel imgLabel = new JLabel(new ImageIcon("path_to_image.png"));
- 应该没有任何问题(除非您对非常大的图像有任何一般性问题)。
- 如果您正在谈论将多个图像添加到单个面板,我会使用
ImageIcon
s。对于单个图像,我会考虑创建JPanel
的自定义子类并重写其paintComponent
方法来绘制图像。 - (见2) 醇>
你可以创建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); }
}
JPanel
几乎总是子类的错误类。你为什么不继承JComponent
?
ImageIcon
有一个小问题,即构造函数阻止读取图像。从应用程序jar加载时不是真正的问题,但也许你可能通过网络连接阅读。即使在JDK演示中,也有很多AWT时代使用MediaTracker
,ImageObserver
和朋友的例子。
我正在做一个与我正在进行的私人项目非常相似的事情。到目前为止,我已经生成了高达1024x1024的图像,没有任何问题(内存除外),并且可以非常快速地显示它们而没有任何性能问题。
覆盖JPanel子类的paint方法是过度的,需要比你需要做的更多的工作。
我这样做的方式是:
Class MapIcon implements Icon {...}
OR
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())。
在项目目录中创建一个源文件夹,在本例中我称之为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();
这个答案是@ shawalli回答的补充......
我也希望在我的jar中引用一个图像,但是我没有使用BufferedImage,而是简单地做了这个:
JPanel jPanel = new JPanel();
jPanel.add(new JLabel(new ImageIcon(getClass().getClassLoader().getResource("resource/images/polygon.jpg"))));
您可以避免使用自己的Component
和SwingX库以及ImageIO
类:
File f = new File("hello.jpg");
JLabel imgLabel = new JLabel(new ImageIcon(file.getName()));
我可以看到很多答案,但没有真正解决OP的三个问题。
1) 关于性能的一句话:字节数组可能效率低下,除非您可以使用与显示适配器当前分辨率和颜色深度相匹配的精确像素字节顺序。
要获得最佳绘图性能,只需将图像转换为 BufferedImage,该图像是使用与当前图形配置相对应的类型生成的。请参阅 createCompatibleImage,网址为 https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html
这些图像在绘制几次后会自动缓存在显示卡内存中,无需任何编程工作(这是自 Java 6 以来 Swing 中的标准),因此实际绘制所花费的时间可以忽略不计 - 如果 你没有改变图像。
更改图像会导致主内存和 GPU 内存之间进行额外的内存传输,速度很慢。因此,避免将图像“重绘”到 BufferedImage 中,完全避免执行 getPixel 和 setPixel 操作。
例如,如果您正在开发一款游戏,与其将所有游戏角色绘制到 BufferedImage,然后绘制到 JPanel,不如将所有角色加载为较小的 BufferedImage,然后在 JPanel 代码中将它们一一绘制,速度要快得多。它们的正确位置 - 这样,除了用于缓存的图像的初始传输之外,主内存和 GPU 内存之间没有额外的数据传输。
ImageIcon 将在后台使用 BufferedImage - 但基本上分配一个 BufferedImage 恰当的 图形模式是关键,没有努力做到这一点。
2) 执行此操作的常用方法是在 JPanel 的重写 PaintComponent 方法中绘制 BufferedImage。尽管 Java 支持大量额外的功能,例如控制 GPU 内存中缓存的 VolatileImage 的缓冲链,但自从 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 循环 并复制像素。那是非常非常慢的。反而:
- 了解 BufferedImage 的首选字节结构(现在可以安全地假设 RGB 或 RGBA,即每个像素 4 个字节)
- 了解使用中的扫描线和扫描尺寸(例如您可能有一个 142 像素宽的图像 - 但在现实生活中,它将存储为 256 像素宽的字节数组,因为处理该图像并由 GPU 硬件屏蔽未使用的像素的速度更快)
- 然后,一旦您根据这些原则构建了数组,BufferedImage 的 setRGB 数组方法就可以将数组复制到 BufferedImage。