PNG-Encoder Performance
PNG-Encoder Performance
Hi,
I started generating large PNG - images with the com.keypoint.PngEncoder @version 1.1, 11 Nov 1999.
No I updated to more recent jfreechart-libraries and switched to PngEncoder @version 1.5, 19 Oct 2003.
I recognized a significate increase in encoding time. With the old encoder the encoding took about 1.5 sec the new encoder needs about 3.4 sec to encode the same image.
While tracing the lost seconds I found that changes to the code where made by David Gilbert. ("19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object Refinery Limited)")
David, can you imagine why the encoding lasts this long? Did you make similar experiences?
Best regards,
Simon
I started generating large PNG - images with the com.keypoint.PngEncoder @version 1.1, 11 Nov 1999.
No I updated to more recent jfreechart-libraries and switched to PngEncoder @version 1.5, 19 Oct 2003.
I recognized a significate increase in encoding time. With the old encoder the encoding took about 1.5 sec the new encoder needs about 3.4 sec to encode the same image.
While tracing the lost seconds I found that changes to the code where made by David Gilbert. ("19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object Refinery Limited)")
David, can you imagine why the encoding lasts this long? Did you make similar experiences?
Best regards,
Simon
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
That sounds quite slow, but it depends on what you're measuring and how fast your machine is etc. etc. Here is a chart showing timings on my machine:

