2010-10-13 19 views
23

Ho ca. 6000 file PNG (256 * 256 pixel) e vuoi combinarli in un grande PNG che li tiene tutti programmaticamente.Come combinare più PNG in un unico grande file PNG?

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

(lo scopo è la stampa su carta, in modo da utilizzare alcuni web-tecnologia non è un'opzione e avere uno, singolo file immagine sarà eliminare molti errori di utilizzo.)

Ho provato il suggerimento di Fahd, ma ottengo un NullPointerException quando Provo a creare uno BufferedImage con 24576 pixel di larghezza e 15360 pixel di altezza. Qualche idea?

+0

Vuoi un massiccio PNG? saranno 1536000x1536000 pixel? Devo dire che una libreria di immagini di base sarebbe un'opzione migliore qui. – kyndigs

+1

Anche se c'è un modo per re-encoding, un'immagine molto grande ricodificata potrebbe potenzialmente comprimersi molto meglio, specialmente se le immagini sono simili. – Thilo

+2

@kyndigs: più simile a 15360 x 25600 (per un arrangiamento 60 x 100) – Thilo

risposta

48

Creare un'immagine grande a cui scrivere. Elabora 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 ciclo attraverso le immagini e disegnarli:

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

Infine scriverlo fuori di file:

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

Oh uomo, questo è stato tremendamente più facile che provare ad usare JAI e Mosaic Descriptor. GRAZIE! – JamesD

3

Il formato PNG non supporta la piastrellatura, quindi non è possibile scappare almeno decomprimendo e ricomprimendo il flusso 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.)

Si potrebbe fare questo in modo streaming, avendo solo una "fila" aperta di PNG alla volta, leggendo blocchi di dimensioni appropriate dal loro flusso di dati e scrivendoli allo 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 utilizzando le utilità della 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.

0

si potrebbe essere la cosa migliore fuori rimbalzare le cose fuori un'altra immagine (lossless) formato. 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 utilizzare un convertitore standard (ad esempio ppm2png) che prende il formato intermedio e lo trasforma nel gigante PNG.

3

Come altri hanno sottolineato, l'utilizzo di Java non è necessariamente la soluzione migliore qui.

Se stai per utilizzare Java, la soluzione migliore è che, supponendo che la memoria sia sufficientemente breve, in modo che non sia possibile leggere l'intero set di dati in memoria più volte e quindi scriverlo di nuovo, è necessario implementare RenderedImage con una classe che leggerà i file 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). È possibile copiare le informazioni SampleModel e ColorModel dal primo PNG, si spera che siano tutte uguali.

Se fai finta che l'intera immagine è più tessere (una tessera immagine di origine per), quindi ImageIO.write creerà un WritableRaster che è la dimensione del l'intero set di dati di immagine, e richiederà l'implementazione di RenderedImage.copyData a riempirlo con dati. Se hai abbastanza memoria, questa è una strada facile da percorrere (perché ottieni un enorme set di dati di destinazione e puoi semplicemente riversare tutti i dati dell'immagine su di essa - usando il metodo setRect(dx,dy,Raster) - e poi non devi preoccuparti di nuovamente). 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), per il raster corrispondente a quell'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 15360x25600 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 piena 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 .

1

semplice script python per unire le piastrelle 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) 

Run:

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

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

6

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, è here.

Aggiornamento: Ecco un esempio di codice: montage

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

Questo è interessante ma il codice qui non è aggiornato con la versione corrente della lib. Qualche possibilità che tu abbia un esempio simile a quello attuale? Grazie molto. –

+0

@MattFriedman Hai provato quello pubblicato in "Snippet"? https://code.google.com/p/pngj/wiki/Snippets – leonbloy

+0

Il codice pubblicato nel link che hai fornito compila. Grazie molto. Forse vuoi andare avanti e scambiare il codice sopra con le nuove cose. Solo un pensiero. Grazie ancora. –

1

Usa di ImageMagick in questo modo:

montage *.png montage.png 

È possibile trovare maggiori informazioni sui parametri here

Buona fortuna

2

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

}