JFreeChart - Java Heap Space issue - java

I am using JFreeChart for the first time and I am using a TimeSeriesCollection() to create a TimeSeriesChart.
My reslutset from the DB query is app. aroung 1000 records. I am using org.jfree.date.time.Minute.Minute(int min.....) object to add it to a TimeSeries object.
I have a JFrame on which I add the ChartPanel directly. The user will provide new input parameters and reload the chart data with new dataset. So I clean up before every reload by calling the following in a method
dataset.removeAllSeries();
chart.removeLegend();
chart.getRenderingHints().clear();
cp.getChartRenderingInfo().setEntityCollection(null);
cp.removeAll();
cp.revalidate();
The output is perfect. But I noticed that after running the program 'several times in Eclipse' I see the below error message about Java heap space. Sometimes I also see in the Task Manager that the program hogs on the PC memory even though the dataset is very small (100 records).
Exception occurred during event dispatching:
java.lang.OutOfMemoryError: Java heap space
at sun.util.calendar.Gregorian.newCalendarDate(Gregorian.java:67)
at java.util.GregorianCalendar.<init>(GregorianCalendar.java:575)
at java.util.Calendar.createCalendar(Calendar.java:1012)
at java.util.Calendar.getInstance(Calendar.java:964)
at org.jfree.chart.axis.DateTickUnit.addToDate(DateTickUnit.java:238)
at org.jfree.chart.axis.DateAxis.refreshTicksHorizontal(DateAxis.java:1685)
at org.jfree.chart.axis.DateAxis.refreshTicks(DateAxis.java:1556)
at org.jfree.chart.axis.ValueAxis.reserveSpace(ValueAxis.java:809)
at org.jfree.chart.plot.XYPlot.calculateDomainAxisSpace(XYPlot.java:3119)
at org.jfree.chart.plot.XYPlot.calculateAxisSpace(XYPlot.java:3077)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3220)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1237)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1677)
at javax.swing.JComponent.paint(JComponent.java:1029)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5124)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1491)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1422)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:294)
at javax.swing.RepaintManager.paint(RepaintManager.java:1225)
at javax.swing.JComponent._paintImmediately(JComponent.java:5072)
at javax.swing.JComponent.paintImmediately(JComponent.java:4882)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:786)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:714)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:694)
at javax.swing.RepaintManager.access$700(RepaintManager.java:41)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1636)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:646)
at java.awt.EventQueue.access$000(EventQueue.java:84)
at java.awt.EventQueue$1.run(EventQueue.java:607)
at java.awt.EventQueue$1.run(EventQueue.java:605)
at java.security.AccessController.doPrivileged(Native Method)
My application is as follows:
I have a JFrame on which I directly add the ChartPanel after passing a Chart to it.
chart = ChartFactory.createTimeSeriesChart("Peak monitor", , "Time: Zoom in", "# of Requests Logged", createDataset(from,to), true, false, false);
chartpanel = new ChartPanel(chart);
FramePanel.this.add(cp);
validate();
Here createDataset(from, to) is a method
private TimeSeriesCollection createDataset(Date from, Date to) {
dataset.addSeries(controller.getStuff(from, to));
return dataset;
}
getStuff is called within a SwingWorker thread (DIBkgd method)
public TimeSeries getStuff(Date from, Date to) {
s1 = new TimeSeries("Log Requests");
final Date from1 = from;
final Date to1 = to;
progressDialog.setVisible(true);
sw = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
if (db.getCon() == null) {
db.connect();
}
Arrlst2.clear();
Arrlst2= db.getDataDB(from1, to1);
for (Qryobjects x : Arrlst2) {
s1.add(new Minute(x.getMinute(), x.getHour(), x.getDay(), x.getMonth(), x.getYear()), x.getCount());
}
System.out.println("finished fetching data");
return null;
}
#Override
protected void done() {
progressDialog.setVisible(false);
}
};
sw.execute();
return s1;
}
Within my Database class the getDataDB is executed:
public List<Qryobjects> getDataDB(Date from, Date to) {
PreparedStatement select;
ResultSet rs;
String selectSql = "Select Sum(Cnt) Cid, Hr, Min, Dat from (Select count(H.Request_Id) Cnt , To_Char(H.Timestamp,'HH24') HR, To_Char(H.Timestamp,'mm') MIN, To_Char(H.Timestamp,'MM-dd-yyyy') DAT From Status_History H Where H.Timestamp Between ? And ? Group By H.Request_Id, H.Timestamp Order By H.Timestamp Asc) Group By Hr, Min, Dat order by Dat asc";
try {
select = con.prepareStatement(selectSql);
select.setDate(1, from);
select.setDate(2, to);
rs = select.executeQuery();
System.setProperty("true", "true");
while (rs.next()) {
int cnt = rs.getInt("cid");
int hour = Integer.parseInt(rs.getString("Hr"));
int min = Integer.parseInt(rs.getString("Min"));
int month = Integer.parseInt(rs.getString("dat").substring(0, 2));
int day = Integer.parseInt(rs.getString("dat").substring(3, 5));
int year = Integer.parseInt(rs.getString("dat").substring(6, 10));
Arrlst1.add(new Qryobjects(cnt, hour, min, day, month,year));
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
return Arrlst1;
}

For reference, I profiled two long running time series DTSCTest and MemoryUsageDemo. To exaggerate the scale, I used an artificially small heap, as shown below. In each case, I saw the typical saw-tooth pattern of periodic garbage collection return to baseline, as shown here. In contrast, this pathological example shows a secular rise in consumed memory from unrecoverable resources.
$ java -Xms32m -Xmx80m -cp build/classes:dist/lib/* chart.DTSCTest
$ java -Xms32m -Xmx80m -jar jfreechart-1.0.14-demo.jar

I resolved my problem.
I took the clue from #TrashGod to use dispose(). But it does not work directly for me.
I was adding the chart panel directly to my main JFrame container. And in my case, I wanted to keep creating the charts in the same JFrame container over and over.
I first tried clearing the dataset and called removeall() on the chart panel, but it did not help.
Then the solution I found was to create another JFrame and add the chart panel to it. And when I closed this JFrame, I again clear the dataset and called removeall() on the chart panel and also called dispose(). So everytime, I create a new chart, this JFrame and its children componenets are created and are completely disposed when I exit this JFrame.
So, when a chart is created a new JFrame is created and then disposed.
I should also add that after making this change I started to see the Saw Tooth pattern in the Java VisualVM profiler. I also used Jprofiler and I was shocked to see more than 100,000 objects were created while I was running my program. Now, I see 9000 objects created and it remains constant for the JFree package and based on my resultset retrieved the number of objects in my database package increases or decreases.
One more thing I did was to make my SQL do the parsing and convert it to a number. I wanted to reduce the number of objects created and also reduce the processing done by my program for each retrieved record.

Your solution is great! :)) Thanks to you, I have fixed my heap overflow problem. But, your solution can be even better. :)) Before you draw your graph onto panel, just call method panel.RemoveAll();
and everything that was before on your panel will be disposed. No other
JFrame
instances are necessary...
In my case, solution was:
for(...)
{
panel.RemoveAll();
drawData(listOfData);
}
Have a nice day! :)

In the method org.jfree.chart.axis.DateAxis.refreshTicksHorizontal, I added following extra lines to successfully avoided the OutOfmemoryError. the reason is for some circumstance, the variable tickDate is not increased, so the loop of "while (tickDate.before(upperDate))" becomes an infinite loop.
protected List refreshTicksHorizontal(Graphics2D g2,
Rectangle2D dataArea, RectangleEdge edge) {
List result = new java.util.ArrayList();
Font tickLabelFont = getTickLabelFont();
g2.setFont(tickLabelFont);
if (isAutoTickUnitSelection()) {
selectAutoTickUnit(g2, dataArea, edge);
}
DateTickUnit unit = getTickUnit();
Date tickDate = calculateLowestVisibleTickValue(unit);
Date upperDate = getMaximumDate();
boolean hasRolled = false;
Date previousTickDate=null; //added
while (tickDate.before(upperDate)) {
if(previousTickDate!=null && tickDate.getTime()<=previousTickDate.getTime()){ //added
tickDate=new Date(tickDate.getTime()+100L); //added
} //added
previousTickDate=tickDate; //added
//System.out.println("tickDate="+tickDate+" upperDate="+upperDate);** //add to see infinite loop

Please try by removing the tooltips and the legend from the chart (making them 'false' in the constructor). It should reduce the memory footprint

Related

java jpanel synchronization with repaint() or: Is a Listener addable to swing's repaint?

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.

Jfreechart unable to set time in X axes

I'm trying to to make a chart that takes data from serial port and plot them in y axes and i want current time in x axes.. I think that i set my code correctly because i managed to run it as XY chart now in TimeSeries chart my only issue is that in method series.add(TIME, SERIALDATA); i dont know how to initialize TIME , i know that i want an object RegularTimePeriod but i dont know how to do that..
here is the code.. i know that only some lines are missing please help me to find them...
void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 817, 525);
final TimeSeries series = new TimeSeries("Charts");
final SerialDataReceived serialdataprint = new SerialDataReceived();
final TimeSeriesCollection data = new TimeSeriesCollection(series);
final JFreeChart chart = ChartFactory.createXYLineChart(
"Tmperature IN",
"Time",
"C",
data,
PlotOrientation.VERTICAL,
true,
true,
false
);
final ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBounds(10, 11, 477, 224);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
chartPanel.setVisible(true);
frame.getContentPane().setLayout(null);
frame.getContentPane().add(chartPanel);
chartPanel.setLayout(null);
Thread outtempthread=new Thread() { //THREAD THAT RUNS ALL THE TIME
public void run() {
try {
while (true){
Thread.sleep(2000);
double intemp = serialdataprint.getintemp(); //THIS WHERE I TAKE MY SERIAL DATA
series.add(I WANT TO DISPLAY HERE LETS SAY 13:23:15, intemp); //HERE IS MY PROBLEM
}}
catch (InterruptedException ie) {}
}
};
outtempthread.start();
}
I've only ever used TimeSeries measured in days so I used the org.jfree.data.time.Day class.
Here's the jfreechart javadoc for all the different time classes : http://www.jfree.org/jfreechart/api/javadoc/org/jfree/data/time/package-summary.html
Try out a few and see what's right for you.
Since you appear to only need hour,minute second of a single day, you might be able to use the Second class.
Here is how you would make a TimeSeries that way:
int todaysDay =...
int todaysMonth =...
int todaysYear =...
TimeSeries series = new TimeSeries(name, Second.class);
//this should mark 'inTemp' as 13:23:15
series.add(new Second(15,23,13,todaysDay, todaysMonth, todaysYear),
inTemp);
ok!! finally i found the solution! i don't know if is the correct way but it works an now i have real time in my chart every time my serial port updates here is the fix code!
String timeStamp1 = new SimpleDateFormat("mm").format(Calendar.getInstance().getTime());
int minute = Integer.parseInt(timeStamp1);
double intemp = serialdataprint.getintemp();
series.addOrUpdate(new Minute(minute,hour), intemp);
A couple of pointers:
The ChartFactory.createXYLineChart() method will create a line chart where both the X and Y axes are numerical. Try the createTimeSeriesChart() to get a chart that shows dates on the X axis (or create a new DateAxis() instance and call plot.setDomainAxis() to change the X axis);
The TimeSeriesCollection class is a good dataset to use for time series data if you need the structure that it provides (it enforces a regular time period and prevents duplicates among other things). However, bear in mind that it is simply an implementation of the XYDataset interface where the x-values returned are "milliseconds since 1-Jan-1970" (the standard encoding of "dates" in Java). You can simplify your code by using an XYSeriesCollection (which also implements the XYDataset interface), and call System.currentTimeInMillis() to get the current x-value when new data comes in. The date axis on your chart will take care of presenting a date scale for this data.

Screen Capture in separate thread making Java application slow/unresponsive

I'm working on an application that records the users screen, webcam and microphone whilst he/she is performing certain activities. It will be used for research purposes. The application has been successfully tested on Windows, but on Mac OS X (Maverick with Java 7.0.45) the application becomes slow and unresponsive when recording is started.
This is why I find this difficult to comprehend:
The recording is done in a separate thread, so how could it influence the responsiveness of another thread? Especially as after each run either Thread.yield() or Thread.sleep(...) are called.
Logs show that whilst attempting to record at 15 FPS, the resulting frame rate was 2 FPS. So it seems the code that does the capturing of a single frame might be too slow. But why then does it work fine on Windows?
Just a quick note: the application was successfully tested by tons of users on Windows, but I only got to test it on a single Mac. However, that one was just formatted and got a clean install of OS X Maverick, Java (and Netbeans).
Below you will find the code that records the screen and writes it to a video using Xuggler. The code for recording the webcam is similar, and I'd doubt recording the audio has anything to do with it. My question is:
What might be the cause of the application becoming unresponsive?, and
How could the code be made more efficient and so improve FPS?
IMediaWriter writer = ToolFactory.makeWriter(file.getAbsolutePath());
Dimension size = Globals.sessionFrame.getBounds().getSize();
Rectangle screenRect;
BufferedImage capture;
BufferedImage mousePointImg;
writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_H264, size.width, size.height);
int i = 0;
while (stop == false) {
// Get mouse cursor to draw over screen image.
PointerInfo mousePointer = MouseInfo.getPointerInfo();
Point mousePoint = mousePointer.getLocation();
Point screenPoint = new Point((int) (mousePoint.getX() -
Globals.sessionFrame.getBounds().getX()), (int) (mousePoint.getY() -
Globals.sessionFrame.getBounds().getY()));
// Get the screen image.
try {
screenRect = new Rectangle(Globals.sessionFrame.getBounds());
capture = new Robot().createScreenCapture(screenRect);
} catch ( ... ) { ... }
// Convert and resize the screen image.
BufferedImage image = ConverterFactory.convertToType(capture,
BufferedImage.TYPE_3BYTE_BGR);
IConverter converter = ConverterFactory.createConverter(image,
IPixelFormat.Type.YUV420P);
// Draw the mouse cursor if necessary.
if (mouseWithinScreen()) {
Graphics g = image.getGraphics();
g.drawImage(mousePointImg, (int) screenPoint.getX(),
(int) screenPoint.getY(), null);
}
// Prepare the frame.
IVideoPicture frame = converter.toPicture(image, (System.currentTimeMillis() -
startTimeMillis()) * 1000);
frame.setKeyFrame(i % (getDesiredFPS() * getDesiredKeyframeSec()) == 0);
// Write to the video
writer.encodeVideo(0, frame);
// Delay the next capture if we are at the desired FPS.
try {
if (atDesiredFPS()) {
Thread.yield();
} else {
Thread.sleep(1000 / getDesiredFPS());
}
} catch ( ... ) { ... }
i++;
}
writer.close();
There are several architectural issues that I can see in your code:
First if you want to execute something at a fixed rate, use the ScheduledThreadPoolExecutor.scheduleAtFixedRate(...) function. It will make your entire delay code part obsolete as well as ensuring that certain OS timing issues will not interfere with your scheduling.
Then to make things faster you need to take your code apart a bit. As far as I can see you have 3 tasks: the capture, the mouse-drawing/conversion and the stream writing. If you put the capture part in a scheduled Runnable, the conversion into multi-parallel execution as Callables into an Executor, and then in a 3rd thread take the results from a result list and write it into the stream, you can fully utilize multi-cores.
Pseudocode:
Global declarations (or hand them over to the various classes):
final static Executor converterExecutor = Executors.newFixedThreadPoolExecutor(Runtime.getRuntime().availableProcessors());
final static LinkedBlockingQueue<Future<IVideoPicture>> imageQueue = new LinkedBlockingQueue<>();
// ...
Capture Runnable (scheduled at fixed rate):
capture = captureScreen();
final Converter converter = new Converter(capture);
final Future<IVideoPicture> conversionResult = converterExecutor.submit(converter);
imageQueue.offer(conversionResult); // returns false if queue is full
Conversion Callable:
class Converter implements Callable<IVideoPicture> {
// ... variables and constructor
public IVideoPicture call() {
return convert(this.image);
}
}
Writer Runnable:
IVideoPicture frame;
while (this.done == false) {
frame = imageQueue.get();
writer.encodeVideo(0, frame);
}
You can ensure that the imageQueue does not overflow with images to render if the CPU is too slow by limiting the size of this queue, see the constructor of LinkedBlockingQueue.

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:
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

Populating huge table in SWT/JFace RCP application

How would you go about displaying huge amount of rows in SWT table? Huge is something above 20K rows, 20 columns. Don't ask me why I need to show that much data, it's not the point. The point is how to make it work as fast as possible so that end user won't get bored waiting. Each row displays an instance of some object, columns are its properties (some). I thought to use JFace content/label provider pattern, but afraid it will be even slower than hitting the table directly with the data. This is how it goes:
Display.getDefault().asyncExec(new Runnable() {
public void run() {
List<MyObject> objects = model.getViewData();
for(MyObject object: objects){
TableItem item = new TableItem(table, SWT.NULL);
item.setImage(0, img1);
item.setBackground(color1);
item.setText(0, object.getProperty0());
item.setText(1, object.getProperty1());
item.setText(2, object.getProperty2());
.....
}
});
Drawing 20k records on my computer takes about 20 sec.
The biggest performance problem I've seen in Windows are caused by incredible amount of native windowing messages sent by SWT when new table item created and populated with text. I've found great workaround for this - hide table before populating, and then show it when done. Just calling table.setVisible(false) before the loop and table.setVisible(true) after the loop does wonders - the speed goes up six-seven times!
I'd like to speed it up even more.
What would you suggest ? Also, I wonder how this trick hiding the widget would work on non-windows implementations of SWT (aka Linux) ?
SWT can do that for you. When you use the SWT.VIRTUAL style flag, items are only created when scrolled into view. Here's how to do it:
Create the table with style SWT.VIRTUAL
Set the row count using Table#setItemCount()
Add a SWT.SetData Listener that fills the TableItems on demand.
Here's a code snippet:
public static void main( String[] args ) {
Display display = new Display();
Shell shell = new Shell( display );
shell.setLayout( new FillLayout() );
final Table table = new Table( shell, SWT.VIRTUAL );
table.setItemCount( 10000 );
table.addListener( SWT.SetData, new Listener() {
public void handleEvent( Event event ) {
TableItem item = (TableItem)event.item;
item.setText( "Item " + table.indexOf( item ) );
}
} );
shell.setSize( 300, 500 );
shell.open();
while( !shell.isDisposed() ) {
if( !display.readAndDispatch() ) {
display.sleep();
}
}
display.dispose();
}
You want to do lazy-loading on the table display. Basically, you keep all these objects offscreen in memory, then create only a handful of actual GUI table rows. As the user scrolls you re-render these rows with the objects at that scroll location.
See this article (EDIT: oops I meant this article) for a JFace example.
1 - use the setText(String[]) instead of setText(int, String) one call instead of several.
2 - use myTable.setRedraw(false) before and myTable.setRedraw(true) after the process to stop all redrawing opérations during loading data.
it's simpler and can improve performance !!
good luck.
on my side using this I load 2500 lines of 20 column in less than 300ms..... on a standard today PC.

Categories