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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3922276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 06:55:47  来源:igfitidea点击:

How to combine multiple PNGs into one big PNG file?

javaimagescalaimage-processingpng

提问by soc

I have approx. 6000 PNG files (256*256 pixels) and want to combine them into a big PNG holding all of them programmatically.

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

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 NullPointerExceptionwhen I try to create a BufferedImagewith 24576 pixels wide and 15360 pixels high. Any ideas?

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

采纳答案by dogbane

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

回答by Neeme Praks

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 imagesand draw them on the bigger one.

我不明白“没有处理和重新编码”怎么可能。如果您坚持使用 Java,那么我只建议您使用JAI此处为项目页面)。有了它,您将创建一个大的 BufferedImage加载较小的图像在较大的图像上绘制它们

Or just use ImageMagickmontage:

或者只是使用ImageMagickmontage

montage *.png output.png

For more information about montage, see usage.

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

回答by gpvos

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.)

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

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.

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

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.

但是,仅仅使用 ImageMagick、netpbm 或类似的命令行实用程序将为您节省大量的开发时间,但收益可能很小。

回答by Alex Feinman

You might be best off bouncing things off another (lossless) image format. PPMis 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!).

您最好从另一种(无损)图像格式中弹跳。PPM非常易于使用(并以编程方式放入切片;它只是磁盘上的一个大数组,因此您最多只需存储一行切片),但它非常浪费空间(每像素 12 个字节! )。

Then use a standard converter (e.g. ppm2png) that takes the intermediary format and turns it into the giant PNG.

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

回答by Rex Kerr

As others have pointed out, using Java is not necessarily the best bet here.

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

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 RenderedImagewith 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 SampleModeland ColorModelinformation from your first PNG--hopefully they're all the same.

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

If you pretend that the entire image is multiple tiles (one tile per source image), then ImageIO.writewill create a WritableRasterthat is the size of the entire image data set, and will call your implementation of RenderedImage.copyDatato 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.

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

Alternatively, if you pretend that the whole image is a single tile, ImageIO.writewill 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.

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

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.

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

回答by Mateusz Rudnicki

simple python script for joining tiles into one big image:

用于将瓷砖连接成一个大图像的简单 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 )

run:

跑:

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

works for files named like %ZOOM_%XCORD_%YCORD.png , for example 15_18188_11097.png

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

回答by leonbloy

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.

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

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

回答by by0

Use imagemagick's montage like this:

像这样使用 imagemagick 的蒙太奇:

montage *.png montage.png

You can find more information on the parameters here

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

Good luck

祝你好运

回答by Yash

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

}