As others have pointed out, using Java is not necessarily the best bet here.
If you're going to use Java, your best bet--assuming you're sufficiently short on memory so that you can't read the entire dataset into memory multiple times and then write it out again--is to implement RenderedImage
with a class that will read your PNGs off the disk upon demand. If you just create your own new BufferedImage and then try to write it out, the PNG writer will create an extra copy of the data. If you create your own RenderedImage, you can pass it to ImageIO.write(myImageSet,"png",myFileName)
. You can copy SampleModel
and ColorModel
information from your first PNG--hopefully they're all the same.
If you pretend that the entire image is multiple tiles (one tile per source image), then ImageIO.write
will create a WritableRaster
that is the size of the entire image data set, and will call your implementation of RenderedImage.copyData
to fill it with data. If you have enough memory, this is an easy way to go (because you get a huge target set of data and can just dump all your image data into it--using the setRect(dx,dy,Raster)
method--and then don't have to worry about it again). I haven't tested to see whether this saves memory, but it seems to me that it should.
Alternatively, if you pretend that the whole image is a single tile, ImageIO.write
will then ask, using getTile(0,0)
, for the raster that corresponds to that entire image. So you have to create your own Raster, which in turn makes you create your own DataBuffer. When I tried this approach, the minimum memory usage that successfully wrote a 15360x25600 RGB PNG was -Xmx1700M
(in Scala, incidentally), which is just barely over 4 bytes per pixel of written image, so there's very little overhead above one full image in memory.
The PNG data format itself is not one that requires the entire image in memory--it would work okay in chunks--but, sadly, the default implementation of the PNG writer assumes it will have the entire pixel array in memory.