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
How to combine multiple PNGs into one big PNG file?
提问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 NullPointerException
when I try to create a BufferedImage
with 24576 pixels wide and 15360 pixels high. Any ideas?
我尝试了 fahd 的建议,但是NullPointerException
当我尝试创建一个BufferedImage
24576 像素宽和 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 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.
如果你打算使用 Java,你最好的选择——假设你的内存足够短缺以至于你不能多次将整个数据集读入内存然后再次写出来——是RenderedImage
用一个类来实现这将根据需要从磁盘中读取您的 PNG。如果您只是创建自己的新 BufferedImage 然后尝试将其写出,PNG 编写器将创建一个额外的数据副本。如果您创建自己的 RenderedImage,则可以将其传递给ImageIO.write(myImageSet,"png",myFileName)
. 您可以从您的第一个 PNG 中复制SampleModel
和ColorModel
信息——希望它们都是一样的。
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.
如果您假设整个图像是多个图块(每个源图像一个图块),那么ImageIO.write
将创建一个WritableRaster
大小为整个图像数据集的数据集,并调用您的实现RenderedImage.copyData
来填充数据。如果你有足够的内存,这是一个简单的方法(因为你得到了一个巨大的目标数据集,可以将你的所有图像数据转储到其中——使用该setRect(dx,dy,Raster)
方法——然后不必担心它再次)。我还没有测试过这是否可以节省内存,但在我看来应该是这样。
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.
或者,如果您假装整个图像是单个图块,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
回答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);
}