2007.11.14.
石立 喬
Visual C++ 2005 Express Edition の易しい使い方(20)
――― Mandelbrot図形の一部をクリックして、対応する条件でJulia図形を描く―――
Mandelbrot(マンデルブロー)集合(「易しい使い方(12)」で紹介)の図形にすっかりはまってしまって、その親戚関係に当たるJulia集合図形も手がけてみることにした。すでに「易しい使い方(12)」で説明した部分は一部省略されているので、そちらも参照して欲しい。
Julia集合とは
フランスの数学者Gaston Maurice Juliaが1918年(Benoit Mandelbrotは1924年生まれであるから、Mandelbrotの生まれる前)に論文発表したものであるが、当時は計算時間が非常に長く掛かることから、実際には永らく図形化されていなかった。
Mandelbrot集合が先に注目され、次いでJulia集合も見直された。Julia集合がMandelbrot集合と違うところは、Z=Z2+Cの繰り返し計算において、Mandelbrot集合がZの初期値Z0を常にZ0=0+0iとしてC平面状にCを変化さて図形を描画したのに対し、Julia集合では、Cを固定してZ0を変化させ、Z0平面上に図形を描画する。何回繰り返すとZの絶対値が一定の値に達するか、またはあらかじめ設定した回数では到達しないか(ゼロに収束してしまう場合もある)を調べて、繰り返し回数を色で表現するところはMandelbrot集合と同じである。Julia集合を図形化するには、CのみならずZ0の初期値も外部から与える必要がある。
Julia集合の画像の作成方法
パソコン画面上の座標を(x,y)としたとき、
zr(Z0の実数部)=x*step(ピクセル当りの変化分)+z0real(Z0の実数部の中心)
zi(Z0の虚数部)=y*step(ピクセル当りの変化分)+z0imag(Z0の虚数部の中心)
で与える。
step(ピクセル当りの変化分)は、xとyに対して同じ値を使用する。stepを大きくすると、画面上でのJulia図形が縮小され、小さくすると部分が拡大される。繰り返し回数を、どのような色の系列で表示するかによって、図形の感じが全く変わったものになる。
高速化の工夫
Julia図形の描画には、非常に時間がかかる場合がある。なかなか発散しない条件で発散を確認するためには、最大繰り返し回数(count_max)を上げて実行する必要がある。
ZとCを共に複素数として、Z=Z2+Cを計算するには、Zの実数部をzr、虚数部をzi、Cの実数部をci、Cの虚数部をciとして、
新しいzr=zr*zr-zi*zi+cr ----------------- (1)
新しいzi=2*zr*zi+ci -------------------- (2)
新しいZの絶対値の平方=zr*zr+zi*zi ------ (3)
を多数回計算する必要がある。ただし、(1)式で得た結果の新しいzrをすぐ(2)式に入れることはできないので、一旦tempに保存しておく。これらの計算は、乗算6回、加減算4回である。
そこで、zr2=zr*zr、zi2=zi*ziをあらかじめ計算しておく方法を用いると、
zr2=zr*zr ----------------------- (1)
zi2=zi*zi ------------------------- (2)
新しいzr=zr2-zi2+cr ---------------- (3)
新しいzi=2*zr*zi+ci ---------------- (4)
新しいZの絶対値の平方=zr2+zi2 ----- (5)
となって、乗算4回、加減算4回となり、乗算回数が2回減る。
プログラムの使用方法
1) プログラムを起動させると、「Mandelbrotモード」になっており、Mandelbrot図形が表示される。
2) Mandelbrot図形上でマウスを移動させると、その位置に対応するCの実数部(creal)、Cの虚数部(ciamg)が右側に表示される。
3) Mandelbrot図形上の希望する位置でマウスをクリックすると、対応するcrealとcimagが設定され、「Juliaモード」に変わる。以後、再び「Mandelbrotモード」に戻ると、Mandelbrot図形上に設定された新しい位置が白い小さな正方形で示される。
4) 表示されたJulia図形は、設定されたcrealとcimagを使用しているが、Z0の中心の実数部(z0real)、Z0の中心の虚数部(z0imag)は共に0.0に初期設定され、ピクセル当りの変化量(step)は0.006に初期設定される。
5) Julia図形上でマウスを移動させると、対応するZ0の実数部(z0real)と虚数部(z0imag)が右側に表示される。
6) Julia図形上の希望する位置でマウスをクリックすると、対応する場所が新しいz0realとz0imagとして設定されて、図形の中央に移動する。左クリックの場合は、stepが1/2になり(図形が拡大される)、右クリックの場合は、stepは変化しない。
7) Julia図形上のクリックを一回だけ元に戻すことができ、その場合は「戻す」ボタンをクリックする。
8) 希望する各データを直接入力したい場合には、画面左上の「条件の設定」メニューをクリックすると「条件の設定」ダイアログボックスが開くので、そこに入力する。「設定」ボタンをクリックすると、ダイアログボックスが閉じる。
9) ダイアログボックスが閉じても、Julia図形のすべてが再描画されないので、「再描画」ボタンをクリックして再描画する。
プログラムの仕組み
creal、cimagなどの各データは、FormDialogに設定されているものを原則として参照し、Form1.h内ではFormDialog::crealなどを呼び出して使用する。ただし、Julia図形の描画は繰り返し回数が多いので、高速化のためにローカルにコピーしたものを使用する。
◎Form1_Load()
1)creal、cimagなどの各データを初期設定する(FormDialog::crealなどに初期値を入れる)。
2)Mandelbrot図形を作成し、bmap_mandelとして保存する(以後、再作成は行わない)。
◎Form1_Paint()
[Mandelbrotモードの場合]
1) bmap_mandelを描画する。
2) その時に設定されているcrealとcimagの位置をMandelbrot図形上に白い四角で表示する。
[Juliaモードの場合]
1) 各データによるJulia図形を作成して描画する。
2) 各データを下部に表示する。
◎Form1_MouseMove()
図形描画領域内の場合は下記による。
[Mandelbrotモードの場合]
1) それまでに文字が表示されている場合があるので、表示領域を消す。
2) マウス位置から、対応するCの実数部の候補(cr)、Cの虚数部の候補(ci)を計算し、画面右方に表示する。
[Juliaモードの場合]
1) それまでに文字が表示されている場合があるので、表示領域を消す。
2) マウス位置から、対応するZ0の実数部の候補(zr)、Z0の虚数部の候補(zi)を計算し、画面右方に表示する。
図形描画領域外の場合は、それまでに文字が表示されている場合があるので、表示領域を消す。
◎Form1_MouseDown()
[Mandelbrotモードの場合]
1) Mandelbrot図形上のクリックした位置からcrealとcimagを求め、設定する。
2) z0real、z0imag、stepを初期設定する。
3) Juliaモードに切り替える。
[Juliaモードの場合]
1) それまでのz0real、z0imag、stepを、z0real_old、z0imag_old、step_oldに保存する。
2) マウス位置から計算してz0real、z0imagを更新する。stepは、左クリックの時のみ1/2に更新する。
3) buttonUndo(「戻る」ボタン)を有効にする。
再描画する。
◎buttonUndo_Click()
1)z0real_old、z0imag_old、step_oldをz0real、z0imag、stepに代入する。
2)buttonUndoを無効にする。
3)再描画する。
◎countToColor()
これは、「易しい使い方(12)」で用いたものと同じで、Mandelbrot図形の作成に使用する。
◎countToColor1()
これは、(株)翔泳社のCodeZineに「ジュリア集合の色付けを工夫して芸術的なフラクタル図形を描く」として紹介したものと同じで、白と黒を含み、彩度を少し下げてある。
プログラム
主な変数の役割は次の通り。
creal -------- 外部から与えるCの実数部の値
cimag ------- 外部から与えるCの虚数部の値
z0real ------- Z0(Zの初期値)の実数部で、表示するJulia画像のx座標の中心部に当たる。
z0imag ------- Z0(Zの初期値)の虚数部で、表示するJulia画像のy座標の中心部に当たる。
step --------- xまたはyが増加する度にZ0の実数部または虚数部の値が変化する量で、小さい程、表示される画像は拡大される。
value -------- Zの絶対値の平方で、これ以下であれば発散しないと判断する。
count_max --- 外部から与える繰り返し回数の上限である。大きすぎると計算時間が長くかかり、小さくするとvalueが4.0に達しない前に計算を打ち切ることになる。
color_number -- 繰り返し回数を表示するための階調数である。
Form1.h
#include "FormDialog.h"
static int X0=10,Y0=35; //図形の表示位置
static int X1=10,Y1=450; //設定条件の表示位置
static int X2=430,Y2=35; //マウス移動位置表示
static int SIZE=200;
int count_max,color_number;
double z0real_old,z0imag_old,step_old; //元に戻すための記憶場所
static double MANDEL_CREAL=-0.5;
static double MANDEL_CIMAG=0.0;
static double MANDEL_STEP=0.006;
Rectangle^ rectangle1;
Bitmap^ bmap_mandel;
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e)
{
radioMandel->Checked=true; //最初はMandelbrotモードなので、radioMandelにチェック
radioJulia->Checked=false; //最初はMandelbrotモードなので、radioJuliaはチェックしない
buttonUndo->Enabled=false; //クリックによる座標指定がされるまで、buttonUndoは不要
rectangle1=gcnew Rectangle(X0,Y0,SIZE*2,SIZE*2); //MandelbrotまたはJulia図形の領域
//各種データーを初期設定
FormDialog::creal=0.0;
FormDialog::cimag=0.0;
FormDialog::z0real=0.0;
FormDialog::z0imag=0.0;
FormDialog::step=0.006;
FormDialog::count_max=256;
FormDialog::color_number=64;
//Mandelbrot図形を作成して、bmap_mandelを用意する
int MANDEL_COUNT_MAX=1024;
int MANDEL_COLOR_NUMBER=64;
int count,x,y;
double cr,ci,zr,zi,temp,value;
bmap_mandel=gcnew Bitmap(SIZE*2,SIZE*2);
for(x=-SIZE;x<SIZE;x++){
cr=x*MANDEL_STEP+MANDEL_CREAL;
for(y=-SIZE;y<SIZE;y++){
ci=y*MANDEL_STEP+MANDEL_CIMAG;
zr=0.0;
zi=0.0;
value=0.0;
count=0;
do{
temp=zr*zr-zi*zi+cr; //Zの二乗の実数部
zi=2.0*zr*zi+ci; //Zの二乗の虚数部
zr=temp;
value=zr*zr+zi*zi; //Zの絶対値の平方
count++;
if(count>MANDEL_COUNT_MAX){
count=-1;
break;
}
}while(value<4.0);
bmap_mandel->SetPixel(SIZE+x,SIZE-1-y,countToColor(count,MANDEL_COLOR_NUMBER));
}
}
}
private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^
e) {
Graphics^ gr=e->Graphics;
Bitmap^ bmap=gcnew Bitmap(SIZE*2,SIZE*2);
String^ string1;
int count,x,y;
double creal,cimag,z0real,z0imag,step;
double zr,zi,zr2,zi2,zreal,temp,value;
if(radioMandel->Checked){ //Mandelbrotモード
gr->DrawImage(bmap_mandel,X0,Y0);
x=(FormDialog::creal-MANDEL_CREAL)/MANDEL_STEP+SIZE;
y=-(FormDialog::cimag-MANDEL_CIMAG)/MANDEL_STEP+SIZE;
gr->FillRectangle(Brushes::White,X0-1+x,Y0-1+y,3,3);
}
if(radioJulia->Checked){ //Juliaモード
creal=FormDialog::creal;
cimag=FormDialog::cimag;
z0real=FormDialog::z0real;
z0imag=FormDialog::z0imag;
step=FormDialog::step;
count_max=FormDialog::count_max;
color_number=FormDialog::color_number;
//Julia図形を作成する
for(x=-SIZE;x<SIZE;x++){
zreal=x*step+z0real;
for(y=-SIZE;y<SIZE;y++){
zr=zreal;
zi=y*step+z0imag;
count=0;
do{
r2=zr*zr;
zi2=zi*zi;
temp=zr2-zi2+creal; //Zの二乗の実数部
zi=2.0*zr*zi+cimag; //Zの二乗の虚数部
zr=temp;
value=zr2+zi2; //Zの絶対値の平方
count++;
if(count>count_max){ //発散しなかった
count=-1;
break;
}
}while(value<4.0);
bmap->SetPixel(SIZE+x,SIZE-1-y,countToColor1(count,color_number));
}
}
gr->DrawImage(bmap,X0,Y0);
string1=String::Format("Cの実数部の中心={0}、Cの虚数部の中心={1}",creal,cimag);
gr->DrawString(string1,Font,Brushes::Black,X1,Y1);
string1=String::Format("Z0の実数部の中心={0}、Z0の虚数部の中心={1}",z0real,z0imag);
gr->DrawString(string1,Font,Brushes::Black,X1,Y1+20);
string1=String::Format("ステップ/ピクセル={0}、最大繰返し回数={1}、表示色の階調{2}",step,count_max,color_number);
gr->DrawString(string1,Font,Brushes::Black,X1,Y1+40);
}
}
private: System::Void Form1_MouseMove(System::Object^ sender, System::Windows::Forms::MouseEventArgs^
e) {
Graphics^ g=this->CreateGraphics();
int x,y;
double cr,cizr,zi;
String^ string1;
if(rectangle1->Contains(e->X,e->Y)){
x=e->X-X0;
y=e->Y-Y0;
if(radioMandel->Checked){ //Mandelbrotモード
cr=(x-SIZE)*MANDEL_STEP+MANDEL_CREAL;
ci=-(y-SIZE)*MANDEL_STEP+MANDEL_CIMAG;
g->FillRectangle(Brushes::White,415,95,110,40);
string1=String::Format("Cの実部={0:F3}",cr);
g->DrawString(string1,Font,Brushes::Black,420,100);
string1=String::Format("Cの虚部={0:F3}",ci);
g->DrawString(string1,Font,Brushes::Black,420,120);
}
if(radioJulia->Checked){ //Juliaモード
zr=(x-SIZE)*FormDialog::step+FormDialog::z0real;
zi=-(y-SIZE)*FormDialog::step+FormDialog::z0imag;
g->FillRectangle(Brushes::White,415,95,110,40);
string1=String::Format("Z0の実部={0:F3}",zr);
g->DrawString(string1,Font,Brushes::Black,420,100);
string1=String::Format("Z0の虚部={0:F3}",zi);
g->DrawString(string1,Font,Brushes::Black,420,120);
}
}
else g->FillRectangle(Brushes::White,415,95,110,40);
}
private: System::Void Form1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^
e) {
Graphics^ g=this->CreateGraphics();
int x,y;
if(rectangle1->Contains(e->X,e->Y)){
x=e->X-X0;
y=e->Y-Y0;
if(radioMandel->Checked){ //Mandelbrotモード
//Mandelbrot図形からcrealとcimagを取り込む
FormDialog::creal=(x-SIZE)*MANDEL_STEP+MANDEL_CREAL;
FormDialog::cimag=-(y-SIZE)*MANDEL_STEP+MANDEL_CIMAG;
radioJulia->Checked=true;
radioMandel->Checked=false;
//Julia図形のz0とstepを初期設定
FormDialog::z0real=0.0;
FormDialog::z0imag=0.0;
FormDialog::step=0.006;
}
else{ //Juliaモード
//それまでのz0real,z0imag,stepを保存する
z0real_old=FormDialog::z0real;
z0imag_old=FormDialog::z0imag;
step_old=FormDialog::step;
//クリックした場所を新しいz0realとz0imagとする
FormDialog::z0real=(x-SIZE)*FormDialog::step+FormDialog::z0real;
FormDialog::z0imag=-(y-SIZE)*FormDialog::step+FormDialog::z0imag;
//stepを1/2にする(図形を2倍に拡大する)
FormDialog::step/=2.0;
//buttonUndoを使用できるようにする
buttonUndo->Enabled=true;
}
Invalidate();
}
}
private: System::Void menuSet_Click(System::Object^ sender, System::EventArgs^
e) {
FormDialog^ form_d=gcnew FormDialog();
form_d->ShowDialog();
}
private: System::Void buttonUndo_Click(System::Object^ sender, System::EventArgs^
e) {
FormDialog::z0real=z0real_old;
FormDialog::z0imag=z0imag_old;
FormDialog::step=step_old;
buttonUndo->Enabled=false;
Invalidate();
}
private: System::Void buttonUpdate_Click(System::Object^ sender, System::EventArgs^
e) {
Invalidate();
}
//カウント数を色に変換するメソッド
private: Color countToColor(int n,int base){
if(n<0) return Color::Black; //Zの絶対値が一定値を超えなかった
else{
int d=n % base;
d*=(256/base);
return HueToRGBColor(d/256.1f);
}
}
//カウント数を色に変換するメソッド
private: Color countToColor1(int n,int base){
int r,g,b;
if(n<0) return Color::Black; //Zの絶対値が一定値を超えなかった
int d=(n % base)*256/base;
int m=d/32;
switch(m){
//青→マゼンタ
case 0: r=63+6*d; g=63; b=255; break;
//マゼンタ→白
case 1: r=255; g=63+6*(d-32); b=255; break;
//白→シアン
case 2: r=255-6*(d-64); g=255; b=255; break;
//シアン→緑
case 3: r=63; g=255; b=255-6*(d-96); break;
//緑→黄
case 4: r=63+6*(d-128); g=255; b=63; break;
//黄→赤
case 5: r=255; g=255-6*(d-160); b=63; break;
//赤→黒
case 6: r=255-6*(d-192); g=63; b=63; break;
//黒→青
case 7: r=63; g=63; b=63+6*(d-224); break;
}
return Color::FromArgb(r,g,b);
}
//色相(0〜1)をColorに変換するメソッド
private: Color HueToRGBColor(float hue){
int h=(int)(hue*6.0f); //0から5までの整数
float f=hue*6.0f-h; //小数点以下の端数
int q=(int)(255*(1.0f-f));
int t=(int)(255*f);
int r,g,b;
switch(h){
case 0:r=255; g=t; b=0; break;
case 1:r=q; g=255; b=0; break;
case 2:r=0; g=255; b=t; break;
case 3:r=0; g=q; b=255; break;
case 4:r=t; g=0; b=255; break;
case 5:r=255; g=0; b=q; break;
}
return Color::FromArgb(r,g,b);
}
private: System::Void radioMandel_CheckedChanged(System::Object^ sender, System::EventArgs^
e) {
Invalidate();
}
private: System::Void radioButton2_CheckedChanged(System::Object^ sender, System::EventArgs^
e) {
Invalidate();
}
FormDialog.h
private: System::Void FormDialog_Load(System::Object^ sender, System::EventArgs^
e) {
//各種データをテキストボックスに表示する
textBox1->Text=creal.ToString();
textBox2->Text=cimag.ToString();
textBox3->Text=z0real.ToString();
textBox4->Text=z0imag.ToString();
textBox5->Text=step.ToString();
textBox6->Text=count_max.ToString();
textBox7->Text=color_number.ToString();
}
private: System::Void buttonOK_Click(System::Object^ sender, System::EventArgs^ e) {
//テキストボックスの内容を各種データに変換する
creal=double::Parse(textBox1->Text);
cimag=double::Parse(textBox2->Text);
z0real=double::Parse(textBox3->Text);
z0imag=double::Parse(textBox4->Text);
step=double::Parse(textBox5->Text);
count_max=int::Parse(textBox6->Text);
color_number=int::Parse(textBox7->Text);
this->Close();
}
private: System::Void buttonCancel_Click(System::Object^ sender, System::EventArgs^
e) {
this->Close();
}
得られた画面
図1は、プログラム起動時の画面である。最初は「Mandelbrotモード」になっていて、Mandelbrot図形が表示されている。図形上に、creal=0、cimag=0に相当する点が、白い正方形で表示されている。図形の中心は、MANDEL_CREAL=-0.5、MANDEL_CIMAG=0により設定されているので、点は図形の右寄りで、上下方向には中心にある。
Mandelbrot図形の右には、Cの実(数)部、Cの虚(数)部が表示されているが、たまたま置かれていたマウスの位置を示していて、特に意味は無い。
マウスクリックによるJulia図形の変更が行われていないので、「戻す」ボタンは無効になっている。
図1 起動直後の画面
図2は「条件の設定」ダイアログボックスを示したもので、図3は、それによって得られたJulia図形を示す。ダイアログボックスでデータを指定した場合には、「戻す」ボタンが無効になっている。
図2 キー入力によって各データを設定したダイアログボックス
図3 ダイアログボックスで設定された条件によるJulia図形