2010.11.23.
石立 喬
Visual C++ 2010 Express の易しい使い方(19)
――― 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の虚数部(cimag)が右側に表示される。
3)Mandelbrot図形上の希望する位置でマウスをクリックすると、対応するcrealとcimagが設定され、「Juliaモード」に変わる。以後、再び「Mandelbrotモード」に戻ると、Mandelbrot図形上に設定された新しいCの位置が白い十字で示される。
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になり(図形が拡大される)。
7)Julia図形上のクリックを一回だけ元に戻すことができ、その場合は「戻す」ボタンをクリックする。
8)希望する各データを直接入力したい場合には、画面左上の「条件設定」ボタンで「条件の設定」ダイアログボックスが開くので、そこに入力する。「設定」ボタンをクリックすると、ダイアログボックスが閉じる。
9)ダイアログボックスが閉じても、Julia図形のすべてが再描画されないので、「再描画」ボタンをクリックして再描画する。
プログラムの説明
creal、cimagなどの各データは、FormDialogに設定されているものを参照し、Form1.h内ではFormDialog::crealなどを呼び出して使用する。ただし、Julia図形の描画は繰り返し回数が多いので、高速化のためにローカルにコピーして使用する。
◎Form1_Load()
1)ラジオボタン、「戻る」ボタンの初期設定をする。
2)creal、cimagなどの各データを初期設定する(FormDialog::crealなどに初期値を入れる)。
3)Mandelbrot図形を作成し、bmap_mandelとして保存する(以後、再作成は行わない)。
◎Form1_Paint()
[Mandelbrotモードの場合]
1)bmap_mandelを描画する。
2)その時に設定されているcrealとcimagの位置をMandelbrot図形上に白い十字で表示する。
[Juliaモードの場合]
1)FormDialogから 各データを読み込み、その条件によるJulia図形を作成して描画する。
2)各データを下部に表示する。
◎Form1_MouseMove()
図形描画領域内の場合は下記による。
[Mandelbrotモードの場合]
1)マウス位置から、対応するCの実数部の値(cr)、Cの虚数部の値(ci)を計算する。
2)crとciを画面右方に表示する。
[Juliaモードの場合]
1)マウス位置から、対応するZ0の実数部の値(zr)、Z0の虚数部の値(zi)を計算する。
2)zrとziを画面右方に表示する。
3)図形描画領域外の場合は、それまでに文字が表示されている場合があるので、表示領域を消す。
◎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(「戻る」ボタン)を有効にする。
4)再描画する。
◎buttonUndo_Click()
1)z0real_old、z0imag_old、step_oldをz0real、z0imag、stepに代入する。
2)buttonUndoを無効にする。
3)再描画する。
◎countToColor()、NumberToRGBColor()
これは、「易しい使い方(12)」で用いたものと同じである。
プログラム
主な変数の役割は次の通り。
creal --------- 外部から与えるCの実数部の値
cimag --------- 外部から与えるCの虚数部の値
z0real -------- Z0(Zの初期値)の実数部で、表示するJulia画像のx座標の中心部に当たる。
z0imag -------- Z0(Zの初期値)の虚数部で、表示するJulia画像のy座標の中心部に当たる。
step ---------- xまたはyが増加する度にZ0の実数部または虚数部の値が変化する量で、小さい程、表示される画像は拡大される。
value --------- Zの絶対値の平方で、これが4.0未満であれば発散していないと判断し、演算の繰り返しを継続する。
count_max ----- 外部から与える繰り返し回数の上限である。大きすぎると計算時間が長くかかり、小さくするとvalueが4.0に達しない前に計算を打ち切ることになる。
color_number -- 繰り返し回数を表示するための階調数である。
Form1.h
#pragma once
#include "FormDialog.h"
static int X0=10,Y0=40; //図形の表示位置
static int X1=10,Y1=450; //設定条件の表示位置
static int X2=430,Y2=35; //マウス移動位置表示
static int SIZE=200;
int count_max,color_number;
float z0real_old,z0imag_old,step_old; //元に戻すための記憶場所
static float MANDEL_CREAL=-0.5f;
static float MANDEL_CIMAG=0.f0;
static float MANDEL_STEP=0.006f;
static Rectangle^ rectangle1=gcnew Rectangle(X0,Y0,SIZE*2,SIZE*2); //MandelbrotまたはJulia図形の領域
static Bitmap^ bmap_mandel=gcnew Bitmap(SIZE*2,SIZE*2);
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は不要
int MANDEL_COUNT_MAX=1024;
int MANDEL_COLOR_NUMBER=64;
int count,x,y;
float cr,ci,zr,zi,temp,value;
//各種データーを初期設定
FormDialog::creal=0.0f;
FormDialog::cimag=0.0f;
FormDialog::z0real=0.0f;
FormDialog::z0imag=0.0f;
FormDialog::step=0.006f;
FormDialog::count_max=256;
FormDialog::color_number=64;
//Mandelbrot図形を作成して、bmap_mandelを用意する
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.0f;
zi=0.0f;
value=0.0f;
count=0;
do{
temp=zr*zr-zi*zi+cr; //Zの二乗の実数部
zi=2*zr*zi+ci; //Zの二乗の虚数部
zr=temp;
value=zr*zr+zi*zi; //Zの絶対値の平方
count++;
if(count>MANDEL_COUNT_MAX){
count=-1;
break;
}
}while(value<4.0f);
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->DrawLine(Pens::White,X0+x-5,Y0+y,X0+x+5,Y0+y);
gr->DrawLine(Pens::White,X0+x,Y0+y-5,X0+x,Y0+y+5);
}
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{
zr2=zr*zr;
zi2=zi*zi;
temp=zr2-zi2+creal; //Zの二乗の実数部
zi=2*zr*zi+cimag; //Zの二乗の虚数部
zr=temp;
value=zr2+zi2; //Zの絶対値の平方
count++;
if(count>count_max){//発散しなかった
count=-1;
break;
}
}while(value<4.0f);
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;
float 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;
//Julia図形のz0とstepを初期設定
FormDialog::z0real=0.0f;
FormDialog::z0imag=0.0f;
FormDialog::step=0.006f;
radioJulia->Checked=true;
radioMandel->Checked=false;
}
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;
//buttonUndoを使用できるようにする
buttonUndo->Enabled=true;
}
Invalidate();
}
}
private: System::Void buttonSettings_Click(System::Object^ sender, System::EventArgs^
e) {
FormDialog^ dlg=gcnew FormDialog();
dlg->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: System::Void radioMandel_CheckedChanged(System::Object^ sender, System::EventArgs^
e) {
Invalidate();
}
private: System::Void radioJulia_CheckedChanged(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 NumberToRGBColor(d);
}
}
//数値をColorに変換するメソッド
private: Color NumberToRGBColor(int n){
int h=n/43;
int r,g,b;
switch(h){
case 0:r=255; g=n*6; b=0; break; //n= 0~ 42
case 1:r=(85-n)*6; g=255; b=0; break; //n= 43~ 85
case 2:r=0; g=255; b=(n-86)*6; break; //n= 86~128
case 3:r=0; g=(171-n)*6; b=255; break; //n=129~171
case 4:r=(n-172)*6; g=0; b=255; break; //n=172~214
case 5:r=255; g=0; b=(255-n)*6; break; //n=215~255
};
return Color::FromArgb(r,g,b);
}
FormDialog.h
public:
static float creal,cimag,z0real,z0imag,step;
static int count_max,color_number;
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 buttonSet_Click(System::Object^ sender, System::EventArgs^
e) {
//テキストボックスの内容を各種データに変換する
creal=float::Parse(textBox1->Text);
cimag=float::Parse(textBox2->Text);
z0real=float::Parse(textBox3->Text);
z0imag=float::Parse(textBox4->Text);
step=float::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図形
子フォームの使い方
すでに、「易しい使い方(4)」で説明してあるが、念のために簡単に解説する。
◎新しいフォーム(子フォーム)を追加する方法
1) 統合開発環境の「ソリューション エクスプローラ」(現れていない場合は、メニューから、「表示」→「ソリューション
エクスプローラ」)で、プロジェクトのトップ(たとえば「Julia」など)を右クリックし、「追加」→「新しい項目」を選択する。
2)「新しい項目の追加」ウインドウが開くので、左の欄から「UI(User Interfaceの意)」、右の欄から 「Windowsフォーム」を選び、「名前」を「FormDialog」(一例)と入力して、「追加」ボタンをクリックする。
3)FormDialog.cpp と FormDialog.h が生成され、FormDialog の「FormDialog.h[デザイン]」画面が現れる。
4)フォームの中央で右クリックして、「プロパティ」ウインドウで下記のように設定する。
「表示」欄で、
FormBorderStyle --- FixedDialog (アイコンが無く、サイズの変更ができない)
Text -------------- 条件の設定(これは一例で、キャプションを付ける)
「ウインドウスタイル」欄で、
MaximizeBox ------- False (最大化ボタンを消す)
MinimizeBox ------- False (最小化ボタンを消す)
「Text」項目以外は、項目を選択すると、右側に下向きの三角矢印が現れるので、これをクリックし、選択する。
◎親フォームのボタンから子フォームをモーダル・ダイアログボックスとして呼び出す方法
モーダルとは、子フォームを閉じるまで、親フォームでの実行が出来ないことで、そうでないものにモードレスがある。特に目的が無い限り、一般的にはモーダルが用いられる。モーダルを用いると、子フォームが閉じられると同時に、親フォームに制御が戻るので、子フォームからの情報の転送に便利である。
1) 親フォームで、「条件設定」ボタン(一例、子フォーム呼び出しのために作成したボタン)をダブルクリックする。
2)buttonDialog_Click() メソッドが現れるので、
FormDialog^ dlg=gcnew FormDialog();
dlg->ShowDialog();
と記述する。ただし、FormDialog は、さきに命名した、開きたい子フォームの名称である。子フォームからの入力情報を受け取るには、この下に続けてコードを記入する。
3)親フォームの Form1.h の最初の部分(#pragma onceの後)に
#include “FormDialog.h”
と記述する。
◎子フォームで設定したデータを親フォームから使う方法
1)子フォームで作成される変数をfloat型で crealとすると、子フォームのFormDialog.h
において、
public: static real creal;
などと宣言しておく。 public でなく、private にすると、親フォームクラスから呼び出せなくなり、static
を省くと親フォームに渡せなくなって、いずれもエラーになってビルドできない。
2)親フォームクラスで子フォームクラスの変数を呼び出すには、
FormDialog::creal
などを用いる。
「Visual C++ の勉強部屋」(目次)へ