How to combine multiple PNGs into one big PNG file?
I have approx. 6000 PNG files (256*256 pixels) and want to combine them into a big PNG holding all of them programmatically.
What's the best/fastest way to do that?
(The purpose is printing on paper, so using some web-technology is not an option and having one, single picture file will eliminate many usage errors.)
I tried fahd's suggestion but I get a NullPointerException
when I try to create a BufferedImage
with 24576 pixels wide and 15360 pixels high. Any ideas?
Create a large image which you will write to. Work out its dimensions based on how many rows and columns you want.
BufferedImage result = new BufferedImage(
width, height, //work these out
BufferedImage.TYPE_INT_RGB);
Graphics g = result.getGraphics();
Now loop through your images and draw them:
for(String image : images){
BufferedImage bi = ImageIO.read(new File(image));
g.drawImage(bi, x, y, null);
x += 256;
if(x > result.getWidth()){
x = 0;
y += bi.getHeight();
}
}
Finally write it out to file:
ImageIO.write(result,"png",new File("result.png"));
I had some similar need some time ago (huge images -and, I my case with 16 bitdepth- to have them fully in memory was not an option). And I ended coding a PNG library to do the read/write in a sequential way. In case someone find it useful, it's here.
Updated: here's a sample code:
/**
* Takes several tiles and join them in a single image
*
* @param tiles Filenames of PNG files to tile
* @param dest Destination PNG filename
* @param nTilesX How many tiles per row?
*/
public class SampleTileImage {
public static void doTiling(String tiles[], String dest, int nTilesX) {
int ntiles = tiles.length;
int nTilesY = (ntiles + nTilesX - 1) / nTilesX; // integer ceil
ImageInfo imi1, imi2; // 1:small tile 2:big image
PngReader pngr = new PngReader(new File(tiles[0]));
imi1 = pngr.imgInfo;
PngReader[] readers = new PngReader[nTilesX];
imi2 = new ImageInfo(imi1.cols * nTilesX, imi1.rows * nTilesY, imi1.bitDepth, imi1.alpha, imi1.greyscale,
imi1.indexed);
PngWriter pngw = new PngWriter(new File(dest), imi2, true);
// copy palette and transparency if necessary (more chunks?)
pngw.copyChunksFrom(pngr.getChunksList(), ChunkCopyBehaviour.COPY_PALETTE
| ChunkCopyBehaviour.COPY_TRANSPARENCY);
pngr.readSkippingAllRows(); // reads only metadata
pngr.end(); // close, we'll reopen it again soon
ImageLineInt line2 = new ImageLineInt(imi2);
int row2 = 0;
for (int ty = 0; ty < nTilesY; ty++) {
int nTilesXcur = ty < nTilesY - 1 ? nTilesX : ntiles - (nTilesY - 1) * nTilesX;
Arrays.fill(line2.getScanline(), 0);
for (int tx = 0; tx < nTilesXcur; tx++) { // open several readers
readers[tx] = new PngReader(new File(tiles[tx + ty * nTilesX]));
readers[tx].setChunkLoadBehaviour(ChunkLoadBehaviour.LOAD_CHUNK_NEVER);
if (!readers[tx].imgInfo.equals(imi1))
throw new RuntimeException("different tile ? " + readers[tx].imgInfo);
}
for (int row1 = 0; row1 < imi1.rows; row1++, row2++) {
for (int tx = 0; tx < nTilesXcur; tx++) {
ImageLineInt line1 = (ImageLineInt) readers[tx].readRow(row1); // read line
System.arraycopy(line1.getScanline(), 0, line2.getScanline(), line1.getScanline().length * tx,
line1.getScanline().length);
}
pngw.writeRow(line2, row2); // write to full image
}
for (int tx = 0; tx < nTilesXcur; tx++)
readers[tx].end(); // close readers
}
pngw.end(); // close writer
}
public static void main(String[] args) {
doTiling(new String[] { "t1.png", "t2.png", "t3.png", "t4.png", "t5.png", "t6.png" }, "tiled.png", 2);
System.out.println("done");
}
}