如何向 JPanel 添加图像?
我有一个 JPanel
我想添加我动态生成的 JPEG 和 PNG 图像。
到目前为止我在 Swing 教程 中看到的所有示例,特别是Swing 示例 使用 ImageIcon
s。
我将这些图像生成为字节数组,并且通常比示例中使用的常见图标(640x480)大。
- 使用
ImageIcon
类在JPanel
中显示该尺寸的图像是否存在任何(性能或其他)问题? - “通常”的做法是什么?
- 如何在不使用
ImageIcon
类的情况下将图像添加到JPanel
中?
编辑:对教程和 API 进行更仔细的检查表明您无法将 ImageIcon
直接添加到 JPanel
。 相反,它们通过将图像设置为 JLabel
的图标来实现相同的效果。 这感觉不太对劲...
I have a JPanel
to which I'd like to add JPEG and PNG images that I generate on the fly.
All the examples I've seen so far in the Swing Tutorials, specially in the Swing examples use ImageIcon
s.
I'm generating these images as byte arrays, and are usually larger than the common icons they use in the examples, at 640x480.
- Is there any (performance or other) problem in using the
ImageIcon
class to display an image that size in aJPanel
? - What's the "usual" way of doing it?
- How to add an image to a
JPanel
without using theImageIcon
class?
Edit: A more careful examination of the tutorials and the API shows that you cannot add an ImageIcon
directly to a JPanel
. Instead, they achieve the same effect by setting the image as an icon of a JLabel
. This just doesn't feel right...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
如果您正在使用 JPanel,那么可能正在使用 Swing。 试试这个:
图像现在是一个 swing 组件。 它像任何其他组件一样受到布局条件的影响。
If you are using JPanels, then are probably working with Swing. Try this:
The image is now a swing component. It becomes subject to layout conditions like any other component.
这是我的做法(包含有关如何加载图像的更多信息):
Here's how I do it (with a little more info on how to load an image):
弗雷德·哈斯拉姆的方法效果很好。 不过,我在文件路径方面遇到了麻烦,因为我想引用我的 jar 中的图像。 为此,我使用了:
由于我只需要使用此方法加载有限数量(大约 10 张)图像,因此它效果很好。 它无需具有正确的相对文件路径即可获取文件。
Fred Haslam's way works fine. I had trouble with the filepath though, since I want to reference an image within my jar. To do this, I used:
Since I only have a finite number (about 10) images that I need to load using this method, it works quite well. It gets file without having to have the correct relative filepath.
我认为没有必要对任何东西进行子类化。 只需使用 Jlabel 即可。 您可以将图像设置到 Jlabel 中。 因此,调整 Jlabel 的大小,然后用图像填充它。 没关系。 我就是这样做的。
I think there is no need to subclass of anything. Just use a Jlabel. You can set an image into a Jlabel. So, resize the Jlabel then fill it with an image. Its OK. This is the way I do.
您可以通过使用免费 SwingX 库中的 JXImagePanel 类来完全避免滚动自己的 Component 子类。
下载
You can avoid rolling your own Component subclass completely by using the JXImagePanel class from the free SwingX libraries.
Download
您可以对 JPanel 进行子类化 - 这是我的 ImagePanel 的摘录,它将图像放置在 5 个位置中的任意一个位置:上/左、上/右、中/中、下/左或下/右:
You can subclass JPanel - here is an extract from my ImagePanel, which puts an image in any one of 5 locations, top/left, top/right, middle/middle, bottom/left or bottom/right:
ImageIcon
。 对于单个图像,我会考虑创建JPanel
的自定义子类并重写其paintComponent
方法来绘制图像。ImageIcon
s. For a single image, I would think about making a custom subclass ofJPanel
and overriding itspaintComponent
method to draw the image.JPanel
几乎总是错误的子类。 为什么不继承JComponent
呢?ImageIcon
有一个小问题,构造函数会阻止读取图像。 从应用程序 jar 加载时这并不是真正的问题,但如果您可能通过网络连接进行读取,则可能会出现问题。 有大量使用MediaTracker
、ImageObserver
等 AWT 时代的示例,甚至在 JDK 演示中也是如此。JPanel
is almost always the wrong class to subclass. Why wouldn't you subclassJComponent
?There is a slight problem with
ImageIcon
in that the constructor blocks reading the image. Not really a problem when loading from the application jar, but maybe if you're potentially reading over a network connection. There's plenty of AWT-era examples of usingMediaTracker
,ImageObserver
and friends, even in the JDK demos.我正在从事的一个私人项目中做了一些非常类似的事情。 到目前为止,我已经生成了高达 1024x1024 的图像,没有任何问题(内存除外),并且可以非常快速地显示它们,并且没有任何性能问题。
重写 JPanel 子类的 Paint 方法是多余的,并且需要做的工作比您需要做的更多。
我这样做的方法是:
或者
您用来生成图像的代码将在此类中。 我使用 BufferedImage 进行绘制,然后当调用 PaintIcon() 时,使用 g.drawImvge(bufferedImage); 这减少了生成图像时完成的闪烁量,并且您可以对其进行线程化。
接下来我扩展 JLabel:
这是因为我想将图像放在滚动窗格上,即显示图像的一部分并让用户根据需要滚动。
然后我使用 JScrollPane 来保存 MapLabel,其中仅包含 MapIcon。
但对于你的场景(每次只显示整个图像)。 您需要将 MapLabel 添加到顶部 JPanel,并确保将它们全部调整为图像的完整大小(通过覆盖 GetPreferredSize())。
I'm doing something very similar in a private project I'm working on. Thus far I've generated images up to 1024x1024 without any problems (except memory) and can display them very quickly and without any performance problems.
Overriding the paint method of JPanel subclass is overkill and requires more work than you need to do.
The way I do it is:
OR
The code you use to generate the image will be in this class. I use a BufferedImage to draw onto then when the paintIcon() is called, use g.drawImvge(bufferedImage); This reduces the amount of flashing done while you generate your images, and you can thread it.
Next I extend JLabel:
This is because I want to put my image on a scroll pane, I.e. display part of the image and have the user scroll around as needed.
So then I use a JScrollPane to hold the MapLabel, which contains only the MapIcon.
But for your scenario (just show the whole image every time). You need to add the MapLabel to the top JPanel, and make sure to size them all to the full size of the image (by overriding the GetPreferredSize()).
这个答案是对 @shawalli 答案的补充...
我也想引用我的 jar 中的图像,但我没有使用 BufferedImage,而是简单地这样做了:
This answer is a complement to @shawalli's answer...
I wanted to reference an image within my jar too, but instead of having a BufferedImage, I simple did this:
在项目目录中创建一个源文件夹,在本例中我将其称为“Images”。
Create a source folder in your project directory, in this case I called it Images.
您可以避免使用自己的 Component 和 SwingX 库以及 ImageIO 类:
You can avoid using own
Component
s and SwingX library andImageIO
class:我可以看到很多答案,但没有真正解决OP的三个问题。
1) 关于性能的一句话:字节数组可能效率低下,除非您可以使用与显示适配器当前分辨率和颜色深度相匹配的精确像素字节顺序。
要获得最佳绘图性能,只需将图像转换为 BufferedImage,该图像是使用与当前图形配置相对应的类型生成的。 请参阅 https://docs.oracle.com/javase/tutorial 处的 createCompatibleImage /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 以来就没有必要使用其中任何一个,Java 6 在不暴露 GPU 加速的所有这些细节的情况下做得相当不错。
请注意,GPU 加速可能不适用于某些操作,例如拉伸半透明图像。
3) 不要添加。 只需按照上面提到的方式绘制它:
如果图像是布局的一部分,“添加”就有意义。 如果您需要将其作为填充 JPanel 的背景或前景图像,只需在 PaintComponent 中绘制即可。 如果您更喜欢构建一个可以显示图像的通用 Swing 组件,那么情况也是一样(您可以使用 JComponent 并重写其 PaintComponent 方法) - 然后将 this 添加到 GUI 组件的布局中。
4) 如何将数组转换为 Bufferedimage
将字节数组转换为 PNG,然后加载它是相当资源密集型的。 更好的方法是将现有的字节数组转换为 BufferedImage。
为此:不要使用 for 循环并复制像素。 那是非常非常慢的。 相反:
I can see many answers, not really addressing the three questions of the OP.
1) A word on performance: byte arrays are likely unefficient unless you can use an exact pixel byte ordering which matches to your display adapters current resolution and color depth.
To achieve the best drawing performance, simply convert your image to a BufferedImage which is generated with a type corresponding to your current graphics configuration. See createCompatibleImage at https://docs.oracle.com/javase/tutorial/2d/images/drawonimage.html
These images will be automatically cached on the display card memory after drawing a few times without any programming effort (this is standard in Swing since Java 6), and therefore the actual drawing will take negligible amount of time - if you did not change the image.
Altering the image will come with an additional memory transfer between main memory and GPU memory - which is slow. Avoid "redrawing" the image into a BufferedImage therefore, avoid doing getPixel and setPixel at all means.
For example, if you are developing a game, instead of drawing all the game actors to a BufferedImage and then to a JPanel, it is a lot faster to load all actors as smaller BufferedImages, and draw them one by one in your JPanel code at their proper position - this way there is no additional data transfer between the main memory and GPU memory except of the initial transfer of the images for caching.
ImageIcon will use a BufferedImage under the hood - but basically allocating a BufferedImage with the proper graphics mode is the key, and there is no effort to do this right.
2) The usual way of doing this is to draw a BufferedImage in an overridden paintComponent method of the JPanel. Although Java supports a good amount of additional goodies such as buffer chains controlling VolatileImages cached in the GPU memory, there is no need to use any of these since Java 6 which does a reasonably good job without exposing all of these details of GPU acceleration.
Note that GPU acceleration may not work for certain operations, such as stretching translucent images.
3) Do not add. Just paint it as mentioned above:
"Adding" makes sense if the image is part of the layout. If you need this as a background or foreground image filling the JPanel, just draw in paintComponent. If you prefer brewing a generic Swing component which can show your image, then it is the same story (you may use a JComponent and override its paintComponent method) - and then add this to your layout of GUI components.
4) How to convert the array to a Bufferedimage
Converting your byte arrays to PNG, then loading it is quite resource intensive. A better way is to convert your existing byte array to a BufferedImage.
For that: do not use for loops and copy pixels. That is very very slow. Instead: