Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I save a huge PNG without the whole thing being in memory?

Tags:

java

png

I'm saving a very large PNG (25 MB or so) with Java. The problem is that while it's being generated, it's using 3+ gigabytes of memory, which is not ideal since it severely slows down systems with low memory.

The code I'm working with needs to combine a set of tiled images into a single image; in other words, I have nine images (PNG):

A1 A2 A3
B1 B2 B3
C1 C2 C3

which need to be combined into a single image.

The code I'm using is this:

image = new BufferedImage(width, height, height, BufferedImage.TYPE_INT_ARGB_PRE);
g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

// draw the 9 images on here at their proper positions...

// save image
g2d.dispose();
File file = getOutputFile();
ImageIO.write(image, "png", file);

Is there a way to make and save an image without having the entire image in memory?


Edit: To draw the images, I'm doing this in a loop:

BufferedImage tile = ImageIO.read(new File("file.png"));
g2d.drawImage(tile, x, y, w, h);

This is being repeated many times (it's usually about 25x25, but sometimes more), so if there is even a small memory leak here, that could be causing the problem.

like image 759
Tom Marthenal Avatar asked Aug 21 '11 17:08

Tom Marthenal


2 Answers

You can also take a look at this PNGJ library (disclaimer: I coded it), it allows to save a PNG image line by line.

like image 55
leonbloy Avatar answered Oct 13 '22 01:10

leonbloy


ImageIO.write(image, "png", file); is internally using com.sun.imageio.plugins.png.PNGImageWriter. That method and that writer expect image to be a rendered image but PNG writting is done by 'bands' so you can make a subclass of RenderedImage that generates the requested bands of the composed large image as the writer ask for that bands to the image.

From PNGImageWriter class:

private void encodePass(ImageOutputStream os,
                        RenderedImage image,
                        int xOffset, int yOffset,
                        int xSkip, int ySkip) throws IOException {
    // (...)
    for (int row = minY + yOffset; row < minY + height; row += ySkip) {
                Rectangle rect = new Rectangle(minX, row, width, 1); // <--- *1
                Raster ras = image.getData(rect); // <--- *2

*2 I think this is the only place where the writer reads pixels from you image. You should make a getData(rect) method that computes that rect joining 3 bands from 3 images into one.

*1 As you see it reads bands with a height of 1 pixel.

If the things are as I think you should only need to compose 3 images at a time. There would be no need for the other 6 to be in memory.

I know it is not an easy solution but it might help you if you don't find anything easier.

like image 20
aalku Avatar answered Oct 13 '22 02:10

aalku