Como combinar vários PNGs em um grande arquivo PNG?

Eu tenho aprox. 6000 arquivos PNG (256 * 256 pixels) e deseja combiná-los em um grande PNG, mantendo todos eles programaticamente.

Qual é a melhor maneira / mais rápida de fazer isso?

(O objective é imprimir em papel, portanto, usar alguma tecnologia da Web não é uma opção e ter um único arquivo de imagem elimina muitos erros de uso.)

Eu tentei a sugestão de fahd, mas eu recebo um NullPointerException quando tento criar um BufferedImage com 24576 pixels de largura e 15360 pixels de altura. Alguma ideia?

Crie uma imagem grande na qual você irá escrever. Calcule suas dimensões com base em quantas linhas e colunas você deseja.

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

Agora percorra suas imagens e desenhe-as:

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

Finalmente escreva no arquivo:

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

Eu tive alguma necessidade semelhante há algum tempo (imagens enormes – e, meu caso com 16 bits – para tê-las totalmente na memory não era uma opção). E terminei de codificar uma biblioteca PNG para fazer a leitura / gravação de forma seqüencial. Caso alguém ache útil, está aqui .

Atualizado: aqui está um código de exemplo:

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

Eu não vejo como seria possível “sem processamento e recodificação”. Se você insistir em usar o Java, sugiro que use a JAI (página do projeto aqui ). Com isso você criaria um grande BufferedImage , carregaria imagens menores e as desenharia no maior .

Ou apenas use a montage ImageMagick :

 montage *.png output.png 

Para mais informações sobre montage , veja o uso .

O formato PNG não tem suporte para ladrilhos, portanto, não há como você conseguir, pelo menos, descompactar e recompactar o stream de dados. Se as paletas de todas as imagens são idênticas (ou todas ausentes), esta é a única coisa que você realmente precisa fazer. (Eu também estou supondo que as imagens não estão entrelaçadas.)

Você poderia fazer isso de maneira contínua, abrindo apenas uma “linha” de PNGs de cada vez, lendo trechos de tamanho apropriado do stream de dados e gravando-os no stream de saída. Dessa forma, você não precisaria manter imagens inteiras na memory. A maneira mais eficiente seria programar isso em cima da libpng. Talvez seja necessário manter um pouco mais de uma linha de varredura de pixels na memory devido à previsão de pixels.

Mas apenas usando os utilitários de linha de comando do ImageMagick, netpbm ou similar você economizará muito tempo de desenvolvimento para o que pode ser pouco ganho.

Como outros apontaram, o uso de Java não é necessariamente a melhor aposta aqui.

Se você for usar Java, sua melhor aposta – supondo que você tenha memory insuficiente para que você não consiga ler o dataset inteiro na memory várias vezes e depois escrevê-lo novamente – é implementar o RenderedImage com um class que lerá seus PNGs do disco sob demanda. Se você acabou de criar seu próprio BufferedImage e, em seguida, tentar escrevê-lo, o gravador PNG criará uma cópia extra dos dados. Se você criar seu próprio RenderedImage, poderá transmiti-lo para ImageIO.write(myImageSet,"png",myFileName) . Você pode copiar as informações de SampleModel e ColorModel do seu primeiro PNG – esperamos que sejam todas iguais.

Se você fingir que a imagem inteira é de vários blocos (um ladrilho por imagem de origem), ImageIO.write criará um ImageIO.write o tamanho do dataset de imagem inteiro e chamará sua implementação de RenderedImage.copyData para preenchê-lo com dados. Se você tiver memory suficiente, esta é uma maneira fácil de ir (porque você obtém um grande dataset de destino e pode simplesmente despejar todos os seus dados de imagem nele – usando o método setRect(dx,dy,Raster) – e então não precisa se preocupar com isso novamente). Eu não testei para ver se isso economiza memory, mas parece-me que deveria.

Alternativamente, se você fingir que a imagem inteira é um único bloco, ImageIO.write perguntará, usando getTile(0,0) , pela imagem que corresponde a toda a imagem. Então você tem que criar seu próprio Raster, que por sua vez faz você criar seu próprio DataBuffer. Quando tentei essa abordagem, o uso mínimo de memory que escreveu com êxito um PNG RGB de 15360×25600 era -Xmx1700M (no Scala, incidentalmente), pouco mais de 4 bytes por pixel de imagem gravada, portanto há muito pouca sobrecarga acima de uma imagem completa memory.

O formato de dados PNG em si não é aquele que requer toda a imagem na memory – funcionaria bem em partes – mas, infelizmente, a implementação padrão do escritor PNG assume que terá toda a matriz de pixels na memory.

Penteando Imagens

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

Você pode ser o melhor em tirar as coisas de outro formato de imagem (sem perdas). O PPM é muito fácil de usar (e colocar os tiles de forma programática; é apenas um grande array no disco, então você só precisa armazenar uma linha de blocos no máximo), mas é um desperdício de espaço (12 bytes por pixel! ).

Em seguida, use um conversor padrão (por exemplo, ppm2png ) que ppm2png o formato intermediário e transforme-o no PNG gigante.

script python simples para unir blocos em uma imagem grande:

 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 ) 

corre:

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

funciona para arquivos nomeados como% ZOOM_% XCORD_% YCORD.png, por exemplo 15_18188_11097.png

Use a assembly do imagemagick assim:

 montage *.png montage.png 

Você pode encontrar mais informações sobre os parâmetros aqui

Boa sorte