web-dev-qa-db-ja.com

複数のPNGを1つの大きなPNGファイルに結合する方法は?

私は約6000個のPNGファイル(256 * 256ピクセル)で、それらをすべてプログラムで保持する大きなPNGに結合したい。

それを行うための最良/最速の方法は何ですか?

(目的は紙に印刷することであるため、一部のWebテクノロジーを使用することはオプションではなく、1つの単一の画像ファイルがあれば、多くの使用エラーがなくなります。)

Fahdの提案を試しましたが、幅24576ピクセル、高さ15360ピクセルのNullPointerExceptionを作成しようとすると、BufferedImageが表示されます。何か案は?

28
soc

書き込む大きなイメージを作成します。必要な行と列の数に基づいて、ディメンションを決定します。

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

画像をループして描画します:

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

最後にファイルに書き出します:

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

しばらく前に、同様のニーズがいくつかありました(巨大なイメージ-そして、16ビット深度の私の場合-それらを完全にメモリに入れることはオプションではありませんでした)。そして、私はPNGライブラリのコーディングを終了して、読み書きをシーケンシャルに実行しました。誰かが便利だと思う場合、それは here です。

更新:サンプルコードは次のとおりです。

/**
 * 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

「処理と再エンコードなしで」どのように実現できるかわかりません。 Javaを使用することを主張する場合は、 [〜#〜] jai [〜#〜] (プロジェクトページ ここ =)。それにより、 1つの大きなBufferedImageを作成より小さい画像をロード 、および 大きい画像に描画 になります。

または、単に ImageMagickmontageを使用します。

montage *.png output.png

montageの詳細については、 sage を参照してください。

6
Neeme Praks

他の人が指摘したように、ここでJavaを使用することは必ずしも最善の策ではありません。

Javaを使用する場合は、データセット全体をメモリに複数回読み取ってから再度書き込むことができないようにメモリが十分に不足していると仮定して、RenderedImageは、必要に応じてディスクからPNGを読み取るクラスを持ちます。独自の新しいBufferedImageを作成してから書き出そうとすると、PNGライターはデータの追加コピーを作成します。独自のRenderedImageを作成する場合、ImageIO.write(myImageSet,"png",myFileName)に渡すことができます。最初のPNGからSampleModelColorModelの情報をコピーできます-うまくいけば、それらはすべて同じです。

画像全体が複数のタイル(ソース画像ごとに1つのタイル)であると想定した場合、_ImageIO.write_は画像データセット全体のサイズであるWritableRasterを作成し、実装を呼び出します_RenderedImage.copyData_にデータを入力します。十分なメモリがある場合、これは簡単な方法です(巨大なターゲットデータセットを取得し、setRect(dx,dy,Raster)メソッドを使用してすべての画像データをそこにダンプすることができるため)もう一度心配する必要があります)。私はこれがメモリを節約するかどうかをテストしていませんが、そうすべきだと思います。

あるいは、画像全体が単一のタイルであるふりをする場合、_ImageIO.write_はgetTile(0,0)を使用して、その画像全体に対応するラスターを要求します。そのため、独自のRasterを作成する必要があり、その結果、独自のDataBufferが作成されます。このアプローチを試したとき、15360x25600 RGB PNGを正常に書き込んだ最小メモリ使用量は_-Xmx1700M_(偶然Scalaで)であり、書き込まれた画像のピクセルあたり4バイトをわずかに超えているため、1つを超えるオーバーヘッドはほとんどありませんメモリ内の完全な画像。

PNGデータ形式自体は、メモリ内の画像全体を必要とするものではありません-チャンクで問題なく動作します-しかし、悲しいことに、PNGライターのデフォルトの実装では、メモリ内にピクセル配列全体があると想定しています。

3
Rex Kerr

PNG形式はタイリングをサポートしていないため、少なくともデータストリームの圧縮解除と再圧縮をエスケープする方法はありません。すべての画像のパレットが同一(またはすべて不在)の場合、これが本当に必要な唯一のことです。 (画像がインターレースされていないことも想定しています。)

これはストリーミング方式で行うことができ、一度に1行のPNGのみを開き、データストリームから適切なサイズのチャンクを読み取り、出力ストリームに書き込みます。この方法では、画像全体をメモリに保持する必要はありません。最も効率的な方法は、libpngの上でこれを自分でプログラムすることです。ピクセルの予測のため、メモリ内にピクセルのスキャンラインをわずかに複数保持する必要がある場合があります。

ただし、ImageMagickやnetpbmなどのコマンドラインユーティリティを使用するだけで、ほとんど利益が得られない場合でも開発時間を大幅に節約できます。

3
gpvos

画像の結合

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タイルを1つの大きな画像に結合するスクリプト:

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 )

実行:

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

%ZOOM_%XCORD_%YCORD.pngのような名前のファイルで機能します(例:15_18188_11097.png)

1

別の(損失のない)画像形式からバウンスすることをお勧めします。 [〜#〜] ppm [〜#〜] は非常に使いやすい(およびプログラムでタイルを配置するのは簡単です。ディスク上の単なる大きな配列なので、1行だけを格納する必要があります)最大でタイル)、しかしそれは非常にスペースの無駄です(ピクセルあたり12バイト!)。

次に、標準のコンバーターを使用します(例:ppm2png)中間形式を取り、それを巨大なPNGに変換します。

1
Alex Feinman

次のようにimagemagickのモンタージュを使用します。

montage *.png montage.png

パラメーターの詳細については、こちらをご覧ください こちら

幸運を

1
by0