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");
}
}
I do not see how it would be possible "without processing and re-encoding". If you insist on using Java then I just suggest you to use JAI (project page here). With that you would create one big BufferedImage, load smaller images and draw them on the bigger one.
Or just use ImageMagick montage
:
montage *.png output.png
For more information about montage
, see usage.
The PNG format has no support for tiling, so there is no way you can escape at least decompressing and recompressing the data stream. If the palettes of all images are identical (or all absent), this is the only thing you really need to do. (I'm also assuming the images aren't interlaced.)
You could do this in a streaming way, only having open one "row" of PNGs at a time, reading appropriately-sized chunks from their data stream and writing them to the output stream. This way you would not need to keep entire images in memory. The most efficient way would be to program this on top of libpng yourself. You may need to keep slightly more than one scanline of pixels in memory because of the pixel prediction.
But just using the command-line utilities of ImageMagick, netpbm or similar will save you a large amount of development time for what may be little gain.
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.
Combing Images
private static void combineALLImages(String screenNames, int screens) throws IOException, InterruptedException {
System.out.println("screenNames --> D:\\screenshots\\screen screens --> 0,1,2 to 10/..");
int rows = screens + 1;
int cols = 1;
int chunks = rows * cols ;
File[] imgFiles = new File[chunks];
String files = "";
for (int i = 0; i < chunks; i++) {
files = screenNames + i + ".jpg";
imgFiles[i] = new File(files);
System.out.println(screenNames + i + ".jpg"+"\t Screens : "+screens);
}
BufferedImage sample = ImageIO.read(imgFiles[0]);
//Initializing the final image
BufferedImage finalImg = new BufferedImage(sample.getWidth() * cols, sample.getHeight() * rows, sample.getType());
int index = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
BufferedImage temp = ImageIO.read(imgFiles[index]);
finalImg.createGraphics().drawImage(temp, sample.getWidth() * j, sample.getHeight() * i, null);
System.out.println(screenNames + index + ".jpg");
index++;
}
}
File final_Image = new File("D:\\Screenshots\\FinalImage.jpg");
ImageIO.write(finalImg, "jpeg", final_Image);
}
You might be best off bouncing things off another (lossless) image format. PPM is dead easy to use (and to put tiles in programmatically; it's just a big array on disk, so you'll only have to store one row of tiles at most), but it's very wasteful of space (12 bytes per pixel!).
Then use a standard converter (e.g. ppm2png
) that takes the intermediary format and turns it into the giant PNG.
simple python script for joining tiles into one big image:
import Image
TILESIZE = 256
ZOOM = 15
def merge_images( xmin, xmax, ymin, ymax, output) :
out = Image.new( 'RGB', ((xmax-xmin+1) * TILESIZE, (ymax-ymin+1) * TILESIZE) )
imx = 0;
for x in range(xmin, xmax+1) :
imy = 0
for y in range(ymin, ymax+1) :
tile = Image.open( "%s_%s_%s.png" % (ZOOM, x, y) )
out.paste( tile, (imx, imy) )
imy += TILESIZE
imx += TILESIZE
out.save( output )
run:
merge_images(18188, 18207, 11097, 11111, "output.png")
works for files named like %ZOOM_%XCORD_%YCORD.png , for example 15_18188_11097.png
Use imagemagick's montage like this:
montage *.png montage.png
You can find more information on the parameters here
Good luck
来源:https://stackoverflow.com/questions/3922276/how-to-combine-multiple-pngs-into-one-big-png-file