私は約6000個のPNGファイル(256 * 256ピクセル)で、それらをすべてプログラムで保持する大きなPNGに結合したい。
それを行うための最良/最速の方法は何ですか?
(目的は紙に印刷することであるため、一部のWebテクノロジーを使用することはオプションではなく、1つの単一の画像ファイルがあれば、多くの使用エラーがなくなります。)
Fahdの提案を試しましたが、幅24576ピクセル、高さ15360ピクセルのNullPointerException
を作成しようとすると、BufferedImage
が表示されます。何か案は?
書き込む大きなイメージを作成します。必要な行と列の数に基づいて、ディメンションを決定します。
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"));
しばらく前に、同様のニーズがいくつかありました(巨大なイメージ-そして、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");
}
}
「処理と再エンコードなしで」どのように実現できるかわかりません。 Javaを使用することを主張する場合は、 [〜#〜] jai [〜#〜] (プロジェクトページ ここ =)。それにより、 1つの大きなBufferedImageを作成 、 より小さい画像をロード 、および 大きい画像に描画 になります。
または、単に ImageMagickmontage
を使用します。
montage *.png output.png
montage
の詳細については、 sage を参照してください。
他の人が指摘したように、ここでJavaを使用することは必ずしも最善の策ではありません。
Javaを使用する場合は、データセット全体をメモリに複数回読み取ってから再度書き込むことができないようにメモリが十分に不足していると仮定して、RenderedImage
は、必要に応じてディスクからPNGを読み取るクラスを持ちます。独自の新しいBufferedImageを作成してから書き出そうとすると、PNGライターはデータの追加コピーを作成します。独自のRenderedImageを作成する場合、ImageIO.write(myImageSet,"png",myFileName)
に渡すことができます。最初のPNGからSampleModel
とColorModel
の情報をコピーできます-うまくいけば、それらはすべて同じです。
画像全体が複数のタイル(ソース画像ごとに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ライターのデフォルトの実装では、メモリ内にピクセル配列全体があると想定しています。
PNG形式はタイリングをサポートしていないため、少なくともデータストリームの圧縮解除と再圧縮をエスケープする方法はありません。すべての画像のパレットが同一(またはすべて不在)の場合、これが本当に必要な唯一のことです。 (画像がインターレースされていないことも想定しています。)
これはストリーミング方式で行うことができ、一度に1行のPNGのみを開き、データストリームから適切なサイズのチャンクを読み取り、出力ストリームに書き込みます。この方法では、画像全体をメモリに保持する必要はありません。最も効率的な方法は、libpngの上でこれを自分でプログラムすることです。ピクセルの予測のため、メモリ内にピクセルのスキャンラインをわずかに複数保持する必要がある場合があります。
ただし、ImageMagickやnetpbmなどのコマンドラインユーティリティを使用するだけで、ほとんど利益が得られない場合でも開発時間を大幅に節約できます。
画像の結合
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);
}
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)
別の(損失のない)画像形式からバウンスすることをお勧めします。 [〜#〜] ppm [〜#〜] は非常に使いやすい(およびプログラムでタイルを配置するのは簡単です。ディスク上の単なる大きな配列なので、1行だけを格納する必要があります)最大でタイル)、しかしそれは非常にスペースの無駄です(ピクセルあたり12バイト!)。
次に、標準のコンバーターを使用します(例:ppm2png
)中間形式を取り、それを巨大なPNGに変換します。