Hatena::ブログ(Diary)

Imaginable Reality

2016-10-30

PImageに対して図形を直接描画する

 Processingで画像処理のプログラムを書いていると、画像(PImage)に対して四角形や円を直接描画したいことがあります。ウィンドウに対する描画命令であるrect()やellipse()をPImageに対して使えたらいいのにねーという話です。間接的にそれを実現する方法について説明します。

f:id:kougaku-navi:20161030144808p:image

左:元の画像、右:元の画像に対して四角形を書き込んだ画像

サンプルコード

 さっそくですが、こちらがその方法です。drawRect()という関数によってあたかもPImageオブジェクトに四角形が書き込まれたように見えますが、どうなっているのでしょうか。

PImage img_test;   // 元画像
PImage img_result; // 結果画像

void setup() {
  size(805, 245);
  img_test = loadImage("cat.jpg");  // ファイルから元画像を読み込み
} 

void draw() {
  background(255);

  // 結果用画像に元の画像をコピー(結果比較用に元の画像を保持するため)
  img_result = img_test.get();  

  // 画像に対する描画処理  
  drawRect( img_result, 200, 60, 120, 120 );

  // 元画像と結果画像を表示
  image( img_test, 0, 0 );
  image( img_result, 405, 0 );
}

// PImage型の画像に対して四角形に描画する関数
void drawRect(PImage img_out, int x, int y, int w, int h) {

  // 対象の画像と同じサイズのPGraphicsオブジェクトを作る
  PGraphics pg = createGraphics( img_out.width, img_out.height );

  pg.beginDraw();            // 描画開始
  pg.image( img_out, 0, 0 ); // 対象の画像を描画
  pg.noFill();               // 塗りなし
  pg.stroke(255, 0, 0);      // 線の色
  pg.strokeWeight(4);        // 線の太さ
  pg.rect( x, y, w, h );     // 四角形の描画
  pg.endDraw();              // 描画終了

  // 対象の画像のピクセルデータをPGraphicsオブジェクトのピクセルデータと置き変える
  img_out.pixels = pg.pixels;
}

f:id:kougaku-navi:20161030144811j:image

コードで使用しているサンプル画像はこちらです。

cat.jpgという名前で保存して使ってください。


PGraphicsを仲介してPImageに対する図形描画を行う

 ここで使われたトリックは、「PGraphics上で図形の描画処理を行った後、その結果でPImageのデータをすり替える」というものです。つまり、厳密にはPImageオブジェクトに対する直接の図形描画は行われていないのですが、すり替えによって「結果的に」それを実現しています。

 PGraphicsはグラフィックスを扱うことができるクラスです。PGraphicsは、rect()やellipse()、stroke()やimage()などのおなじみの描画命令をメソッドとして持っており、自身に対する描画処理を記述することができます。また、PGraphicsはPImageの派生クラスなので、PImageと同様にwidthやheightやpixelsなどのフィールドを持っています。

具体的な処理手順はこうです。

  1. ターゲットのPImageオブジェクトと同サイズのPGraphicsオブジェクトをcreateGraphics()によって作成する。
  2. PGraphicsオブジェクトに対する描画処理を開始する(beginDraw())。
  3. PGrapchisオブジェクトに対してimage()で元のPImageオブジェクトを描画する。
  4. PGraphicsオブジェクトに対してなんらかの図形描画を行う。
  5. PGraphicsオブジェクトに対する描画処理を終了する(endDraw())。
  6. ターゲットのPImageオブジェクトのpixels(ピクセルデータ)をPGrapchisオブジェクトのpixelsで置き換える(もともとPImageが保有していたピクセルデータは参照されなくなり、ガベージコレクタの餌食に)。

 このトリックのためにわざわざ新しいPGraphicsオブジェクトを作成するという無駄なことをやっているので、効率的にはあまり良い方法ではありません。効率を求めるならば最初からC++でOpenCVとかで書いた方が良いのですが、それはそれ、これはこれ。