Imaginary Code

from kougaku-navi.net

Processingにおけるの画像(PImage)のコピー

【2016/10/25修正】

 Processingにおいて画像はPImage型で扱われます。特に画像処理をやるようなケースにおいて、画像のコピーを行いたいことが多々あります。その場合に注意すべきことをここに書いておきます。

PImageのコピー

 画像をコピーしたいときうっかり以下のようにやりがちですが、よく考えずにこれをやるとちょっと困ったことになります。

PImage src = loadImage("lenna.jpg"); // srcにファイルから画像を読み込み
PImage dst = src; // srcをdstにコピーしたつもり

「dst = src」でコピーされるのはデータの実体ではなく、データがメモリ上のどこにあるのかという参照値です。つまりdstもsrcもメモリ上で同じデータを参照していることになるので、このあとにdstに対してピクセルデータを書きかえる処理を行うと、srcのピクセルデータも書き変わってしまいます。

 PImage型のデータのコピーを作りたいときは、get()というメソッドを使います(Processing3以降はcopy()でも可)。

PImage src = loadImage("lenna.jpg"); // srcにファイルから画像を読み込み
PImage dst = src.get(); // srcのコピー(新しく生成)をdstに代入

2行目が意味するところは「srcをコピーして新しいオブジェクトを生成し、そこへの参照をdstという変数に入れる」です。ここで注意すべきは、dstという変数にもともと別な値(別なオブジェクトへの参照)が入っていた場合には、それが代入によって失われるという点です。

PImage型変数間でのピクセルデータのコピー

 2つのPImage型の変数があって、その一方から他方に向かってピクセルデータのみをコピーしたい場合はset()を使うとよいです。

PImage src = loadImage("lenna.jpg"); // srcにファイルから画像を読み込み
PImage dst = createImage( src.width, src.height, RGB ); // srcと同じサイズの画像を作成

dst.set( 0, 0, src ); // dstにsrcのピクセルデータを書き込み。0,0はオフセット。

PImage型の引数を持つ関数を作る場合

 もうひとつ例をみておきましょう。以下のコードは「copyImage()という自作の関数を用いて、img1のピクセルデータをimg2にコピーしたい」という意図で書かれたものですが、これはプログラマの期待どおりに動作しません。

PImage img1;
PImage img2;

void setup() {
  size( 800, 600 );
  img1 = loadImage("lenna.jpg"); // ファイルから画像を読み込み
  img2 = createImage( img1.width, img1.height, RGB ); // img1と同サイズの空の画像を作成
}

void draw() {
  // img1のピクセルデータをimg2にコピーした(つもり)
  copyImage( img1, img2 );

  // img2を画面描画。img1と同じ画像が表示されるはずなんだけど、表示されない
  image( img2, 0, 0 );
}

// 入力画像(img_in)を出力画像(img_out)にコピーする関数(のつもり)
void copyImage( PImage img_in, PImage img_out ) {
  img_out = img_in.get();
}

 copyImage()という関数内で行われるのは、img_in.get()でコピーされて作られた新しいオブジェクトへの参照を、img_outという変数に入れるという処理なので、呼び出し元の変数img2の画像にはなんの影響ももたらしません。

 プログラマの意図は「img1のピクセルデータをimg2にコピーしたい」ということなので、以下のように書けば期待通り動作します。

void copyImage( PImage img_in, PImage img_out ) {
  img_out.set( 0, 0, img_in );
}