Processing覚書
Amebaでブログを始めよう!

画像を扱う

今回は画像を扱います。


画像が使えると、何かと便利ですよね。




画像の扱い


P5で画像は一律にPImageクラスで扱います。


PImageクラスはメンバ変数として、width, height, pixel[]を持っています。


widthとheightは同名の予約変数と似ています。


画像の幅、高さが格納されています。



pixel配列は画像のピクセル単位のデータが配列が格納されています。


指定されたピクセルの色情報を得たり、色を設定する場合は専用のメソッドが用意されています。


pixel配列を無理に使う必要はありませんが、画像の多くの位置を参照する場合は、メソッドを利用するとオーバーヘッドが大きく、処理速度が低下しますので、そのような場合利用すると良いのではないでしょうか。



PImageクラスには6つのメソッドが用意されています。


とりあえず、getメソッドとsetメソッドだけ覚えていれば良いと思います。


使い方は前回のget関数と同じです。




画像を読み込む


PImageクラスに画像を読み込むにはどうしたらよいのでしょうか。


loadImage関数を利用します。


loadImage関数は指定されたファイル名の画像を、PImageとして返す関数です。


対応している画像形式は、.gif、.jpg、.tgaそして.pngです。




それでは早速サンプルを見てみましょう。


PImage img;
int pointnum = 50 * 50;
int maxradius = 25;
int maxamplitude = 5;

dot p[];

void setup()
{
  img = loadImage("wara.jpg");
  size(img.width, img.height);
  
  p = new dot[pointnum];

  for(int i = 0; i < pointnum; i++){
    p[i] = new dot((int)random(img.width),(int)random(img.height),(int)random(maxradius));
    p[i].setColor(img.get(p[i].pos.x, p[i].pos.y));
  }
}

void draw()
{
  background(0);
  
  for(int i = 0; i < pointnum; i++){
    p[i].drawdot((int)((mouseX / (float)img.width) * maxamplitude));
  }
}

void mousePressed()
{
  for(int i = 0; i < pointnum; i++){
    p[i] = new dot((int)random(img.width),(int)random(img.height),(int)random(maxradius));
    p[i].setColor(img.get(p[i].pos.x, p[i].pos.y));
  }
}

class Position{
  int x,y;
};

class dot{
  Position pos;
  int radius;
  color col;
  
  public dot(int _x, int _y, int _rad){
    pos = new Position();
    col = color(0,0,0);
    pos.x = _x;
    pos.y = _y;
    radius = _rad;
  }
  
  public void setColor(color _col){
    col = _col;
  }
  
  public void drawdot(int amplitude){
    noStroke();
    fill(col);
    
    ellipse((pos.x - radius/2)+random(-amplitude, amplitude),
            (pos.y - radius/2)+random(-amplitude, amplitude), radius, radius); 
  }
};

こんな感じのプログラムを作ってみました。


このプログラムは、画像を読み込み、・ランダムな位置の色情報を読み取り、ランダムなサイズの円を描くプログラムです。


それだけだとつまらないので、マウスの位置によってちょっと震えるようにしてます。


今回使用した画像は工学ナビ さんのところの、攻殻機動隊で登場した「笑い男」の画像を利用させてもらいました。


画像が少々大きかったので、勝手に解像度を変更させてもらい、こんな具合にしました。

wara

この画像ファイルを"wara.jpg"というファイル名にして、ソースファイルと同じフォルダに保存すればプログラムが実行されるはずです。



今回のプログラムで肝の部分は


img.width img.heightやimg.get()等のPImageのメンバ変数やメソッドの使い方です。


複数の画像を扱いたい場合は、PImageのオブジェクトを必要な分だけ作って、それぞれのメンバに対して実行してください。


例えばPImage img1,img2という二つのオブジェクトを作りたい場合

img1 = loadImage("file1");

img2 = loadImage("file2");

と二つロードしたり


画像1の画素を画像2の同じ画素に上書きしたい場合は

color col1 = img1.get(x1, y1);

img2.set(x1, y1);


なんて具合で、ちゃんとオブジェクトを切り分けて考えてください。


大学のJAVAやC++の演習をお手伝いしていると、この考えができない人が結構います。


