user1224534
user1224534

Reputation: 137

Improving performance when processing images in Java

I am writing a program which among other things takes a folder of images (Typically around 2000 jpeg images) resizes them, and adds them to a timeline of images. The result of this being as follows:

enter image description here

This works fine, however the way I have done this seems very inefficient. The code which processes these images is shown below:

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]);
        }
    }
}

As can be seen, this code loops through each image path, adds it to a label and adds it to the panel - performing exactly as it should with the correct output.

However, the time taken to do this is substantial. Typically around 5 minutes for 2000 images (depending on the machine). I wondered if there is any way I could improve this performance by using different techniques?

Any help is greatly appreciated.

Upvotes: 3

Views: 1829

Answers (6)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285430

To add to mKorbel's excellent answer, I would definitely use a background thread such as a SwingWorker. This may not make the program any faster, but it will seem a lot faster, and that can make all the difference. Something like:

// 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);
     }
  }
}

Upvotes: 3

mKorbel
mKorbel

Reputation: 109823

takes a folder of images

with processes by using tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);

  • use JTable, with reduced funcionality you can use JList too

Typically around 5 minutes for 2000 images

Image.getScaledInstance is simple asynchonous, witouth guarantee an fast and performance, then you have to redirect loading of images to the Background task

  • advantage first part of images are available immediatelly

  • dis_advantage required dispalying statuses of loading for user, very good knowledge about Swing and Event Dispatch Thread

I'd suggest to look at Runnable#Thread, and output put to the DefaultTableModel, notice this output must be wrapped into invokeLater

another and most complex way is use SwingWorker, but required very good knowledge about Java and Swing too

Upvotes: 3

UmNyobe
UmNyobe

Reputation: 22910

Use tiles. Which means than rather than operating on images which are not shown in the screen, you only operated when the image has to be shown on the screen.

You need to maintain the logical position of the timeline, as well as displayed images. When the user move the cursor to a previously hidden position, you compute which image(s) need to be shown next. If the images are not already processed, you process them. That's the same technique web-browsers use for performance.

Upvotes: 2

Tschallacka
Tschallacka

Reputation: 28742

what you could do to make it faster is by making 4 threads, and have them computing simultaneously the images. i dont know if the vm will spread them over multiple cpu cores though. something to look into because that would boost perfotrmace on a multicore pc

Upvotes: -1

Matt Brock
Matt Brock

Reputation: 5401

Save your scaled instances and load them direct. Hard drive space is cheap. This won't get around the initial cost of generating the thumbs, but any subsequent appearances will be lightning-fast.

Upvotes: 4

haylem
haylem

Reputation: 22673

  • A first thing you could do would be to add the images asynchronously, instead of trying to add all of them at once. Loop over them as you do, add them to the panel and render it every few images or so the user doesn't need to wait for a long initialization time.

  • Reuse image objects. A flyweight pattern would come to mind, and possibly limit the screen redraws to only the portions where you add a new image in your asynchronous loading.

  • If you are likely to have the same images redrawn (or to reload the same folders) in the future, you might want to consider caching some of the image objects, and maybe to save to file the resized thumbnails (many photo viewers do this and will store thumbnails versions - and some useful metadata - in hidden files or folders, so they can reload them faster the next time around.

Upvotes: 1

Related Questions