15

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.

Tom Marthenal
  • 3,066
  • 3
  • 32
  • 47
  • Can you provide the code for how you draw one of the 9 images? That is probably where the memory is being consumed. – arunkumar Aug 21 '11 at 17:33
  • I've added it to the end of the original post—thanks! – Tom Marthenal Aug 21 '11 at 17:44
  • What are the dimensions of the nine images and the resulting image? – Codo Aug 21 '11 at 17:45
  • The images to be stitched together (there are actually several hundred of them, usually around 25x25 images) are 500x500px each. The resulting image is about 10,000x10,000px. – Tom Marthenal Aug 21 '11 at 17:53
  • The main image in ARGB (10,000 x 10,000 x 4 byte) takes up at least 400MB of memory. Due to their size, the smaller images will be allocated in the part of the heap that is garbage collected less often. So they might take up another 600 MB of memory. However, it doesn't explain the problem fully yet... – Codo Aug 21 '11 at 18:00
  • Can you better locate where the 3+ GB of memory are really needed. Is it when you draw the small images into the large image, when you save the final image as PNG or both? As a test, put `System.gc()` before `ImageIO.write()`. And don't keep any references / local variables to images that you don't need anymore. It would prevent garbage collecting them. – Codo Aug 21 '11 at 18:16
  • BTW. Are we talking about 3+ GB of Java heap space, ie. you're running the VM with `-xmx3500M` (or similar) because the app otherwise throws an OutOfMemory excpetion. Or are you seeing this amout of used memory with some operating system tool that measures both Java heap space and natively allocated memory? What operating system are we talking about anyway? – Codo Aug 21 '11 at 18:20
  • 2
    Use ImageMagick with JMagick. Urls to the aforementioned: http://stackoverflow.com/questions/7142568/how-to-get-the-dpi-of-an-imagejava/7147327#7147327 – heikkim Aug 22 '11 at 12:22

3 Answers3

5

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

leonbloy
  • 73,180
  • 20
  • 142
  • 190
4

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.

aalku
  • 2,860
  • 2
  • 23
  • 44
1

Would using an external tool be an option? I remember using ImageMagick for similar purpose, you would need to save your smaller images first.

mrembisz
  • 12,722
  • 7
  • 36
  • 32