/home08/ktanaka/bin/report531のようにすると,提出状況を確認できるが,
/home08/ktanaka/bin/report531 1とすると再提出になってしまう.期限前に提出したのに,誤って期限後に再提 出したという人は,本人のホームディレクトリのファイルの更新日付を見て, 期限前にできていたことを確認するので,自習時間中に田中に申告するように.
今回の課題では,プログラムで画像ファイルを出力するが,テキストファイと して扱えるPPM形式という画像ファイル形式を紹介する.実際の例で見てみる.
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 していくと,
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の順番になっている.上の例で現れる色は
他の形式の画像ファイルからppm形式の画像ファイルを作ることもできる.たとえば,HWBのロゴをファイルにhwb3.pngという名前でセーブして,
convert hwb3.png hwb3raw.ppm pnmnoraw hwb3raw.ppm > hwb3.ppmとして,logo3.ppm というファイルができる.これを見ると
P3 225 27 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 ...のように,1ピクセルのRGB値がテキスト形式で表されていることがわかる. このファイルを編集して(たとえば 255をすべて100に変更して),
convert hwb3.ppm hwb3new.gifのようにすると,gif形式にできる.
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){ // int r,g,b; r=255; // g=255; // b=j*2; // r=Math.min(255,Math.max(0,r)); // g=Math.min(255,Math.max(0,g)); // b=Math.min(255,Math.max(0,b)); // System.out.println(r); // R System.out.println(g); // G System.out.println(b); // B } } } }を ~/jousho05以下にGrad.java という名前で保存して,
javac Grad.javaとコンパイルして,
java Grad > grad.ppmとプログラムを実行し,出力を grad.ppm というファイルに保存すると,この ファイルは PPM形式(Portable PixMap形式)の画像ファイルになっている.
convert grad.ppm grad.pngを実行して,png形式にしたのが
まずは,
int width=128, height=128;の部分を書き換えると作成する画像の縦横のピクセル数を変更することができる. これをたとえば,
int width=400, height=200;に変更すると,幅が400, 高さが200に変更となるはずだが,実際に実行して 作成されたファイルを変換してみると,グラデーションが途中で飽和してしまう. これは,widthが400になると,
b=j*2; // Bがjの値に応じて0-254の行で出力するB(青)の階調の範囲が0から798となり,0-255という制限を 超えてしまうからである.これを
b=j*256/width;と直すと,幅と高さを変更できる.
青から赤へのグラデーションを縦方向におこなう場合には,
r=255; // Rが255(最も明るい) g=255; // Gが255(最も明るい) b=j*2; // Bがjの値に応じて0-254 で変化を
r=i*256/height; // R g=0; // G b=255-r; // Bのようにする.ここで,
r=i*256/height; // Rを
r=i/height*256; // Rのようにしても,同じになりそうだが,(i/height)は整数の除算で, (0<=i <=height-1)の範囲では常に結果が0になってしまう.
また,青から緑に斜めにグラデーションさせるには,同じ部分を
r=0; // R g=(i+j)*256/(width+height); // G b=255-g; // Bのようにする.
Z0=0 Zn+1=Zn*Zn+Cとして, 複素数列{Zn}が定義できる.ここで,n->∞としたとき, |Zn|が発散しないようなCの集合をマンデルブロー集合という.
マンデルブロー集合は,集合の境界が複雑な形をしていて, 拡大をしても元の 図形と同じような複雑度を保っている.このような自己相似のことを総称して フラクタルと呼ぶが,マンデルブロー集合はフラクタルな図形の中ではもっと も有名である.
マンデルブロー集合は定義に無限回の繰り返しを含んでいるため, 計算機上 で正確に描くのは,困難である.そこで, 一般には
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,count; double x, y; for(i=0;i<height;i=i+1){ for(j=0;j<width;j=j+1){ int r,g,b; // -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 <= count <= 50の値がかえる count=mandel(x,y); // count=50の時に黒(0,0,0), count=0の時にほぼ白(250,250,250)とする r=250-count*5; // g=250-count*5; // b=250-count*5; // r=Math.min(255,Math.max(0,r)); // R の範囲を0から255までに g=Math.min(255,Math.max(0,g)); // G の範囲を0から255までに b=Math.min(255,Math.max(0,b)); // B の範囲を0から255までに System.out.println(r); // R System.out.println(g); // G System.out.println(b); // B } } } }このプログラムを Mandel.java というファイルに保存して,
javac Mandel.java java Mandel > mandel.ppmと実行すると,mandel.ppm というファイルが得られる.これを
open mandel.ppmで見ることもできるし,
convert mandel.ppm mandel.pngとして png 形式に変換して,WWWブラウザで見ることもできる. できた mandel.png は以下のようになる.
r=250-count*5; // g=250-count*5; // b=250-count*5; //の行を,たとえば
r=((50-count)*71)%256; g=((50-count)*111)%256; b=((50-count)*97)%256;とすると(「%」は剰余(mod)を表す演算子, 256で割った余りは 0-255になる),
int width=256, height=256;を書き換えると画像の大きさを変更できるが,widthとheightの比率を1:1以外にする 場合は,
x= -1.7+2.4*((double)j/(double)width); y= -1.2+2.4*((double)i/(double)height);の行の拡大率(2.4)をxとyとで変更した方が良い.
元のプログラムでは,-1.7 <= x < 0.7, -1.2 <= y < 1.2の範囲で計算していたが, たとえば,-0.12 <= x <=-0.02, -0.7 <= y <= -0.6 の 範囲を指定するには,
x= -0.12+0.1*((double)j/(double)width); y= -0.7+0.1*((double)i/(double)height);のようにする.
色を変更するには,
r=250-count*5; // g=250-count*5; // b=250-count*5; //の行を変更する.たとえば,白の背景に青で出したい場合は,
r=count*255/50; //R g=count*255/50; //G b=255; // Bのようにすれば良い.
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); } } } }このプログラムを実行した結果は,
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++){ int r,g,b; // Math.sinを呼び出してsinを計算する double sinY=-Math.sin(x*0.03)*100.0+128.0; // Math.absを呼び出して絶対値をとる // (int)で整数型に変換する int dl=(int)Math.abs(y-sinY); r=200*25/(dl+20); g=200*25/(dl+20); b=200*25/(dl+20); r=Math.min(255,Math.max(0,r)); // R の範囲を0から255までに g=Math.min(255,Math.max(0,g)); // G の範囲を0から255までに b=Math.min(255,Math.max(0,b)); // B の範囲を0から255までに System.out.println(r); System.out.println(g); System.out.println(b); } } } }このプログラムを実行した結果は,
Forbidden You don't have permission to access /~ktanaka/jousho05/report628/index.html on this server.とうメッセージが出てアクセ スできないはずである.15:10以降にも同様のエラーが出る時は,Shiftキーを 押しながら,再読み込み(Reload)を押してみること.
なお課題の締切りは7/19(火)の21:00である.