如何将多个PNG组合成一个大的PNG文件?

我有约。 6000个PNG文件(256 * 256像素),并希望将它们组合成一个以编程方式保存所有这些文件的大PNG。

最好/最快的方法是什么?

(目的是在纸上打印,因此使用某些网络技术不是一种选择,只有一个单个图片文件将消除许多使用错误。)

我尝试了fahd的建议但是当我尝试创建一个宽24576像素,高15360像素的BufferedImage时,我得到一个NullPointerException 。 有任何想法吗?

创建一个要写入的大图像。 根据您想要的行数和列数来计算其维度。

  BufferedImage result = new BufferedImage( width, height, //work these out BufferedImage.TYPE_INT_RGB); Graphics g = result.getGraphics(); 

现在循环浏览图像并绘制它们:

  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(); } } 

最后把它写到文件中:

  ImageIO.write(result,"png",new File("result.png")); 

前段时间我有一些类似的需求(巨大的图像 – 而且,我的情况是16位深度 – 将它们完全放在内存中不是一种选择)。 我结束了PNG库的编码,以顺序方式进行读/写。 如果有人发现它有用,它就在这里 。

更新:这是一个示例代码:

 /** * 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"); } } 

我没有看到“如果没有处理和重新编码”,它将如何成为可能。 如果你坚持使用Java,那么我建议你使用JAI (项目页面)。 有了它,你将创建一个大的BufferedImage , 加载较小的图像 , 并在较大的 图像上 绘制它们 。

或者只使用ImageMagick montage

 montage *.png output.png 

有关montage更多信息,请参阅用法 。

PNG格式不支持平铺,因此您无法逃脱至少解压缩和重新压缩数据流。 如果所有图像的调色板都相同(或全部不存在),那么这是您真正需要做的唯一事情。 (我也假设图像没有隔行扫描。)

您可以以流式方式执行此操作,一次只打开一个“行”PNG,从其数据流中读取适当大小的块并将它们写入输出流。 这样您就不需要将整个图像保存在内存中。 最有效的方法是自己在libpng上编程。 由于像素预测,您可能需要在内存中保留略多于一个像素的扫描线。

但是只使用ImageMagick,netpbm或类似的命令行实用程序将为您节省大量的开发时间。

正如其他人所指出的那样,使用Java并不一定是最好的选择。

如果您打算使用Java,那么最好的选择 – 假设您的内存足够短,以至于您无法将整个数据集多次读入内存然后再将其写出来 – 就是实现RenderedImage将根据需要从磁盘读取PNG的类。 如果您只是创建自己的新BufferedImage,然后尝试将其写出来,PNG编写器将创建一个额外的数据副本。 如果您创建自己的RenderedImage,则可以将其传递给ImageIO.write(myImageSet,"png",myFileName) 。 您可以从第一个PNG复制SampleModelColorModel信息 – 希望它们都是一样的。

如果假装整个图像是多个图块(每个源图像一个图块),那么ImageIO.write将创建一个与整个图像数据集大小相同的WritableRaster ,并调用RenderedImage.copyData的实现来填充它数据。 如果你有足够的内存,这是一个简单的方法(因为你获得了一个庞大的目标数据集,可以将所有图像数据转储到其中 – 使用setRect(dx,dy,Raster)方法 – 然后不必再担心了)。 我没有测试过这是否可以节省内存,但在我看来应该这样做。

或者,如果您假装整个图像是单个图块,则ImageIO.write将使用getTile(0,0)询问与整个图像对应的栅格。 因此,您必须创建自己的Raster,这反过来会让您创建自己的DataBuffer。 当我尝试这种方法时,成功写入15360×25600 RGB PNG的最小内存使用量是-Xmx1700M (在Scala中,顺便-Xmx1700M ),每个像素的写入图像仅略高于4个字节,因此在一个完整图像上方的开销非常小。记忆。

PNG数据格式本身并不需要内存中的整个图像 – 它可以在块中正常工作 – 但遗憾的是,PNG编写器的默认实现假定它将整个像素arrays存储在内存中。

梳理图像

 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); } 

你可能最好从另一种(无损)图像格式中剔除东西。 PPM很容易使用(并且以编程方式放置磁贴;它只是磁盘上的一个大数组,所以你最多只需要存储一行磁贴),但它非常浪费空间(每个像素12个字节! )。

然后使用标准转换器(例如ppm2png ),该转换器采用中间格式并将其转换为巨型PNG。

用于将切片连接成一个大图像的简单python脚本:

 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 ) 

跑:

 merge_images(18188, 18207, 11097, 11111, "output.png") 

适用于名为%ZOOM_%XCORD_%YCORD.png的文件,例如15_18188_11097.png

像这样使用imagemagick的蒙太奇:

 montage *.png montage.png 

您可以在此处找到有关参数的更多信息

祝你好运