Come combinare più PNG in un unico grande file PNG?

Ho ca. 6000 file PNG (256 * 256 pixel) e vuoi combinarli in un grande PNG che li tiene tutti programmaticamente.

Qual è il modo migliore / più veloce per farlo?

(Lo scopo è la stampa su carta, quindi l’uso di alcune tecnologie web non è un’opzione e avere un unico file di immagine eliminerà molti errori di utilizzo.)

Ho provato il suggerimento di fahd ma ottengo una NullPointerException quando provo a creare una BufferedImage con 24576 pixel di larghezza e 15360 pixel di altezza. Qualche idea?

Crea una grande immagine alla quale scrivere. Calcola le sue dimensioni in base a quante righe e colonne desideri.

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

Ora fai scorrere le tue immagini e disegnale:

  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 scrivilo su file:

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

Ho avuto qualche bisogno simile qualche tempo fa (immagini enormi -e, io il mio caso con 16 bitdepth- per averli completamente in memoria non era un’opzione). E ho finito di scrivere una libreria PNG per fare la lettura / scrittura in modo sequenziale. Nel caso qualcuno lo trovi utile, è qui .

Aggiornato: ecco un codice di esempio:

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

Non vedo come sarebbe ansible “senza elaborare e ricodificare”. Se insisti sull’uso di Java, ti suggerisco di usare JAI (pagina del progetto qui ). Con questo si creerebbe una grande BufferedImage , si caricheranno le immagini più piccole e le si disegnerà su quella più grande .

Oppure usa solo il montage ImageMagick :

 montage *.png output.png 

Per ulteriori informazioni sul montage , consultare l’ utilizzo .

Il formato PNG non ha supporto per la piastrellatura, quindi non c’è modo di evitare almeno la decompressione e la ricompressione del stream di dati. Se le palette di tutte le immagini sono identiche (o tutte assenti), questa è l’unica cosa che devi davvero fare. (Suppongo anche che le immagini non siano interlacciate).

Potresti farlo in streaming, avendo solo una “fila” di PNG alla volta, leggendo blocchi di dimensioni appropriate dal loro stream di dati e scrivendoli nel stream di output. In questo modo non avresti bisogno di conservare intere immagini in memoria. Il modo più efficiente sarebbe programmarlo su libpng da solo. Potrebbe essere necessario mantenere leggermente più di una linea di scansione in pixel a causa della previsione dei pixel.

Ma solo usando le utility da riga di comando di ImageMagick, netpbm o simili si risparmia un grande ammontare di tempo di sviluppo per quello che può essere un piccolo guadagno.

Come altri hanno sottolineato, l’uso di Java non è necessariamente la migliore scommessa qui.

Se stai per utilizzare Java, la soluzione migliore, supponendo che la memoria sia sufficientemente breve, in modo che non sia ansible leggere l’intero set di dati in memoria più volte e quindi scriverlo di nuovo, è implementare RenderedImage con un class che leggerà i tuoi PNG dal disco su richiesta. Se crei la tua nuova BufferedImage e poi provi a scriverlo, il writer PNG creerà una copia extra dei dati. Se crei la tua RenderedImage, puoi passarla a ImageIO.write(myImageSet,"png",myFileName) . Puoi copiare le informazioni SampleModel e ColorModel dal tuo primo PNG, speriamo che siano tutte uguali.

Se si immagina che l’intera immagine sia ImageIO.write più ImageIO.write (una tessera per immagine sorgente), quindi ImageIO.write creerà un WritableRaster che è la dimensione dell’intero set di dati dell’immagine e chiamerà l’implementazione di RenderedImage.copyData per riempirla con dati. Se hai abbastanza memoria, questo è un modo facile per andare (perché ottieni un enorme set di dati di destinazione e puoi semplicemente riversare tutti i dati dell’immagine in esso – usando il setRect(dx,dy,Raster) – e poi non devi preoccuparti di nuovo di nuovo). Non ho provato per vedere se questo fa risparmiare memoria, ma mi sembra che dovrebbe.

In alternativa, se si pretende che l’intera immagine sia una singola tessera, ImageIO.write chiederà quindi, utilizzando getTile(0,0) , al raster che corrisponde all’intera immagine. Quindi devi creare il tuo Raster, che a sua volta ti fa creare il tuo DataBuffer. Quando ho provato questo approccio, l’utilizzo minimo della memoria che ha scritto correttamente un PNG RGB 15360×25600 era -Xmx1700M (in Scala, per inciso), che è appena appena più di 4 byte per pixel di immagine scritta, quindi c’è poco overhead sopra un’immagine intera in memoria.

Il formato dei dati PNG non è uno che richiede l’intera immagine in memoria – funzionerebbe bene in blocchi – ma, purtroppo, l’implementazione predefinita del writer PNG presuppone che avrà l’intera matrice di pixel in memoria.

Combinare immagini

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

Potrebbe essere meglio rimbalzare su un altro formato di immagine (senza perdita di dati). PPM è morto facile da usare (e per mettere le tessere in modo programmatico, è solo un grande array su disco, quindi dovrai solo memorizzare una riga di piastrelle al massimo), ma è molto dispendioso di spazio (12 byte per pixel! ).

Quindi usa un convertitore standard (es. ppm2png ) che prende il formato intermedio e lo trasforma nel gigante PNG.

semplice script python per unire le tessere in un’unica grande immagine:

 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 ) 

correre:

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

funziona per file denominati come% ZOOM_% XCORD_% YCORD.png, ad esempio 15_18188_11097.png

Usa il assembly di imagemagick in questo modo:

 montage *.png montage.png 

Puoi trovare maggiori informazioni sui parametri qui

In bocca al lupo