落ち着いて考えれば、全く難しくないので頑張りましょう。



今回画像の画素だけを利用しましたが、読み込んだ画像を表示することはもちろんできます。


image関数を利用すれば良いのです。簡単ですので、リファレンスを見てください。


近いうちにimage関数を利用したサンプルを掲載する予定です。



それでは上のサンプルの実行結果です。

getcolor

このようになりました。


今回は50x50=2500個の円を表示しています。


さすがに動作が遅くなりますよね。


そこで今回はちょっとOpenGLを使って高速化を図りたいと思います。


といっても、非常に簡単です。




プログラムの一行目に


import processing.opengl.*;

と書いて、ウインドウをサイズを決めるsize関数の3つ目の引数にOPENGLと追加するだけです。


今回のサンプルだと


size(img.width, img.height);

size(img.width, img.height, OPENGL);


とするだけです。


OpenGLではグラフィックボードを利用して高速に描画するライブラリですので、グラフィックボードが遅い場合高速化は望めません。


が、大概の状況では高速化するのではないでしょうか。


OpenGLで描画が高速化される状況は検証中ですが、とりあえず

・beginShape関数で表現できる形状

・ellipse関数

は高速化されました。


ほとんどの場合いけるのでしょうかね?


今後検証したいと思います。

ネットワークっぽい表現をしてみる

ネットワークを利用した、アート作品でよくグラフを利用してネットワークを可視化します。


インターネットなどは複雑なネットワークなので、今回は単純なリスト状のネットワークを表現します。




グラフって何?って人は


wikipedia - グラフ理論

http://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%A9%E3%83%95%E7%90%86%E8%AB%96

の、一番最初の図だけ見てください。理論は今回は気にしないで。



今回のリストは、リストの中でも最も簡単な、次の情報しかもたない有向グラフです。


図にすると

NODE2

こんな感じで、次のノード(四角)の情報をもっていて、それがどんどん繋がります。


今回ノードには座標しか入れていません。面倒なので。


座標以外にも、ノードの色の情報とか色々詰め込むと、表現の幅が広がるのではないでしょうか。




では、さっさとソースを晒します。


int nodenum = 20;
int wid = 512;
int hei = 512;
int rwid = 10;
int rhei = 10;

Node node;
Node sentinel;

void setup()
{
  size(wid, hei);
  
  node = new Node((int)random(width), (int)random(height));
  sentinel = node;
  
  for(int x = 0; x < nodenum; x++){
    node.insertNode((int)random(width), (int)random(height));
  }  
}

void draw()
{
  background(0, 0, 0);

  fill(250, 0, 0);
  
  sentinel = node;
  Position pos,tmppos;
  
  pos = node.getNodePosition();
  rect(pos.x - rwid/2, pos.y - rhei/2, rwid, rhei);
  tmppos = pos;
  for(int x = 0; x < nodenum; x++){
    if(sentinel.childe != null){
      pos = sentinel.childe.getNodePosition();
      sentinel = sentinel.childe;
     
      stroke(0,0,200);
      line(tmppos.x, tmppos.y, pos.x, pos.y);
      noStroke();
      rect(pos.x - rwid/2, pos.y - rhei/2, rwid, rhei);
    
      tmppos = pos;
    } else {
      break;
    }
  }
}

void mousePressed()
{
  node = new Node((int)random(width), (int)random(height));
  
  for(int x = 0; x < nodenum; x++){
    node.insertNode((int)random(width), (int)random(height));
  }  
}

class Position{
  public int x,y;
};

class Node{
  Position pos;
  
  Node childe;
  
  public Node(){
    pos = new Position();
    pos.x = -1;
    pos.y = -1;
  }
  
  public Node(int _x, int _y){
    pos = new Position();
    pos.x = _x;
    pos.y = _y;
  }
  
  public void setNodePosition(int _x, int _y){
    pos.x = _x;
    pos.y = _y; 
  }
  
  public Position getNodePosition(){
    return pos;
  }
  
  public void insertNode(int _x, int _y){
    if(childe == null){
      childe = new Node(_x,_y);
    } else {
      this.childe.insertNode(_x,_y);
    }
  }
};

