PImageに対して図形を直接描画する
Processingで画像処理のプログラムを書いていると、画像(PImage)に対して四角形や円を直接描画したいことがあります。ウィンドウに対する描画命令であるrect()やellipse()をPImageに対して使えたらいいのにねーという話です。間接的にそれを実現する方法について説明します。
サンプルコード
さっそくですが、こちらがその方法です。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; }
PGraphicsを仲介してPImageに対する図形描画を行う
ここで使われたトリックは、「PGraphics上で図形の描画処理を行った後、その結果でPImageのデータをすり替える」というものです。つまり、厳密にはPImageオブジェクトに対する直接の図形描画は行われていないのですが、すり替えによって「結果的に」それを実現しています。
PGraphicsはグラフィックスを扱うことができるクラスです。PGraphicsは、rect()やellipse()、stroke()やimage()などのおなじみの描画命令をメソッドとして持っており、自身に対する描画処理を記述することができます。また、PGraphicsはPImageの派生クラスなので、PImageと同様にwidthやheightやpixelsなどのフィールドを持っています。
具体的な処理手順はこうです。
- ターゲットのPImageオブジェクトと同サイズのPGraphicsオブジェクトをcreateGraphics()によって作成する。
- PGraphicsオブジェクトに対する描画処理を開始する(beginDraw())。
- PGrapchisオブジェクトに対してimage()で元のPImageオブジェクトを描画する。
- PGraphicsオブジェクトに対してなんらかの図形描画を行う。
- PGraphicsオブジェクトに対する描画処理を終了する(endDraw())。
- ターゲットのPImageオブジェクトのpixels(ピクセルデータ)をPGrapchisオブジェクトのpixelsで置き換える(もともとPImageが保有していたピクセルデータは参照されなくなり、ガベージコレクタの餌食に)。
このトリックのためにわざわざ新しいPGraphicsオブジェクトを作成するという無駄なことをやっているので、効率的にはあまり良い方法ではありません。効率を求めるならば最初からC++でOpenCVとかで書いた方が良いのですが、それはそれ、これはこれ。