web-dev-qa-db-fra.com

Comment combiner plusieurs PNG en un seul gros fichier PNG?

J'ai env. 6000 fichiers PNG (256 * 256 pixels) et souhaitez les combiner en un grand PNG contenant tous par programme.

Quelle est la meilleure façon/la plus rapide de le faire?

(Le but est d'imprimer sur du papier, donc l'utilisation d'une technologie Web n'est pas une option et le fait d'avoir un seul fichier d'image éliminera de nombreuses erreurs d'utilisation.)

J'ai essayé la suggestion de fahd mais j'obtiens un NullPointerException lorsque j'essaye de créer un BufferedImage avec 24576 pixels de large et 15360 pixels de haut. Des idées?

29
soc

Créez une grande image sur laquelle vous allez écrire. Calculez ses dimensions en fonction du nombre de lignes et de colonnes que vous souhaitez.

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

Maintenant, parcourez vos images et dessinez-les:

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

Enfin, écrivez-le dans un fichier:

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

J'ai eu un besoin similaire il y a quelque temps (des images énormes - et, dans mon cas, avec une profondeur de 16 bits - pour les avoir entièrement en mémoire n'était pas une option). Et j'ai fini de coder une bibliothèque PNG pour faire la lecture/écriture de manière séquentielle. Si quelqu'un le trouve utile, c'est ici .

Mise à jour: voici un exemple de 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");
        }
}
8
leonbloy

Je ne vois pas comment cela serait possible "sans traitement et recodage". Si vous insistez pour utiliser Java alors je vous suggère simplement d'utiliser JAI (page du projet ici =). Avec cela, vous feriez créez une grande BufferedImage , chargez des images plus petites et dessinez-les sur la plus grande .

Ou utilisez simplement ImageMagickmontage:

montage *.png output.png

Pour plus d'informations sur montage, voir sage .

6
Neeme Praks

Comme d'autres l'ont souligné, utiliser Java n'est pas nécessairement le meilleur pari ici.

Si vous allez utiliser Java, votre meilleur pari - en supposant que vous êtes suffisamment à court de mémoire pour que vous ne puissiez pas lire l'ensemble de données en mémoire plusieurs fois, puis le réécrire - est d'implémenter RenderedImage avec une classe qui lira vos PNG sur le disque à la demande. Si vous venez de créer votre propre nouvelle BufferedImage, puis essayez de l'écrire, le rédacteur PNG créera une copie supplémentaire des données. Si vous créez votre propre RenderedImage, vous pouvez le transmettre à ImageIO.write(myImageSet,"png",myFileName). Vous pouvez copier les informations SampleModel et ColorModel à partir de votre premier PNG - j'espère qu'elles sont toutes identiques.

Si vous prétendez que l'image entière est constituée de plusieurs tuiles (une tuile par image source), alors ImageIO.write Créera un WritableRaster qui correspond à la taille de l'ensemble des données d'image et appellera votre implémentation de RenderedImage.copyData pour le remplir de données. Si vous avez suffisamment de mémoire, c'est un moyen simple (car vous obtenez un énorme ensemble cible de données et pouvez simplement y vider toutes vos données d'image - en utilisant la méthode setRect(dx,dy,Raster) - puis pas besoin de s'en inquiéter à nouveau). Je n'ai pas testé pour voir si cela économise de la mémoire, mais il me semble que cela devrait.

Alternativement, si vous prétendez que l'image entière est une seule tuile, ImageIO.write Demandera alors, en utilisant getTile(0,0), le raster qui correspond à cette image entière. Vous devez donc créer votre propre Raster, ce qui à son tour vous fait créer votre propre DataBuffer. Lorsque j'ai essayé cette approche, l'utilisation minimale de la mémoire qui a réussi à écrire un PNG RVB 15360x25600 était -Xmx1700M (En Scala, d'ailleurs), ce qui est à peine supérieur à 4 octets par pixel d'image écrite, il y a donc très peu de surcharge au-dessus une image complète en mémoire.

Le format de données PNG lui-même n'est pas celui qui nécessite l'image entière en mémoire - cela fonctionnerait bien en morceaux - mais, malheureusement, l'implémentation par défaut du rédacteur PNG suppose qu'il aura la totalité du tableau de pixels en mémoire.

3
Rex Kerr

Le format PNG ne prend pas en charge le tuilage, il n'y a donc aucun moyen d'échapper au moins à la décompression et à la recompression du flux de données. Si les palettes de toutes les images sont identiques (ou toutes absentes), c'est la seule chose que vous devez vraiment faire. (Je suppose également que les images ne sont pas entrelacées.)

Vous pouvez le faire en streaming, en n'ayant ouvert qu'une "ligne" de fichiers PNG à la fois, en lisant des morceaux de taille appropriée dans leur flux de données et en les écrivant dans le flux de sortie. De cette façon, vous n'auriez pas besoin de conserver des images entières en mémoire. Le moyen le plus efficace serait de le programmer vous-même en plus de libpng. Vous devrez peut-être conserver un peu plus d'une ligne de balayage de pixels en mémoire en raison de la prédiction des pixels.

Mais le simple fait d'utiliser les utilitaires de ligne de commande d'ImageMagick, netpbm ou similaire vous permettra d'économiser une grande quantité de temps de développement pour ce qui peut être peu de gain.

3
gpvos

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

}
2
Yash

simple python pour joindre les tuiles en une grande image:

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 )

courir:

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

fonctionne pour les fichiers nommés comme% ZOOM_% XCORD_% YCORD.png, par exemple 15_18188_11097.png

1
Mateusz Rudnicki

Il vaut peut-être mieux faire rebondir les choses sur un autre format d'image (sans perte). PPM est très facile à utiliser (et à insérer des tuiles par programme; c'est juste un grand tableau sur le disque, vous n'aurez donc qu'à stocker une ligne de tuiles au maximum), mais c'est très gaspilleur d'espace (12 octets par pixel!).

Utilisez ensuite un convertisseur standard (par exemple ppm2png) qui prend le format intermédiaire et le transforme en PNG géant.

1
Alex Feinman

Utilisez le montage imagemagick comme ceci:

montage *.png montage.png

Vous pouvez trouver plus d'informations sur les paramètres ici

Bonne chance

1
by0