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
Related
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'm trying to create a loop using Java Swing Timer to constantly cycle through a set of images (i1, i2, i3....in where n is total number of images).
Each of the images is exactly the same size and must be displayed on a Label (say, l1).
There must be a delay of ten seconds between each image being displayed.
Any idea how I can go about this without using the Java TumbleItem applet> It's seems much too complicated for a simple implementation such as mine. (Displaying special deals posters on an online storefront application for school).
I am open to this being achieved in any other way.
Help would be greatly appreciated. Thanks in advance!
I'm trying to create a loop using Java Swing Timer to constantly cycle through a set of images
When you use a Timer you don't use a loop. When the Timer fires you just change the image. So somewhere you would need to keep a List of the images to display and an index of the currently displayed image.
Any idea how I can go about this without using the Java TumbleItem applet> It's seems much too complicated for a simple implementation such as mine
How is it complicated? It displays a series of images, which is close to what you want.
Yes, there is some extra code that loads the images and doesn't start the animation until all the images are loaded. So you could easily simplify the code by not worry about that. Also, there is code that does animation from from left-to-right and then right-to-left. You also don't need that part of the code. Also, there is code that configures the animation speed. Again you can hard code that.
So if you start with that example and then simplify the code you will have a simple solution. Give it a try and then post your code when you encounter a problem.
This is very simple. Use a timer like this:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
//codehere
}
}, 0, delayInMillis)
Use can use an integer to specify in image.
public int image = 1;
in the run() function, use this to switch between the image
if(image = 1) {
image = 2;
} else if(image = 2) {
image = 3;
} if(image = 3) {
image = 0;
}
Now, wherever you are drawing your images, use this:
if(image == 1) {
//draw first image
} else if(image == 2) {
//draw second image
} else if(image == 3) {
//draw third image
}
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 have a problem saving large (f.e. 12 000 x 9 000 ) images.
I'm developing a graphical editing software ( something like simple Photoshop ) and
The user obviously has to have to ability to save the image.
Lets say I would like to save the image as .png.
Does JAVA always need to use the BufferedImage for saving drawn stuff ?
I know the equation for size of the image is:
Xsize * Ysize * 4 ( red, green, blue, alpha )
So in this case we get over 400 MB.
I know I could save the image in parts ( tiles ) but the user would have to merge them somehow anyway.
Is there any other way to save such a large image without using the BufferedImage ?
Code for saving the image:
public static void SavePanel() {
BufferedImage image = null;
image = new BufferedImage(
(int) (Main.scale * sizeX ),
(int) (Main.scale * sizeY ),
BufferedImage.TYPE_INT_RGB);
g2 = image.createGraphics();
panel.paint(g2);
try {
ImageIO.write(image, "png", new File(FactoryDialog.ProjectNameTxt.getText() + ".png"));
} catch (IOException e) {
}
}
Thank you in advance !
The ImageIO.write(..) methods accept an RenderedImage, not just a BufferedImage. I successfully exploited this fact some time ago to write out really large images. Generally, the writer implementations write out the image sequentially, and ask the RenderedImage only for the pieces they currently need.
From looking at your code, I think it should be possible to hack a RenderedImage implementation which takes your panel in it's constructor and can be passed to ImageIO for writing. During the process, ImageIO will request data from your image. You can then use the panel to create the requested pieces (Raster contents) on the fly. This way, the whole image does not have to be stored in memory at any point. A starting point for this approach is
public class PanelImage implements RenderedImage {
private final Panel panel;
public PanelImage(Panel panel) {
this.panel = panel;
}
/* implement all the missing methods, don't be afraid, most are trivial */
}
Obviously, you should also check if your panel doesn't suffer from the same problem as the BufferedImage. Depending on the nature of you application, you'll have to hold the image in memory at least once anyway (modulo using tiles). But this way you can at least avoid the duplication.
Using native image resizer like image magick instead.
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.