6/30 プログラミング(2)


前回までの補足


6/16の課題に関して


今回の目標


はいぱーワークブックの補足


画像ファイル

PPM形式に関しては,6/2の講義でも触れたが,もう 一度説明する.

PPM形式にはテキスト形式とバイナリ形式があるが,こ こではテキスト形式のみ扱う(convert コマンドで作成されるPPMファイルはバ イナリ形式の場合があるが,pnmnorawコマンドで変換できる).実際の例で見 てみる.

P3
4 4
255
  0   0   0    0   0   0    0   0   0  255   0 255
  0   0   0    0 255 127    0   0   0    0   0   0
  0   0   0    0   0   0    0 255 127    0   0   0
255   0 255    0   0   0    0   0   0    0   0   0
上の部分をt.ppmというファイルで保存して,
gimp t.ppm
でみると,幅4ドット,高さ4ドットの米粒のような画像が現れるがこれを zoom in していくと,
4x4.png
のような画像とわかる.このファイルの説明をする.
P3
の部分がマジックナンバーという部分で,このファイルが他の画像ファイルで
はなくPPM形式であることを示す(Windowsの多くのアプリケーションのように,
拡張子が .ppm でああることで判断しているわけではない).PPM形式では空白
(White Space, Spaceコードだけではなく改行,タブも含まれる)はいくついれ
ても良いが,P3の前に入れてはいけない.マジックナンバーの後に 空白
があり,
4 4 255
で,画像の幅,高さ,色の階調数を順に指定する.階調の最大値は,一般には
R(red), G(Green), B(Blue)それぞれ 0(暗い)から255(明るい)までの256階調
あれば人間に自然な画像を表せるといわれているので,ここでは 255 を指定
している.詳しくは, ラスタ画像の形式を参照すること.

その後で,

  0   0   0    0   0   0    0   0   0  255   0 255
  0   0   0    0 255 127    0   0   0    0   0   0
  0   0   0    0   0   0    0 255 127    0   0   0
255   0 255    0   0   0    0   0   0    0   0   0
のように,4x4=16ピクセルの RGB値を順に指定している.ピクセルの指定の順 番は横書きの文章と同じように左上から右下へ横方向に走査していく.1ピク セル内の指定の順番は R G Bの順番になっている.

上の例で現れる色は

  • (0,0,0) 黒
  • (255,0,255) RとBを混ぜた色なので紫(マゼンダ)
  • (0 255 127) Gに少しBを加えた色なので青目の緑
の3色である.

PPMファイルを出力するプログラム(グラデーション)

以下のプログラム
class Grad{
  public static void main(String[] args){
    int width=128, height=128;
    System.out.println("P3");
    System.out.println(width);
    System.out.println(height);
    System.out.println(255);
    int i,j;
    for(i=0;i<height;i=i+1){
      for(j=0;j<width;j=j+1){
        System.out.println(255);
        System.out.println(255);
        System.out.println(j*2);
      }
    }
  }
}
を ~/jousho04以下にGrad.java という名前で保存して,
javac Grad.java
とコンパイルして,
java Grad > grad.ppm
とプログラムを実行し,出力を grad.ppm というファイルに保存すると,この ファイルは PPM形式(Portable PixMap形式)の画像ファイルになっている.
convert grad.ppm grad.png
を実行して,png形式にしたのが
grad.png
である.

マンデルブロー集合を表示するプログラム

ある複素数 C を与えた時,
Z0=0
Zn+1=Zn*Zn+C
として, 複素数列{Zn}が定義できる.ここで,n->∞としたとき, |Zn|が発散しないようなCの集合をマンデルブロー集合という.

マンデルブロー集合は,集合の境界が複雑な形をしていて, 拡大をしても元の 図形と同じような複雑度を保っている.このような自己相似のことを総称して フラクタルと呼ぶが,マンデルブロー集合はフラクタルな図形の中ではもっと も有名である.

マンデルブロー集合は定義に無限回の繰り返しを含んでいるため, 計算機上 で正確に描くのは,困難である.そこで, 一般には

  • |Zn|がある値(たとえば 2)を超えたら発散と見なす.
  • 十分大きい回数(たとえば 50)繰り返しても|Zn|がある範囲 に収まっていれば,発散しないと見なす.
という近似を使って計算できる.以下は, この近似を用いてマンデルブロー集合を 計算するプログラムである(マンデルブロー集合は画像の黒い部分で表される).なお,「//」は注釈(コメント)の始まりを表す.