実行結果です。

NODE1

このようになりました。


ソースの説明は…(´Д`)メンドクセ


Positionクラスは座標を保持するクラス。


Nodeクラスは座標と次のノードを保持するクラス。


あとは、自力でなんとかして。

大型連休

大型連休中には一度くらい更新したいな。

セミナーIII

セミナーIIIの一部の方が見ているようですね。


ここに掲載するレベルを上げて、ハードル上げてやろうw

前の画像を記憶する

「マウスを使う」では、線を描くたびに画面をクリアしていたため、一本の線しか描けませんでした。


これではいくらやっても絵は描けませんよね。


そこで今回はこの問題をクリアします。




「マウスを使う」のソースコードではドラッグする度、またリリースする度に画面を白くしていました。


一見この”画面を白くする”という行為をやらなければ、事が足りる気がします。


しかし、この画面をクリアしないければならないのです。


それはなぜかと言うと…


drawline2_2

このように、ドラッグする度に新しい線を上書きしてしまうのです。




そこで、この問題をどうやって解決したらいいのか。


想像できるとは思いますが、線を描いたときの画面を保存しておけばよいのです。



やり方は簡単。リファレンスに書いてあるget関数を利用すれば良いのです。


get関数を利用すると、PImageクラスのオブジェクトとして記憶することができます。


ちょっと専門用語でしたが、ようするにPImageの変数を用意しておけばget関数が利用できるということ。


get関数はPImageのメソッドでもあるので、PImage間のコピーにも利用されます。




ではでは、このことを踏まえて改造したソースコードを晒してみます。



int x1,y1;
int x2,y2;

PImage img;

void setup()
{
  size(300,300);
  framerate(30);
  
  img = new PImage(width, height);
  
  background(255);
  img = get();
  stroke(0);
}

void draw()
{
}

void mousePressed()
{
  x1 = mouseX;
  y1 = mouseY;
}

void mouseDragged()
{
  background(img);
  line(x1,y1,mouseX,mouseY); 
}

void mouseReleased()
{
  x2 = mouseX;
  y2 = mouseY;
  
  background(img);
  line(x1, y1, x2, y2);
  img = get();
}

はい。このようになりました。


グローバルにPImageの変数を用意しておきます。


setup関数内で、PImageを画面の大きさで生成し、画面を白くクリアした状態でget関数を利用して初期化完了です。



マウスがドラッグされる度に、保存しておいた画像でバックグラウンドをクリアします。



リリースされた時は、バックグラウンドをクリアし、線を描き、画面を保存します。


こうしておけば、リリースした時=線を確定したときなので、間違いなく正しい画面を保存することができます。



実行結果です。

drawline2_1

実行形式のファイルに書き出す

P5でプログラムを作った場合、P5を起動してから実行しなければなりません。


実は、標準機能で、実行形式のファイルを書き出すことができます。



ちょっとした、トリビアですよね。


では、さっそくやり方を説明します。


  1. プログラムを作成し、スケッチを保存します
  2. メニューの、File→ExportApplicationを選びます
  3. スケッチと同じフォルダに生成されます

以上の3段階で終了です。


export

生成すると、このように三つディレクトリが出来ています。


自分が実行したいOSを選んでみましょう。


すると、実行形式のファイルが生成されているはずです。




こうすることによって、P5をインストールしていない人にも、自分の作品が渡せますね。


ちなみに、P5で読み込むファイル(画像、音、etc…)はコピーされていないので、自分の手でコピーしましょう。

マウスを使う

今回はマウスを使い方の覚書です。




マウスを使う上で、頭に入れてほしいのは以下の4つです。


・マウスの座標

・マウスが押された(イベント)

・マウスがドラッグされた(イベント)

・マウスが離された(イベント)


これらがあれば、マウスを扱う上で十分です。


そしてP5でのマウスの扱いは、これが全てです。



それではもう例題を出しちゃいましょう。


今回作るプログラムは、クリックした点から、離した点まで線を描くプログラムです。



int x1,y1;
int x2,y2;

void setup()
{
  size(300,300);
  framerate(30);
  background(255);
  stroke(0);
}

void draw()
{
}

void mousePressed()
{
  x1 = mouseX;
  y1 = mouseY;
}

void mouseDragged()
{
  background(255);
  line(x1,y1,mouseX,mouseY); 
}

void mouseReleased()
{
  x2 = mouseX;
  y2 = mouseY;
  
  background(255);
  line(x1, y1, x2, y2);
}

ちょっと長いですね。


それでは、上から順に説明しましょう。


x1,y1はマウスが押された瞬間の座標を入れる変数です。


x2,y2はマウスが離された瞬間の座標を入れる変数です。



mousePressed関数はマウスが押された瞬間に呼ばれる関数です。


今回この関数のなかでは座標を記憶します。


x1=mouseXとなっていますよね。


mouseXっていう変数は予約された変数です。このなかにマウスのX座標が記録されています。


mouseYも同じようにY座標が記録されています。


その値を変数に記録しています。




mouseDragged関数は、マウスのボタンが押され続けている状態のときに呼ばれる関数です。


今回はドラッグ中は、仮の線を書いておきたいのでline(x1,y1,mosueX,mouseY);として、現在のマウスの座標と、記憶しておいた始点の座標で線を描きます。




mouseReleased関数は、マウスのボタンが離された瞬間に呼ばれる関数です。


今回のプログラムでは話された瞬間に、終点が決定されるので、x2,y2に記録しておきます。


その後に、line関数によって線を描きます。



今回大したことはしなかった、draw関数ですが、mousePressed関数のようなイベント関数を利用する場合必須のようです。


とりあえず入れておきましょう。


MOUSE


これでやっと作品にとりかかれそうです。

アニメーションの基礎

色々すっとばして、アニメーションに入ります。


言語の構文とかはJAVAと同じだと思う…ので…あはは。




アニメーションは描画を繰り返し行うことによって実現します。


具体的には


1. 背景を描く


2. 物体を描く


の繰り返しです。


なぜ背景を先に描くかというと、描画はどんどん上書きをするからです。


物体も奥にあるものから先に描かなければ、上書きされてしまうので注意が必要です。



ではでは、とりあえずアニメーションの基礎をやってみましょう。



void setup()
{
  size(300, 300);
  framerate(30);
}

void draw()
{
  background(255);
}

これが、アニメーションを行ううえで一番シンプルな形じゃないかな?


それでは、はじめて出てきた関数の説明をしましょう。


・setup関数

この関数はP5を実行すると一回だけ実行されます。

初期設定などをここでやりましょう。

size関数や、アニメーション描画レートを設定する関数framaerate関数などはここで実行しましょう。


・draw関数

この関数は適当なタイミングで実行される関数です。

ここにアニメーションの内容を書きます。



framerate関数は、アニメーションの一秒間あたりの書き換え回数を指定する関数です。


background関数は指定した色で、背景を塗りつぶします。




さて、これだけ実行しても、白い画面が出てきてアニメーションしてるんだかわかりませんよね。


適当なオブジェクトを動かしてアニメーションしてみましょう。



int x;
int y;
int wid;
int hei;

void setup()
{
  size(300, 300);
  framerate(30);
  
  x = 0;
  y = 140;
  wid = hei = 20;
}

void draw()
{
  background(255);
  
  if(x>width) x = 0 - wid;
  rect(x,y,wid,hei);  
  x = x + 10;
}

はい。これで四角が左から右へとループしますね。


ANIM


draw関数が1/30秒に一回呼び出されます。


そのたびに矩形の座標を、ずらすことによってアニメーションさせてます。



ここで注意!!


draw関数内で、矩形の座標の変数を宣言しないこと!!


すると、毎回変数が宣言されてしまい、初期の値から変化しません。


だから、グローバルな位置(関数の外)に作っておきましょう。


それだけ。




アニメーション簡単でしょ。


ここで、背景を描画しないと…


ANIM2

こんな感じで、どんどん上書きされちゃうから、背景の描画を忘れないようにしましょう。

描画エリアサイズの変更

いつまでも100x100の描画エリアじゃ狭いですよね。


そこで、今回は描画エリアのサイズを変更したいと思います。


と言っても非常に簡単。



size(200, 100);


とやるだけで200x100の描画エリアに大変身。


めでたしめでたし。






なんですが、ちょっとこれだけじゃあ寂しいので、便利機能を一つ。


P5では変数も予約されていて、色々な値を保持しています。


今回の描画エリアのサイズも、実はバッチリwidthheightという予約変数に保持さています。


ですから、描画エリアのサイズを元に、相対的に位置を指定していれば、サイズが変わっても自動的に対処が可能なのです。


描画サイズを変更するたびに、全部の座標を書き直してたら日が暮れちゃいますからね。



ではでは、サンプルを



line( 0, 0, 100, 100) ;

これを実行すると


drawline1


このようになりますよね。


そこで、描画エリアのサイズを変更してみましょう。



size(200,100);

line( 0, 0, 100, 100) ;

window2

このように、描画エリアのサイズを変更すると、端から端まで線を引きたいのに、引く事ができません。


頑張ってsize関数を変更する度に、line関数の値を書き換えなくてはいけませんね。



そこで、widthとheightを使ってみましょう。



size(200,100);

line( 0, 0, width, height) ;

window1

このように、見事斜線を描く事ができました。


widthとheightを使えばsize関数を使っても、自動的に値が更新されるので、余計な手間が省けます。



このようにP5では様々な、予約変数が存在しています。


代表格としては、マウスの位置を保持している変数でしょうか。


とにかくお世話になります。



マウスの話はまたの機会に。

変数を使う

P5では座標などの情報を変数に蓄えておくことができます。


変数を使うと、座標だとか、高さだとか色々覚えておかなくて楽チンです。


例えば



int x;

int y;

int wid;

int hei;


x = y = 10;

wid = 80;

hei = 70;


rect(x,y,wid,hei);

このように値を保存しておく事ができます。


x,yは矩形のx,y座標。


widは幅、heiは高さとしました。


変数の名前は基本的に自由です。


ですが、最初の一文字目が数字や記号ではNGです。(例 1x→× x1→○)


あと、予約語とかありますが、それまた別に説明します。


アルファベットで名前をつけてやるのがベターですね。


VAR1


実行するとこのようになります。


rect(x,y,wid,hei);


となっています。


これを変数の中身で書き直してみると…


rect(10,10,80,70);


となるわけです。結果と一致していますよね。


もちろん、rect(10,10,80,70);と書き直しても同じ結果になります。


疑い深い人は書き直してみましょう。




ところで、変数を使う場合”宣言”をしてやらねばなりません。


運動会で、選手宣誓とかしますよね。あんな感じです。


int x;


これはint(整数の値を扱う変数)xをここに宣言します!!


って言っているのです。


(あ、intって予約語です。意味のある言葉はP5で予約されていて、既に意味をもっています。)


(intって入れれば色が変わるからわかるかな?)


(他にも色々あるので、リファレンスを眺めてみましょう。)


y,wid,heiも同じintで、整数を扱います。


説明が段々面倒になってきたので、JAVAの本や、webサイトを見てください。





しかし、これでは変数を使う恩恵にあやかれていません。


変数は値を保存することができるのですから、複雑な計算をさせてやった答えも覚えてくれるのです。


沢山の数なんて覚えておけませんからね。



int x;

int y;

int wid;

int hei;


x = y = 10;

wid = 80;

hei = 70;


x = x + y;

wid = wid -13;

hei = hei %20;


rect(x,y,wid,hei);

このように計算することも可能になるのです。


実行するとこのようになります。

VAR2


xはxとyの値を足した結果を保存しているので、x座標が20になったのが確認できます。


幅のwidは13引かれているので、そのぶん幅が縮みました。


高さheiはみなれない%という記号が使われています。


数学ではこの記号は100分率と扱いましたが、P5では余りを計算する記号となります。


heiを20で割った余りをheiに代入しているのです。


ここで、heiは70だったので、余りは10となり、10が代入され高さが10となりました。




変数を扱うと様々な計算ができたりします。


また、覚え切れない数なども入れておくことができて便利ですね。


この性質を利用すると、アニメーションを行うときに非常に便利です。


それもまた別のお話…。