3

EDIT: See comments to this answer for answer to this question. TLDR: The bottleneck is scaling the image, but profiling shows this as a problem in the ImageIcon constructor.


$ java -version
Picked up _JAVA_OPTIONS: -Dswing.aatext=true -Dawt.useSystemAAFontSettings=on
java version "1.7.0_45"
OpenJDK Runtime Environment (IcedTea 2.4.3) (ArchLinux build 7.u45_2.4.3-1-x86_64)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)

My application loads 1,380 icons at startup. The loading code is shown below. Using an SSD disk, the ImageIcon constructor calls is actually more expensive than the disk reads (the disk reads might use some cache though..)

Loading the icons takes 5.4s, which 3.2s is spent in the ImageIcon constructor.

Is there a way to speed up ImageIcon construction or use something else?

private ImageIcon setIcon(LogicalIcon iconInfo, int size) {
    ImageIcon icon = iconImages.get(iconInfo.getIconName());
    if(icon == null) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(getBestPreviewIcon(iconInfo).getFile());
            icon = new ImageIcon(ImageUtils.scaleDownTo(image, new Dimension(size, size)));
            iconImages.put(iconInfo.getIconName(), icon);
        } catch (IOException e) {
            // FIXME: Add a "Missing Image" icon
            e.printStackTrace();
        }
    }
    setIcon(icon);
    return icon;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
simendsjo
  • 4,739
  • 2
  • 25
  • 53
  • 2
    Resampling takes time; try it without the scaling. – trashgod Jan 02 '14 at 21:26
  • 1
    You're thinking about the scaleDownTo call? I also expected that was the problem, but according to the NetBeans profiler, that only accounts for 9ms. It might be that the Image class uses some late binding so it's not actually done until requested though.. I'll check. EDIT: Yes, disabling the scaling entirely removes the bottleneck.. Guess I'll need to add a loading screen after all.. And a setting for choosing the scaling quality.. – simendsjo Jan 02 '14 at 21:31
  • [`SwingWorker`](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/)? – trashgod Jan 02 '14 at 21:38
  • Yeah, I can probably use that while showing a progress bar. The applications sole purpose is to select icons though, so it's no use until the icons are loaded. – simendsjo Jan 02 '14 at 21:42

1 Answers1

6

To mitigate the observed latency, load and resample the images in the background thread of a SwingWorker. You can publish() each as it becomes available and add it to a List<Image> in your implementation of process(). Initially loaded images can be selected and manipulated, while the rest are loading. Let a progress indicator listen for property changes. Related examples are seen here and here.

Addenda summarizing the comments:

  • A worker thread may enhance the user's perception of liveness.

  • Knowing the number of images at the outset may allow finer granularity in the progress indicator.

  • Focusing on the bottleneck, the scaleDownTo() method appears to call getScaledInstance(); see The Perils of Image.getScaledInstance() for details, and consider the alternatives examined here and here.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for the tip. The applications main job is filtering a set of icons though, so it's pretty confusing if you can start using it without it showing all the icons. I think I'll leave it as-is for the time being. Good answer given the faulty question though :) – simendsjo Jan 03 '14 at 08:05
  • Seems reasonable, but users may tolerate a "live" wait better than "dead" time. If you know the number of icons at the outset, you can show finer granularity in the progress indicator. – trashgod Jan 03 '14 at 08:14
  • I agree. It's 6.7 seconds to start at my computer, but I expect the application to be in use for many minutes once opened. I can show a pretty exact progress bar if needed. I'll need to think about a non-confusing way of handling this. We haven't had any reports on the application being slow since it was released 6 months ago, but then again.. We haven't gotten much feedback at all :/ – simendsjo Jan 03 '14 at 08:18
  • 1
    You might also look at the implementation of `ImageUtils.scaleDownTo()`; alternatives are examined [here](http://stackoverflow.com/a/4216635/230513) and [here](http://stackoverflow.com/a/6916719/230513). – trashgod Jan 03 '14 at 08:24
  • 1
    Ah, good to know. ```scaleDownTo``` uses ```Image.getScaledInstance``` after computing the new size. Good to see I have some alternatives. – simendsjo Jan 03 '14 at 08:29