class Mandel{
  // 実数を引数(パラメタ)に持つメソッド mandel の定義
  static int mandel(double x, double y){
    double zr=0, zi=0,new_zr;
    int i;
    for(i=0;i<50;i=i+1){
      if(zr*zr+zi*zi>4.0) { // |Zi|>2.0 の時は発散と判断
        return i;
      }
       // 複素数の計算
       // (zr+zi*i)*(zr+zi*i)+x+y*i=(zr*zr-zi*zi+x)+(2*zr*zi+y)*i
      new_zr=zr*zr-zi*zi+x;
      zi=2*zr*zi+y;
      zr=new_zr;
    }
    return 50;
  }
  public static void main(String[] args){
    int width=256, height=256;
    System.out.println("P3");
    System.out.println(width);
    System.out.println(height);
    System.out.println(255);
    int i,j,r;
    double x, y;
    for(i=0;i<height;i=i+1){
      for(j=0;j<width;j=j+1){
           // -1.7 <= x < 0.7, -1.2 <= y < 1.2の範囲で計算
           // 整数(int)から実数(double)への変換は「(double)整数式」
           // のようにおこなう.
        x= -1.7+2.4*((double)j/(double)width);
        y= -1.2+2.4*((double)i/(double)height);
           // 0 <= r <= 50の値が返る
        r=mandel(x,y);
           // r=50の時に黒(0,0,0), r=0の時にほぼ白(250,250,250)とする.
        System.out.println(250-r*5);
	System.out.println(250-r*5);
	System.out.println(250-r*5);
      }
    }
  }
}
このプログラムを Mandel.java というファイルに保存して,
javac Mandel.java
java Mandel > mandel.ppm
と実行すると,mandel.ppm というファイルが得られる.これを
open mandel.ppm
で見ることもできるし,
convert mandel.ppm mandel.png
として png 形式に変換して,WWWブラウザで見ることもできる. できた mandel.png は以下のようになる.
mandel.png
        System.out.println(250-r*5);
	System.out.println(250-r*5);
	System.out.println(250-r*5);
の行を,たとえば
        System.out.println(((50-r)*71)%256);
	System.out.println(((50-r)*111)%256);
	System.out.println(((50-r)*97)%256);
とすると(「%」は剰余(mod)を表す演算子, 256で割った余りは 0-255になる),
mandel-color.png
のようなカラーの絵が得られる.

円や長方形を描くプログラム

class Draw{
    /**
     *
     * Math.sqrtを呼び出して平方根を計算できる
     */
    static double distance(int x0,int y0, int x1, int y1){
	int dx=x0-x1;
	int dy=y0-y1;
	return Math.sqrt(dx*dx+dy*dy);
    }
    /**
     * circleX 円の中心のX座標 
     * circleY 円の中心のY座標 
     * circleR 円の半径
     */
    static boolean inCircle(int x, int y, int circleX, int circleY, int circleR){
	if(distance(x,y,circleX,circleY) <= circleR) {
	    return true;
	}
	else {
	    return false;
	}
    }
    /**
     * rectangleX 矩形の左上X座標
     * rectangleY 矩形の左上Y座標
     * rectangleW 矩形の幅
     * rectangleH 矩形の高さ
     */
    static boolean inRectangle(int x, int y, 
			    int rectangleX, int rectangleY, 
			    int rectangleW, int rectangleH){
	if(rectangleX <= x &&
	   x <= rectangleX+rectangleW &&
	   rectangleY <= y &&
	   y <= rectangleY+rectangleH){
	    return true;
	}
	else {
	    return false;
	}
    }
    public static void main(String args[]){
	int width=256;
	int height=256;
	int x,y;
	System.out.println("P3");
        System.out.println(width);
        System.out.println(height);
        System.out.println(255);
	for(y=0;y < height;y++){
	    for(x=0;x < width;x++){
		/**
		 * 赤 r, 緑 g, 青 b をすべて0にすると黒
		 */
		int r=0, g=0, b=0;
		if(inRectangle(x,y,0,50,150,100)){
		    // 白みがかった暗い黄色
		    r=200; g=200; b=100;
		}
		if(inCircle(x,y,200,150,50)){
		    // 暗いマゼンダに
		    r=200; g=0; b=200;
		}
		if(inCircle(x,y,150,200,70)){
		    // 白を重ねる
		    r=r+100;
		    g=g+100;
		    b=b+100;
		    // 255までしか色を表現できないので,
		    // 255と小さい方の値を取ることにより,切り捨てる
		    // Math.minを呼び出して小さい方の値を取る
		    r=Math.min(255,r);
		    g=Math.min(255,g);
		    b=Math.min(255,b);
		}
		{
		    // (200,50)を中心にした緑のフレア効果
		    double len=distance(x,y,200,50);
		    g+=200*25/(len+20);
		    g=Math.min(255,g);
		}
		System.out.println(r);
                System.out.println(g);
                System.out.println(b);
	    }
	}
    }
}
このプログラムを実行した結果は,
となる.

sin カーブを描くプログラム

class Sin{
    public static void main(String args[]){
	int width=256;
	int height=256;
	int x,y;
	System.out.println("P3");
        System.out.println(width);
        System.out.println(height);
        System.out.println(255);
	for(y=0;y < height;y++){
	    for(x=0;x < width;x++){
		    // Math.sinを呼び出してsinを計算する
		double sinY=-Math.sin(x*0.03)*100.0+128.0;
		    // Math.absを呼び出して絶対値をとる
		    // (int)で整数型に変換する
		int dl=(int)Math.abs(y-sinY);
		int r,g,b;
		r=200*25/(dl+20);
		g=200*25/(dl+20);
		b=200*25/(dl+20);
		r=Math.min(255,r);
		g=Math.min(255,g);
		b=Math.min(255,b);
		System.out.println(r);
                System.out.println(g);
                System.out.println(b);
	    }
	}
    }
}
このプログラムを実行した結果は,
となる.

今日の課題

上のURLは,今日(6/30)の11:10 までは
Forbidden

You don't have permission to access /~ktanaka/jousho04/report630/index.html on this server.
とうメッセージが出てアクセ スできないはずである.11:10以降にも同様のエラーが出る時は,Shiftキーを 押しながら,再読み込み(Reload)を押してみること.

なお課題の締切りは7/21(水)の21:00である.