Each bar represents a compression level (1 = fastest, 9 = best compression, 0 = no compression). I'm not sure why the 0 case is so much slower, but it is.
Here is the code I ran to produce this chart:
Try it out on your own machine and see how it compares (I'm running JDK 1.4.2, 256MB, 2.6GHz CPU, and Windows XP right at this moment).

Each bar represents a compression level (1 = fastest, 9 = best compression, 0 = no compression). I'm not sure why the 0 case is so much slower, but it is.
Here is the code I ran to produce this chart:
Code: Select all
import java.awt.Color;
import java.awt.image.BufferedImage;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.CategoryDataset;
import org.jfree.data.DefaultCategoryDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import com.keypoint.PngEncoder;
public class PNGPerformance extends ApplicationFrame {
private JFreeChart chart;
private DefaultCategoryDataset dataset;
private long[][] times = new long[10][40];
public PNGPerformance(String title) {
super(title);
this.dataset = new DefaultCategoryDataset();
this.chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
setContentPane(chartPanel);
}
/**
* Creates a chart.
*
* @param dataset a dataset.
*
* @return The chart.
*/
private JFreeChart createChart(CategoryDataset dataset) {
JFreeChart chart = ChartFactory.createBarChart(
"PNG Performance",
"Compression Level",
"Milliseconds to Encode",
dataset,
PlotOrientation.VERTICAL,
true,
false,
false
);
chart.setBackgroundPaint(Color.white);
CategoryPlot plot = (CategoryPlot) chart.getPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
return chart;
}
public double calculateMean(long[] values, int count) {
double result = 0.0;
if (values.length > 0) {
double sum = 0.0;
for (int i = 0; i < count; i++) {
sum = sum + values[i];
}
result = sum / count;
}
return result;
}
public void run() {
for (int i = 0; i < 40; i++) {
for (int j = 0; j < 10; j++) {
BufferedImage image = this.chart.createBufferedImage(350, 500);
PngEncoder encoder = new PngEncoder(image, false, PngEncoder.FILTER_NONE, j);
long start = System.currentTimeMillis();
byte[] data = encoder.pngEncode();
long end = System.currentTimeMillis();
this.times[j][i] = end - start;
data = null;
encoder = null;
System.gc();
double mean = calculateMean(this.times[j], i + 1);
this.dataset.setValue(mean, "PngEncoder", "C = " + j);
System.out.println("i = " + i + ", " + (end - start) + " (" + Runtime.getRuntime().freeMemory() + " / " + Runtime.getRuntime().totalMemory() + ")");
}
}
}
/**
* The starting point for the demo.
*
* @param args ignored.
*/
public static void main(String[] args) {
PNGPerformance demo = new PNGPerformance("PNG Performance");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
demo.run();
}
}
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


Hi,
I tried the benchmark on my AMD Athlon 1400, 1GB Ram, Win2000, JDK 1.4.2 and got the following results:




It seems, that the old encoder is very fast while encoding big image-sizes but looses a lot of time creating smaller PNGs.
Can You imagine why the new encoder gets this slow while images get bigger?
Regards,
Simon
I tried the benchmark on my AMD Athlon 1400, 1GB Ram, Win2000, JDK 1.4.2 and got the following results:




It seems, that the old encoder is very fast while encoding big image-sizes but looses a lot of time creating smaller PNGs.
Can You imagine why the new encoder gets this slow while images get bigger?
Regards,
Simon
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
That is quite interesting. I've sent an e-mail to the original author of the encoder to see if he has any time to look at it. When I get a chance, I'll see if I can spot anything that could be improved...
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


Haven't looked at code in a while...
I wrote the original PNG encoder, and I haven't looked at the code in ages. (I figured that everyone was using the PNG encoder library that comes with the new Java SDK.) I just did a diff on the two files, and I don't see any code differences at all between 1.4 and 1.5; it really appears to be all stylistic.
If someone can email me (catcode@catcode.com) a copy of version 1.1, which I don't have any more, I can take a look.
I know that one big difference between the original and current versions was the addition of these lines:
nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft);
nRows = Math.max( nRows, 1 );
to fix a bug when you had large images; you'd get nRows = 0, and that would cause problems.
You might want to expand these into "if" statements rather than function calls, and see if that makes a difference. Or change the code to take the invariant calculation out of the loop [sorry,I can't do indenting properly here]
int rowSize = 32767 / (width * (bytesPerPixel + 1));
while (rowsLeft > 0) {
nRows = Math.min(rowSize, rowsLeft);
nRows = Math.max( nRows, 1 );
If someone can email me (catcode@catcode.com) a copy of version 1.1, which I don't have any more, I can take a look.
I know that one big difference between the original and current versions was the addition of these lines:
nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft);
nRows = Math.max( nRows, 1 );
to fix a bug when you had large images; you'd get nRows = 0, and that would cause problems.
You might want to expand these into "if" statements rather than function calls, and see if that makes a difference. Or change the code to take the invariant calculation out of the loop [sorry,I can't do indenting properly here]
int rowSize = 32767 / (width * (bytesPerPixel + 1));
while (rowsLeft > 0) {
nRows = Math.min(rowSize, rowsLeft);
nRows = Math.max( nRows, 1 );
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
I notice in the writeBytes() method there is some array resizing being done which uses a constant value of '1000'. I changed that to '2000' and ran the numbers again:

It looks promising, but I want to spend some more time understanding what the code is doing before I jump to conclusions.

It looks promising, but I want to spend some more time understanding what the code is doing before I jump to conclusions.

David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
I'm pretty confident that we can squeeze some extra performance out of the PNG encoder, but I haven't had much time to work on it.Simon wrote:Are there any other news on this topic?
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


PngEncoder + resolution and OutOfMemoryError
Hi,
I'm also using the PngEncoder (modified to write the resolution).
I'm using it to output some really big chart images, 17cm*12cm*600dpi which should gime me (with no compression) an image size of 34,14 Mb...
I get an OutOfMemoryError on the the image array creation... it seems that allocating 32Mb as a variable is not working too well for some reason:
I've got a pc running XP with 1Gb ram, I've tried increasing the VM size to 1500m using the -X switches... but nothing does it !
Does someone have an idea of how to solve this problem ?
Is there a way to increase the stack size ?
it does not seam that 32Mb as a byte array is much... ?
I calculated the aproximate sizes using the following formulae:
If this can help...
I'm also using the PngEncoder (modified to write the resolution).
I'm using it to output some really big chart images, 17cm*12cm*600dpi which should gime me (with no compression) an image size of 34,14 Mb...
I get an OutOfMemoryError on the the image array creation... it seems that allocating 32Mb as a variable is not working too well for some reason:
Code: Select all
public byte[] pngEncode(boolean encodeAlpha) {
(...)
// this line below throws the OutOfMemoryError !
pngBytes = new byte[((width + 1) * height * 3) + 200];
(...)
Does someone have an idea of how to solve this problem ?
Is there a way to increase the stack size ?
it does not seam that 32Mb as a byte array is much... ?
I calculated the aproximate sizes using the following formulae:
Code: Select all
byte size: ((width + 1) * height * 3) + 200
=> ((4015+1) * 2834 * 3) + 200 = 34144232
=> (/(2*1024)) 32.56Mb!!!
I calculated the size of the image 17cm*12cm*2.54*600dpi
2.54 is the cm to inch
600 is the dpi
giving 190485979 pixels... at 8bit per pixels
=> 1523887833.6 bytes
=> (/1024) 1488171.7 Kb
=> (/1024) 1453.29 Mb
=> (/1024) 1.42Gb in memory
Tzaphkiel


-
- Posts: 115
- Joined: Fri Mar 14, 2003 3:13 pm
- Location: London, England
- Contact:
SVG?
Can you use SVG instead? The vector nature of the format would make it a lot easier.
Regards,
Richard...
Regards,
Richard...
Hi, I can't, I have to generate some PNGs with resolution information in them.
The images are used for publications using QuickSilver... I don't think it reads SVGs... I'll have a look at the SVG's import in QS (I know it would be better to use it, but...).
It seems strange that allocating 32Mb in a var is so "hard" for the java VM ! ?
If I have to use SVG, I suppose I have to use Batik libraries ?
Regards
The images are used for publications using QuickSilver... I don't think it reads SVGs... I'll have a look at the SVG's import in QS (I know it would be better to use it, but...).
It seems strange that allocating 32Mb in a var is so "hard" for the java VM ! ?
If I have to use SVG, I suppose I have to use Batik libraries ?
Regards
Tzaphkiel


PNG, JPEG, GIF encoding using ImageIO
Hi all,
I've been looking throught the forum to solve my pngEncoder problems (see the PNGEncoder performance post).
I came up with a solution that not content with reducing the encoding time (4 images in 36 seconds (disk writes included) to 15 seconds !!!) it also reduces the file size by 44 times ( of a 17cmx12cmx300dpi image => 8.14Mb to 186.61Kb without loosing image definition) !!!
I have a question nonetheless: (I've not been able to take the time and find out for myself) why is there such a big file size difference ??? If anyone finds out the why, please post a reply, I really would like to find out!!!
I've based my solution on:
JpegWrite, Java Forums - how to set PNG dpi and RFE [ 777515 ] High resolution images saving
I wanted to encode pngs of my graphics from a batch application and to load them into a publication application such as QuickSilver.
I thus needed a way to encode the file quickly and with resolution information in them! (ATT: Gif does not provide resolution information !)
Here is the code I've used to do so:
Edit: png.pHYs_unitSpecifier = 1; must be specified otherwise windows does not recognize the dpi correctly in the file (it sees 96 dpi whilst IrfanView sees the 300dpi) !
Also int resX = (int)Math.ceil(resolution / 0.0254f); is "ceilled" because otherwise, windows sees 299 dpi...
NB: to change the format, just ask for another writer using the ImageIO.getImageWritersByFormatName !
The getHiResChartImage is taken from the solution provided with the PNGEncoder... It actually re-sizes the image according to the ratio between "screen res" and "wanted image res" ! (ATT: on windows, the screen resolution is not 72dpi as one could expect but is often 96dpi !!!)
Here is the code:
Hope this post helps everyone out there and I do believe that including such code in a next release of JFreeChart would be benefic to the library (ImageIO comes with 1.4+ JDKs) !!!
Regards
I've been looking throught the forum to solve my pngEncoder problems (see the PNGEncoder performance post).
I came up with a solution that not content with reducing the encoding time (4 images in 36 seconds (disk writes included) to 15 seconds !!!) it also reduces the file size by 44 times ( of a 17cmx12cmx300dpi image => 8.14Mb to 186.61Kb without loosing image definition) !!!


I've based my solution on:
JpegWrite, Java Forums - how to set PNG dpi and RFE [ 777515 ] High resolution images saving
I wanted to encode pngs of my graphics from a batch application and to load them into a publication application such as QuickSilver.
I thus needed a way to encode the file quickly and with resolution information in them! (ATT: Gif does not provide resolution information !)
Here is the code I've used to do so:
Code: Select all
private void encodeAndWritePreviewImage(String filePath, ChartParameter _chartParams)
{
double outputWidth = _chartParams.getWidth(); //cm
double outputHeight = _chartParams.getHeight(); //cm
float resolution = _chartParams.getResolution();//dpi
String fileName = filePath.substring(0, filePath.lastIndexOf("."));
String extension = filePath.substring(filePath.lastIndexOf("."));
String file = fileName + "_" + outputWidth + "x" + outputHeight + "x" + resolution + "dpi" + extension;
try
{
// old code
// FileOutputStream fos = null;
// fos = new FileOutputStream(file);
// PngEncoder encoder = new
// PngEncoder(_chart.getHiResChartImage(resolution, outputWidth,
// outputHeight), false, 0, 0);
// encoder.setDpi((int) resolution, (int) resolution);
// encoder.pngEncodeToStream(fos);
// fos.close();
RenderedImage rendImage = _chart.getHiResChartImage(resolution, outputWidth, outputHeight);
PNGMetadata png = new PNGMetadata();
int resX = (int)Math.ceil(resolution / 0.0254f);
png.pHYs_pixelsPerUnitXAxis = resX;
png.pHYs_pixelsPerUnitYAxis = resX;
png.pHYs_unitSpecifier = 1;
png.pHYs_present = true;
IIOImage iioImage = new IIOImage(rendImage, null, png);
//Write generated image to a file
// Save as PNG
File f = new File(file);
ImageWriter writer = (ImageWriter) ImageIO.getImageWritersByFormatName("png").next();
ImageOutputStream ios = ImageIO.createImageOutputStream(f);
writer.setOutput(ios);
// Set the compression quality and other header information
ImageWriteParam iwparam = new ImageWriteParam()
{
public int getProgressiveMode()
{
return MODE_COPY_FROM_METADATA;
}
};
// writer.write(png, iioImage, iwparam);
writer.write(iioImage);
writer.dispose();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
Also int resX = (int)Math.ceil(resolution / 0.0254f); is "ceilled" because otherwise, windows sees 299 dpi...
NB: to change the format, just ask for another writer using the ImageIO.getImageWritersByFormatName !
The getHiResChartImage is taken from the solution provided with the PNGEncoder... It actually re-sizes the image according to the ratio between "screen res" and "wanted image res" ! (ATT: on windows, the screen resolution is not 72dpi as one could expect but is often 96dpi !!!)
Here is the code:
Code: Select all
public BufferedImage getHiResChartImage(double resolution, double width, double height)
{
width = width / CM_TO_INCH * _screenResolution;
height = height / CM_TO_INCH * _screenResolution;
double scaleRatio = resolution / _screenResolution;
int rasterWidth = (int) (width * scaleRatio);
int rasterHeight = (int) (height * scaleRatio);
BufferedImage image = new BufferedImage(rasterWidth, rasterHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.transform(AffineTransform.getScaleInstance(scaleRatio, scaleRatio));
_chart.draw(g2, new Rectangle2D.Double(0, 0, width, height), null);
g2.dispose();
return image;
}
Regards
Last edited by Tzaphkiel on Thu Feb 24, 2005 12:03 pm, edited 1 time in total.
Tzaphkiel


-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Thanks for your post. We need to maintain compatibility with JDK 1.3 still as there are a good number of users that need it.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader

