I'm writing a Java Swing application that will load from disk about 1500 png images that range in size from 50kb to 75kb each. I don't need to load all of them at once, but I will need to load at 50 images at a time. I've been trying to load them using the typical:
new javax.swing.ImageIcon(getClass().getResource("myimage.jpeg")
but my application freezes and I run out of memory after about the first 30 images or so.
What is the best way to load these images in such a way that I will not overload the jvm and that i will be able to monitor which have loaded successfully so far? If possible and necessary, I'd wouldn't mind if the application showed a "loading..." screen while the images loaded, but I'm not sure how to do that.
Is caching useful here? I don't quite understand it, but I saw this question about using MediaTracker and I'm not sure how that could be implemented here.
Why not create a wrapper object for each image, load them on-demand, and make use of WeakReferences or SoftReferences. That way the garbage collector can bin the image data when necessary, and you can reload as/when the weak reference is cleared.
The upside is that the memory for the images can be cleared when required for other uses. The downside is that you will have to reload the image prior to display (or whatever you're doing with the image).
If you already know how many .pngs you are going to load, you may want to create an ImageIcon Array and load them one by one from the directory/directories (which would allow you to display a loading... screen).
What I think you should do is increasing the min/max. HeapSize of the JVM when running the application. You can specify them by e.g. adding -Xmx256m as a parameter (this sets the max-heap to 256MB) (and maybe -Xms32m [this sets the min-heap to 32mb]) see http://docs.sun.com/source/817-2180-10/pt_chap5.html#wp57033
You will either add these options when launching your app (e.g. "java -jar myApp.jar -Xmx128m") or to your system's jvm-configuration-file or to your project's build properties.
This piece of code would load the entire directory; if you want only 50 images to be loaded, just fiddle with the start and stop parameters.
As already said, you will have to set the max-heap (-Xmx) to something around 300M (e.g. resolution of 1280x1024 -> 1310720px -> 4 byte/pixel -> 5242880 bytes -> 50 images -> 256MB).
File dir = new File("/path/to/directory");
File[] files = dir.listFiles();
BufferedImage[] images = new BufferedImage[files.length];
for (int i = 0; i < files.length; i++)
{
try
{
images[i] = ImageIO.read(files[i]);
} catch (IOException ex){}
}
As stated by Tedil, you should give more memory to the app by launching with something like:
java -Xmx256m -classpath yourclasspath YourMainClass
To load the images with a "please wait" loading screen and a progress bar is tricky. It's already in the realm of Advanced Swing. If you are using Java 6 I recommend reading up the SwingWorker class.
Here's a demo that shows you one approach:
package com.barbarysoftware;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.util.List;
public class ImageLoadingDemo {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(600, 400));
frame.getContentPane().add(new JLabel("I'm the main app frame", JLabel.CENTER));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
final JDialog pleaseWaitDialog = new JDialog(frame, "Loading images", true);
final int imageCount = 50;
final JProgressBar progressBar = new JProgressBar(0, imageCount);
final BufferedImage[] images = loadImages(frame, pleaseWaitDialog, imageCount, progressBar);
System.out.println("images = " + images);
}
private static BufferedImage[] loadImages(JFrame frame, final JDialog pleaseWaitDialog, final int imageCount, final JProgressBar progressBar) {
final BufferedImage[] images = new BufferedImage[imageCount];
SwingWorker<Void, Integer> swingWorker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < imageCount; i++) {
System.out.println("i = " + i);
publish(i);
Thread.sleep(1000); // to simulate the time needed to load an image
// images[i] = ImageIO.read(new File("... path to an image file ..."));
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
final Integer integer = chunks.get(chunks.size() - 1);
progressBar.setValue(integer);
}
#Override
protected void done() {
pleaseWaitDialog.setVisible(false);
}
};
JPanel panel = new JPanel();
panel.add(progressBar);
panel.add(new JButton(new AbstractAction("Cancel") {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}));
pleaseWaitDialog.getContentPane().add(panel);
pleaseWaitDialog.pack();
pleaseWaitDialog.setLocationRelativeTo(frame);
swingWorker.execute();
pleaseWaitDialog.setVisible(true);
return images;
}
}
You should be very careful with loading files through the classloader since these resources are not freed while the classloader is active.
Instead use another approach to load the files directly from disk using a java.io.File object. THey can then be discarded without laying invisibly around.
What are you going to do with the images? Do you really need ImageIcon instances, or will an Image do as well? In either case, it may be easier for you to control the loading if you use the synchronous methods in ImageIO instead of the ImageIcon constructors.
If you run into OutOfMemoryErrors already after 30 images, I assume that the images may have a high resolution and/or colour depth, even if they are relatively small on disk. When you load an image, the image is decompressed and requires much more memory (usually 4*width*height bytes for a colour image) than the size of the compressed file. Unless the images are very small, you are probably not able to cache 1500 uncompressed images within a reasonable amount of memory, so you will have to implement some strategy to only load the images you currently need.
Related
This time I got a really hard nut to crack. I was able to implement a slideshow-program which is able to display a number of random pictures after each other in a given time. The program also reacts on button presses.
Now I got the task to also make it able to display video files and it's wrecking my head. The tasks that need to be solved are the following:
The resolution of the file should be dependent on the actualy size of the screen. If an image or video has a greater resolution than the screen it is supposed to be scaled down (see the example code).
Images and videos are supposed to be implemented as JComponents in a JFrame which itself should be composed out of several elements like an area for the image/video, an area for text and for buttons etc - (I solved this for pictures).
After a certain amount of time, the slideshow is supposed to show the next picture/video. With pictures the time is fixed but when showing a video, the time should be dependent on the duration of the video itself (we wouldn't want the slideshow to jump to the next slide in the middle of the video)
For easier explanation let me first show how I solved the implementation of the pictures into the slideshow:
'''
public class DisplayImage extends JComponent {
private static final long serialVersionUID = 2613775805584208452L;
private static Image image;
public static Image displayImage(File f, Dimension screenSize) throws IOException {
//This method loads a file from the computer and resizes it in comparison to the size of the computer screen. The image is then returned for further processing.
BufferedImage img = ImageIO.read(f);
Image dimg;
double width = screenSize.getWidth()*0.75;
double z1 = (img.getWidth()/width);
double z2 = (img.getHeight()/screenSize.getHeight());
if (img.getHeight()/z1 <= width && img.getHeight()/z1 < screenSize.getHeight()) {
dimg = img.getScaledInstance((int)(img.getWidth()/z1), (int) (img.getHeight()/z1),Image.SCALE_SMOOTH);
} else {
dimg = img.getScaledInstance((int)(img.getWidth()/z2), (int)(img.getHeight()/z2),Image.SCALE_SMOOTH);
}
return dimg;
}
public void setImage(Image image1) {
//When an image is resized, it is given to this method.
//It replaces the global variable "image" with the new loaded image so the JFrame in the slideshow is actually reset and will display the new image.
image = image1;
repaint();
invalidate();
}}
'''
As you can see, I am completely fine for loading a new image and rewriting the image as well as the JComponent of the class with it.
Coming to a video file it get's messy instead. I was able to get video files to be loaded by another code taken from somewhere here using Maven but I didn't succeed in implementing it as a JComponent (I have been browsing stackoverflow as well as google already for days but couldn't find the solution for my problem). So far the only thing I can do is starting an extra player besides the slideshow as if they have nothing in common:
'''
public void playVideo(File f, Dimension screenSize) throws IOException, JCodecException {
Picture img = FrameGrab.getFrameAtSec(f, 1);
double width = screenSize.getWidth()*0.75;
double z1 = (img.getWidth()/width);
double z2 = (img.getHeight()/screenSize.getHeight());
NativeLibrary.addSearchPath(RuntimeUtil.getLibVlcLibraryName(), "C:\\Program Files\\VideoLAN\\VLC");
Native.loadLibrary(RuntimeUtil.getLibVlcLibraryName(), LibVlc.class);
JFrame frame = new JFrame("vlcj Tutorial");
MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory();
Canvas c = new Canvas();
c.setBackground(Color.black);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
p.add(c, BorderLayout.CENTER);
frame.add(p, BorderLayout.CENTER);
EmbeddedMediaPlayer mediaPlayer = mediaPlayerFactory.newEmbeddedMediaPlayer();
mediaPlayer.setVideoSurface(mediaPlayerFactory.newVideoSurface(c));
if (img.getHeight()/z1 <= width && img.getHeight()/z1 < screenSize.getHeight()) {
frame.setSize((int)(img.getWidth()/z1), (int)(img.getHeight()/z1));
} else {
frame.setSize((int)(img.getWidth()/z2), (int)(img.getHeight()/z2));
}
frame.setSize(screenSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
mediaPlayer.playMedia(f.getPath());
}
'''
The mess starts already with me not being able to actually get the measurements of the video file itself (meaning width and height). I have been crushing my head over implementing different frameworks like JavaCV, Xuggle, MarvinFramework and much more but it was no good at all. The only thing I can do is to get a frame from the video as a Picture-type as shown in this example. But this doesn't work for me to give back either a JComponent or a BufferedImage (as with the pictures seen in the first method). Even worse: I have found no possible way to make the JFrame actually be reset when a video file is loaded leading for it to freeze dead as soon as a video is started in a new player. After that there is only the kill switch left.
So I'm lost here. Any help is greatly appreciated.
I'm writing a plugin for a miscropy program and have problems with the repaint() method.
short question:
Is there any way to get informed as soon as the repaint of a JPanel was done or synchronize the code with it?
detailed version:
My program can plot a set of data in a xy-chart to a JPanel and show it using jfree.chart; In another part of the programm I have many datasets (~100) that I want to plot and save as images. I've also found a solution, but I really don't like it. The Problem can be reduced to a notification about the paint status of a JPanel.
In the part that shall save all images I have this solution:
PlotSpectrum spectrumWindow = getTheWindow(); //pseudo code...
// some stuff
ti = storage.getImage(channel, slice, frame, position);
spectrumWindow.plotData(false, andor.captureSpectrum(ti.pix), wave,
centerWave, fineGrating, exposureTime,
slitWidth, substractBackground);
spectrumWindow.repaint(); // probably not necessary
sleep(100); // this annoys me...
spectrumWindow.savePlot(path, true, config, null);
spectrumWindow is a JPanel that is also displayed in another window and it all works fine.
BUT I really don't like that sleep(100) in there... without it I'm asking for a repaint but it isn't done till I try to save a "snapshot" of (thats what savePlot is doing...). I know, other Thread and these damn synchronization problems...
With the sleeping I'm just making it unnecessary slow and if I wait not long enough the images are not completly drawn (eg lower half missing)
Is there any way to get informed as soon as the repaint was done? I probably would be also fine with a Listener, better would be a solution with a monitor or sth comparable or a method that is repainting NOW (doesn't exists as far I know?)
The main GUI (include the JPanel spectrumWindow) and the earlier pasted code are running in different Threads.
The probably also important parts of my code are following here. Please excuse if some brackets aren't matching or some variables aren't declared, I removed very much code.
thanks
schetefan24
class PlotSpectrum extends ApplicationFrame // that extends JFrame
{
public void plotData(boolean backgroundGiven, int[] spect, double[] wave_,
double centerWave, boolean fineGrating_, double exposureTime,
double slitWidth, boolean substractBackground)
{
//process data and store internally
replot();
}
private void replot()
{
XYSeries series = new XYSeries("Spectrum");
//add data to series
XYSeriesCollection collection = new XYSeriesCollection(series);
//setting up some labels and such stuff...
JFreeChart chart = ChartFactory.createXYLineChart(
title,
"Wavelength [nm]",
yLabel,
collection,
PlotOrientation.VERTICAL,
false,
false,
false
);
dataPanel.add(new ChartPanel(chart)); // this is contained in a Frame
}
public void savePlot(String path, boolean overWriteAll, HashMap<String,String> config, int[][] addData)
{
File output = new File(path);
//some more stuff, ask overwrite etc
if(image)
{
BufferedImage im = createImage();
String extension = path.substring(path.lastIndexOf(".")+1, path.length());
ImageIO.write(im, extension, output);
} else {
//that is an textexport, works fine
}
}
public BufferedImage createImage()
{
JPanel panel = (JPanel) flipChart.getSelectedComponent();
int w = panel.getWidth();
int h = panel.getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
panel.paint(g);
return bi;
}
}
that I want to plot and save as images.
add the data to a non visible panel.
create a BufferedImage of the panel
create an ImageIcon using the Image from above
update a JLabel (that has already been added to the frame) using the setIcon(...) method
the above step should generate a PropertyChange event when the Icon changes. You can use a ProperChangeListener to listen for this event. When you receive the event you can repeat steps 1 - 4.
Check out Screen Image. It will help you create an image of a non-visible component.
Note, you don't really need steps 4-5. I just added them so you have a visual of the plots as they are being processed. If you don't want the visual then maybe you just display text on a JLabel indicating which plot is currently being converted.
I think I've just pinpointed the problem, but I'm still not sure what to do about it.
I've created various animated gifs using Photoshop and I wanted to display them in my java application. Using ImageIcon, some of them display as they should. Those are the ones with frames that have the same frame rate (such as each frame is 1 second long). However, the gifs that have varying frame rates (ex. one frame is 1 second, the other is .5 seconds, the next is .2) doesn't seem to be showing correctly. Once the gif reaches the frames that vary in seconds the image messes up. Some examples include the background briefly changing color and half of the image disappearing, or most of the image disappearing with the gif still animating.
I'm having a difficult time articulating, but do I need to use something else other than ImageIcon to correctly load the gif with varying frame rates? Like something with BufferedImage?
Edit: Added complete code below. Again, it works for the image with equal frame rates, but not for one with varying frame rates.
here is the image that works fine
and here is the image that messes up
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JWindow;
public class Creature extends JWindow {
public void initCreature() {
JPanel contentPane = (JPanel) getContentPane();
ImageIcon idleImage = new ImageIcon(getClass().getResource("img.gif"));
// Set window properties
getRootPane().putClientProperty("Window.shadow", false);
setBackground(new Color(0,0,0,0));
setAlwaysOnTop(true);
contentPane.setOpaque(false);
// Get size of user's screen
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice defaultScreen = ge.getDefaultScreenDevice();
Rectangle screen = defaultScreen.getDefaultConfiguration().getBounds();
int taskbarheight = (int) (Toolkit.getDefaultToolkit().getScreenSize().height
- GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getHeight());
int x = (int) screen.getMaxX() - idleImage.getIconWidth();
int y = (int) screen.getMaxY() - idleImage.getIconHeight() - taskbarheight;
JLabel imageLabel = new JLabel(idleImage);
setSize(idleImage.getIconWidth(), idleImage.getIconHeight());
contentPane.add(imageLabel);
setLocation(x, y);
setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
Creature cr = new Creature();
cr.initCreature();
}
});
}
}
The frame rates seem the same in both versions (one seen in a browser, the other in a JLabel). The actual problem is that the rendering in Java becomes 'clipped' in the areas of the animation that do not change.
At a guess I'd say the problem is that PhotoShop is using an advanced encoding to ensure that only the parts of the frame that change, are updated. Note that although Java supports a particular file type like GIF, PNG or JPEG, does not mean it correctly understands every encoding type for each file type.
A simpler image that, for every change, changes the entire frame should work better, but also be larger in bytes. In fact, you can see that working in the other image. Because the little dragon is hunching down then rising back up, all parts of the visible image need to change, so the animation of that cannot be optimized in the same way as the first image can.
The imageLabel doesn't know to repaint when the GIF's frame changes. You need to tell the ImageIcon to notify the JLabel of frame changes, by passing the JLabel to setImageObserver:
idleImage.setImageObserver(imageLabel);
From that method's documentation:
Set this property if the ImageIcon contains an animated GIF, so the observer is notified to update its display.
I am attempting to write a program that will loop through lots of images and apply various operations to them and then store use the result to train a self organizing map, i wish to write a front end to this program that for each image i am processing it will display the original and it will display the resultant image after i have applied the operations to it, so far i have written a GUI and from what i have researched it should display the image ( which i have scaled because some can be rather large ) on the left of the screen but nothing is displayed apart from the text labels i was hoping for some incite into what is going wrong as i am new to programming GUI's.
This is what i get when i run the program, what i would like is a image displayed below the "Origonal Image" label
This portion of code just handles the initialization as you can see an panel is added to the frame all the program is inside here. When the panel is initialized the following code is run, note that this is inside the ImageComparatorPanel class
public ImageComparatorPanel() throws FileNotFoundException, UnsupportedEncodingException
{
setLayout(new BorderLayout());
origonalImage = new JLabel( new ImageIcon() );
leftTitle = new JLabel("Origonal Image");
rightTitle = new JLabel("Shrunken Image");
ButtonListener listener = new ButtonListener();
start = new JButton("Start!");
start.addActionListener(listener);
JPanel left = new JPanel();
left.add(leftTitle);
left.add(origonalImage);
//add(leftTitle, BorderLayout.WEST);
//add(origonalImage, BorderLayout.WEST);
add(left, BorderLayout.WEST);
add(rightTitle, BorderLayout.EAST);
add(start, BorderLayout.NORTH);
setPreferredSize(new Dimension(800,800));
}
in the main part of my program ( the bit that is executed when the start button is pressed ) the bit of code that is supposed to update the image is as follows
origonalImage.setIcon(getImage(imagePath));
the getImage function opens the image and shrinks it so that it will fit on the panel the code for this is ( thought i should include this just in case...
public ImageIcon getImage(String URL) throws IOException
{
double scale = 0.5;
File f = new File(URL);
Image image = ImageIO.read(f);
ImageIcon icon = new ImageIcon(image);
int h = icon.getIconHeight();
int w = icon.getIconWidth();
Image newImg = icon.getImage();
Image scaled = newImg.getScaledInstance((int)(w * scale), (int)(h * scale), Image.SCALE_SMOOTH);
ImageIcon newIcon = new ImageIcon(scaled);
return newIcon;
}
How can i change this so on each iteration of the loop in the RUN function the image displayed in the GUI will be updated?
You state:
How can i change this so on each iteration of the loop in the RUN function the image displayed in the GUI will be updated?
To change a GUI's visible state every x msecs, you need to either use a Swing Timer or a background thread such as a SwingWorker.
The Timer would be used if your code being called intermittently is not long running and does not tie up the Swing event thread (the Event Dispatch Thread or EDT) inordinately. If the code being called periodically does take time to run, then this technique will tie up the EDT making your GUI completely unresponsive. In this case, use the SwingWorker when you do in fact need to do heavy processing between image changes, and then use the SwingWorker's publish/process method pair to obtain and display updated images. A properly created SwingWorker will run the heavy lifting code in a thread background to the EDT, but will allow you to make Swing calls on the EDT when necessary.
If your main problem is just that no images are showing, then you're going about solving this wrong: you shouldn't be trying to solve this in a huge complex program but instead create a small program that just tries to show an image and nothing else. Solve each small sub-problem in a step-wise fashion, and only when solved, add it to the greater whole, the large program.
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:
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.
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.
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
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);
}
}
}
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.
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.
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