让JFileChooser显示图像缩略图

8
我想创建一个带有图像文件缩略图视图的JFileChooser。所以我子类化了FileView,并在创建ImageIcon的方法中进行了一些缩放,以便显示缩略图。

然而,总体效果是,filechooser小部件在打开目录并显示缩略图之前需要一些时间。在下面的createImageIcon()中,我需要调用两次new ImageIcon(),一次使用图像文件路径,下一次使用调整大小后的图像作为构造函数参数。我认为这会减慢小部件的速度。

是否有更有效的替代方案?欢迎任何建议/指针。

谢谢, 马克

public static void main(String[] args) { 
    JFileChooser chooser=new JFileChooser();
    ThumbNailView thumbView=new ThumbNailView();
    chooser.setFileView(thumbView);
  }

class ThumbNailView extends FileView{
 public Icon getIcon(File f){
  Icon icon=null;
  if(isImageFile(f.getPath())){
   icon=createImageIcon(f.getPath(),null);
  }
  return icon;
 }
 private ImageIcon createImageIcon(String path,String description) {
  if (path != null) {
   ImageIcon icon=new ImageIcon(path);
   Image img = icon.getImage() ; 
   Image newimg = img.getScaledInstance( 16, 16,  java.awt.Image.SCALE_SMOOTH ) ;
   return new ImageIcon(newimg);
  } else {
   System.err.println("Couldn't find file: " + path);
   return null;
   }
}

private boolean isImageFile(String filename){
    //return true if this is image
}
3个回答

11

使用原生的 Windows 外观,我惊讶地发现文件选择器实际上没有缩略图视图。我尝试了你的例子,你的方向是对的,但我发现在有很多大型图像的文件夹中速度很慢。这当然是由于读取文件内容和解释图像时的 I/O 导致的开销,这是不可避免的。

更糟糕的是,我发现在显示文件列表之前、鼠标悬停在图标上时以及选择更改时会频繁调用 FileView.getIcon(File)。如果我们不在加载后缓存图像,那么我们将一直无意义地重新加载图像。

显而易见的解决方案是将所有图像加载推迟到另一个线程或线程池中,并一旦得到缩小后的结果,就将其放入临时缓存中,以便可以再次检索它们。

我对 ImageImageIcon 进行了大量尝试,发现 ImageIcon 的图像可以随时通过调用 setImage(Image) 更改。这对我们来说意味着,在 getIcon(File) 中,我们可以立即返回空或默认图标,但保留对它的引用,并将其传递给工作线程,在后台加载图像并在完成时设置图标的图像(唯一需要注意的是,我们必须调用 repaint() 才能看到更改)。

对于此示例,我正在使用一个 ExecutorService 缓存线程池(这是获取所有图像的最快方法,但使用了大量 I/O)来处理图像加载任务。我还使用了 WeakHashMap 作为缓存,以确保我们只保留所需时间的缓存图标。您可以使用另一种类型的 Map,但您必须管理所持有的图标数量,以避免内存不足。

package guitest;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.filechooser.FileView;

public class ThumbnailFileChooser extends JFileChooser {

    /** All preview icons will be this width and height */
    private static final int ICON_SIZE = 16;

    /** This blank icon will be used while previews are loading */
    private static final Image LOADING_IMAGE = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB);

    /** Edit this to determine what file types will be previewed. */
    private final Pattern imageFilePattern = Pattern.compile(".+?\\.(png|jpe?g|gif|tiff?)$", Pattern.CASE_INSENSITIVE);

    /** Use a weak hash map to cache images until the next garbage collection (saves memory) */
    private final Map imageCache = new WeakHashMap();

    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        JFileChooser chooser = new ThumbnailFileChooser();
        chooser.showOpenDialog(null);
        System.exit(1);
    }

    public ThumbnailFileChooser() {
        super();
    }

    // --- Override the other constructors as needed ---

    {
        // This initializer block is always executed after any constructor call.
        setFileView(new ThumbnailView());
    }

    private class ThumbnailView extends FileView {
        /** This thread pool is where the thumnnail icon loaders run */
        private final ExecutorService executor = Executors.newCachedThreadPool();

        public Icon getIcon(File file) {
            if (!imageFilePattern.matcher(file.getName()).matches()) {
                return null;
            }

            // Our cache makes browsing back and forth lightning-fast! :D
            synchronized (imageCache) {
                ImageIcon icon = imageCache.get(file);

                if (icon == null) {
                    // Create a new icon with the default image
                    icon = new ImageIcon(LOADING_IMAGE);

                    // Add to the cache
                    imageCache.put(file, icon);

                    // Submit a new task to load the image and update the icon
                    executor.submit(new ThumbnailIconLoader(icon, file));
                }

                return icon;
            }
        }
    }

    private class ThumbnailIconLoader implements Runnable {
        private final ImageIcon icon;
        private final File file;

        public ThumbnailIconLoader(ImageIcon i, File f) {
            icon = i;
            file = f;
        }

        public void run() {
            System.out.println("Loading image: " + file);

            // Load and scale the image down, then replace the icon's old image with the new one.
            ImageIcon newIcon = new ImageIcon(file.getAbsolutePath());
            Image img = newIcon.getImage().getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH);
            icon.setImage(img);

            // Repaint the dialog so we see the new icon.
            SwingUtilities.invokeLater(new Runnable() {public void run() {repaint();}});
        }
    }

}

已知问题:

1) 当缩放图片时,我们不保持图像的长宽比。 这样做可能会导致图标具有奇怪的尺寸,从而破坏列表视图的对齐。 解决方法可能是创建一个新的16x16 BufferedImage,并在其上绘制缩放后的图像,在中心位置上进行。 如果您愿意,您可以实现这个解决方法!

2) 如果一个文件不是图像,或者损坏了,将根本不会显示任何图标。 程序似乎只在渲染图像时检测到此错误,而不是在加载或缩放图像时检测到,因此我们无法提前检测到这个错误。 但是,如果我们解决第一个问题,可能会检测到它。


6

使用 fileDialog 替代 JfileChooser 选择图像:

FileDialog fd = new FileDialog(frame, "Test", FileDialog.LOAD);
String Image_path

fd.setVisible(true);
name = fd.getDirectory() + fd.getFile();
        image_path=name;
        ImageIcon icon= new ImageIcon(name);
        icon.setImage(icon.getImage().getScaledInstance(jLabel2.getWidth(),jLabel2.getHeight() , Image.SCALE_DEFAULT));
        jLabel2.setIcon(icon);

在2012年的Swing问题中建议使用AWT?呃... - David Foerster

0

你可以为每个文件使用默认图标,并在另一个线程中加载实际的图标(也许使用 SwingWorker?)。当图标被加载时,SwingWorker 可以回调并更新 FileView。

不确定单个 SwingWorker 是否足够,还是最好为每个正在加载的图标使用一个 SwingWorker。


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