在Java中处理图像时如何提高性能

3

我正在编写一个程序,其中一项任务是将一个包含大约2000个jpeg图像的文件夹进行缩放,并将它们添加到时间轴上。最终效果如下图所示:

enter image description here

这个程序能够正常工作,但是我感觉实现方式非常低效。下面是用于处理这些图片的代码:

public void setTimeline(Vector<String> imagePaths){
    
    int numberOfImages = imagePaths.size();     
    
    JLabel [] TotalImages = new JLabel[numberOfImages];
    setGridPanel.setLayout(new GridLayout(1, numberOfImages, 10, 0));
    
    Dimension image = new Dimension(96, 72);

    if (imagePaths != null){
        for(int i = 0; i <numberOfImages; i++){
            TotalImages[i] = new JLabel("");
            TotalImages[i].setPreferredSize(image);
            
            ImageIcon tempicon = new ImageIcon(imagePaths.elementAt(i));
            Image tempimage = tempicon.getImage();
            
            Image newimg = tempimage.getScaledInstance(96, 72,  java.awt.Image.SCALE_SMOOTH);
            ImageIcon newIcon = new ImageIcon(newimg);
            TotalImages[i].setIcon(newIcon);

            setGridPanel.add(TotalImages[i]);
        }
    }
}

如您所见,这段代码循环遍历每个图像路径,将其添加到标签并添加到面板中 - 输出与预期完全一致。
然而,执行时间相当长。对于2000个图片,通常需要大约5分钟的时间(取决于计算机)。我想知道是否有任何方法可以通过使用不同的技术来提高性能?
非常感谢您的帮助。

1
我会说在5分钟内生成2000个缩略图是非常不错的时间。在Windows中打开一个图像文件夹并等待Windows索引和生成缩略图,速度远没有这么快。 - Tschallacka
1
请参考这个答案,其中引用了Image.getScaledInstance()的危险 - trashgod
6个回答

4

保存您的缩放实例并直接加载它们。硬盘空间很便宜。这不会解决生成缩略图的初始成本,但任何后续出现将非常快速。


3
批量处理图片文件夹,使用 tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH); 进行处理。
  • 可以使用缩减功能的 JTable 或者 JList

一般处理 2000 张图片需要大约 5 分钟时间。

Image.getScaledInstance 是简单的异步操作,不能保证快速和高效,所以必须将图片加载重定向到后台任务。
  • 优点是第一部分图片立即可用。

  • 缺点是必须为用户显示加载状态,并且需要很好地了解 SwingEvent Dispatch Thread

建议查看 Runnable#Thread,并将输出放入 DefaultTableModel 中,注意此输出必须包装在 invokeLater 中。
另一种更复杂的方法是使用 SwingWorker,但这也需要非常好的关于 JavaSwing 的知识。

3

在mKorbel的优秀回答上,我一定会使用后台线程,比如SwingWorker。这可能不会使程序更快,但它会看起来更快,而这可以产生重大影响。可以像下面这样进行:

// use List<String> not Vector<String> so you can use Vector now, or change your 
// mind and use ArrayList later if desired
// pass dimensions and components in as parameters to be cleaner
public void setTimeLine2(List<String> imagePaths, Dimension imgSize,
     JComponent imgDisplayer) {

  if (imagePaths != null && imgSize != null && imgDisplayer != null) {

     // are you sure you want to set the layout in here?
     imgDisplayer.setLayout(new GridLayout(1, 0, 10, 0));

     // create your SwingWorker, passing in parameters that it will need
     ImageWorker imgWorker = new ImageWorker(imagePaths, imgSize,
           imgDisplayer);
     imgWorker.execute(); // then ask it to run doInBackground on a background thread
  } else {
     // TODO: throw exception
  }
}

private class ImageWorker extends SwingWorker<Void, ImageIcon> {
  private List<String> imagePaths;
  private JComponent imgDisplayer;
  private int imgWidth;
  private int imgHeight;

  public ImageWorker(List<String> imagePaths, Dimension imgSize,
        JComponent imgDisplayer) {
     this.imagePaths = imagePaths;
     this.imgDisplayer = imgDisplayer;
     imgWidth = imgSize.width;
     imgHeight = imgSize.height;
  }

  // do image creation in a background thread so as not to lock the Swing event thread
  @Override
  protected Void doInBackground() throws Exception {
     for (String imagePath : imagePaths) {
        BufferedImage bImg = ImageIO.read(new File(imagePath));
        Image scaledImg = bImg.getScaledInstance(imgWidth, imgHeight,
              Image.SCALE_SMOOTH);
        ImageIcon icon = new ImageIcon(scaledImg);
        publish(icon);
     }
     return null;
  }

  // but do all Swing manipulation on the event thread
  @Override
  protected void process(List<ImageIcon> chunks) {
     for (ImageIcon icon : chunks) {
        JLabel label = new JLabel(icon);
        imgDisplayer.add(label);
     }
  }
}

2
使用瓦片。这意味着,你只在图像需要显示在屏幕上时进行操作,而不是处理不显示在屏幕上的图像。
你需要维护时间线的逻辑位置以及显示的图像。当用户将光标移动到先前隐藏的位置时,你计算哪些图像需要下一步显示。如果图像尚未处理,则进行处理。这是 web 浏览器用于提高性能的相同技术。

1
  • 首先,您可以异步添加图像,而不是尝试一次性添加所有图像。像您所做的那样循环遍历它们,将它们添加到面板并每隔几个图像渲染一次,这样用户就不需要等待长时间的初始化时间。

  • 重复使用图像对象。飞行权模式可能会出现,并且可能仅限于在异步加载中添加新图像的部分重新绘制屏幕。

  • 如果您将来可能会重新绘制相同的图像(或重新加载相同的文件夹),则可能要考虑缓存一些图像对象,并且可能要保存调整大小的缩略图(许多照片查看器都会执行此操作,并将缩略图版本和一些有用的元数据存储在隐藏文件或文件夹中,以便下次更快地重新加载它们。


-1
你可以通过创建4个线程并让它们同时计算图像来提高速度。不过我不确定虚拟机是否会将它们分配到多个CPU核心上。这是需要研究的问题,因为这样可以在多核PC上提高性能。

如果他有8000张图片,同样的问题。 - UmNyobe
没错。我不会一次性加载2000或8000张图片。我会计算前100个最高优先级,然后将其推到后台进程上,在磁盘上生成缩略图,或者窃取Windows的索引缩略图进行显示。我给出这个答案的基础是他有充分的理由用2000张图片占用宝贵的内存。 - Tschallacka
我不知道如何使用线程来完成这个任务,因为这些图像的处理需要按顺序进行,以便将所有图像按顺序添加到时间轴面板中。是否可以在提高性能的同时使用线程来完成这项任务? - user1224534

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接