2006.11.5.
2008.10.13. FillRectangleにより点を打つ方がベターとの説明を追加
石立 喬

Visual C++ 2005 Express Edition の易しい使い方(12)

――― 高速化のためにcomplexクラスを使用しないでMandelbrot図形を描く―――

 電気技術者にとって、カオスやフラクタルは興味ある対象である。電気・電子回路には直接関係がないが、頭の体操として、そして一寸した芸術として、Mandelbrot図形の描画を試みる。電気技術者を悩ませた複素数が、こんなに芸術的であったとは驚きである。Mandelbrot集合や、それを図形的に表示する方法は、すでによく知られているので詳述しない。
 Mandelbrot(マンデルブロー)集合の図形を描くには、複素数演算を1万回以上も多数回繰り返す必要があることもある。ここでは、演算速度を優先して、標準のcomplexクラスを使用しないでMandelbrot図形を描いてみた。Mandelbrot図形はフラクタル図形の一種で、拡大すると再び相似の図形が現れる。図形上をマウスクリックで自由に探索して興味ある図形を捜し求めることができるようにし、別フォームのダイアログボックスで希望する条件を入力できるようにもした。

Mandelbrot集合の画像の作成方法
 Mandelbrot図形は、ZおよびCを複素数としたとき、Z=Z2+Cを繰り返し計算し、何回繰り返すとZの絶対値が一定の値を越えるか(例えば2.0を越えると発散すると予測する)、またはあらかじめ設定した回数(例えば16384回)以内では一定値に到達しないか(ゼロまたは一定値に収束すると予測する)を調べて、繰り返し回数を色で表現したものである。Cは、パソコン画面上の座標を(x,y)としたとき、
  creal(Cの実数部)=x*step(ピクセル当りの変化分)+cr(Cの実数部の中心)
  cimag(Cの虚数部)=y*step(ピクセル当りの変化分)+ci(Cの虚数部の中心)
で与える。
 (ピクセル当りの変化分)は、xとyに対して同じ値を使用する。(ピクセル当りの変化分)を大きくすると、画面上でのMandelbrot図形が縮小され、小さくすると拡大される。
Zの初期値は、実数部、虚数部ともにゼロとする。Z=Z2+Cの繰り返し計算は、C++の標準ライブラリによらず、
  zreal(Zの実数部)=zreal*zreal-zimag*zimag
  zimag(Zの虚数部)=-2*zreal*zimag
で求めた。ただし、プログラムの中では、1番目の式の結果を二番目の式で用いることはできないので、一時的な変数tempを使用する。
 計算を打ち切るためのZの絶対値は、
  value(Zの絶対値の二乗)=zreal*zreal+zimag*zimag
で求めた。これも、高速化のために平方根の演算を省略している。
 繰返し回数を色に変換するには、自作のcountToColorメソッドを使用した。この中で、繰返し回数を色相(hue)に対応させ、彩度(saturation)、明度(brightness)は常に1としたので、既存のメソッドを使用することなく、簡略化した自作メソッドで高速化を図った。
 countは繰返し回数を表し、初期値は0で、Z=Z2+Cを一回実行する度に増加する。繰返し回数の上限count_maxに達すると、発散しなかったとして-1を入れる。
 count_maxは外部から与える繰返し回数の上限で、大きすぎると計算時間が長くかかり、小さくするとvalueが4.0に達しない前に計算を打ち切ることになり、暗黒色の部分が増える。
 color_numberは繰返し回数を表示するための階調数で、正の場合はカラーの階調数、負の場合は白黒の階調数を表す。図形が最も美しく見える値があり、試行錯誤で決める。

プログラムの機能
 1) 起動させると、基本のMandelbrot図形が表示され、初期設定値による「Cの実数部の中心」、「Cの虚数部の中心」、「ピクセル当たりのステップ」、「最大繰り返し回数」が下方に表示される。
 2) メニュー「条件の設定」をクリックすると、「条件の設定」ダイアログボックスが現れ、「Cの実数部の中心」、「Cの虚数部の中心」、「ピクセル当たりのステップ」、「最大繰り返し回数」、「カラーの階調」を入力でき、「OK」のクリックにより条件が設定される。
 3) 「条件の設定」ダイアログボックスが閉じ、Mandelbrot図形が描画されるが、全範囲が描画されないことが多いので、メニュー「再描画」をクリックして強制的に全体を再描画させる。
 4) 画面上でマウスを移動させると、その位置に相当するCの値が右側に表示されるので、参考にする。
 5) 画面上でマウスを左クリックすると、その位置に相当するCの値が新しい図形の中心に変更され、中心が移動した図形が描画される。
 6) 画面上でマウスを右クリックすると、その位置に相当するCの値が新しい図形の中心に変更され、同時に表示倍率が5倍になる(ピクセル当たりのステップが5分の1になる)。
 7) メニュー「元に戻す」をクリックすると、図形上でマウスクリックした直前の図形に戻ることができる。
 8) 繰り返しによってZの値の絶対値が一定の値(2.0を使用)に達した場合は、その時の繰返し回数をカラー(赤→黄→緑→シアン→青→マゼンタ→赤の順)または白黒(黒→白)の階調で表示する。繰返し回数が階調数を超えた場合には、赤または黒からもう一度繰り返す。
 9) 最大繰返し回数に達しても一定の値に至らなかった場合は、カラー表示の場合は暗黒色、白黒表示の場合には、ピンク色で表示する。最大繰り返し回数を大きく設定すると、暗黒色で表されていたものがカラーで表示される場合もある。
 10) 繰返し回数をカラーで表示するか白黒で表示するかは、表示色の階調の正負(負は白黒)で決まる。

プログラムの説明
 「条件の設定」ダイアログボックスFormDialogを用いて、メインのForm1とデータをやりとりする仕組みを図1に示す。
 Form1が起動されると、まずForm1_Load()で初期データをFormDialogに書き込む。これは、Mandelbrot図形の全貌を示すためのものである。次にForm1_Paint()で、FormDialogからForm1に、そのデータを読み込む。ダイアログボックスを開くと、データがテキストボックスに書き込まれる。ダイアログボックスで「OK」ボタンがクリックされると、テキストボックスの値がデータとして読み込まれる。そして、Invalidate()によりForm1_Paint()が呼び出され、Form1に送られる。


図1 Form1(メイン画面)とFormDialog(ダイアログボックス)とのデータのやりとり


主となるプログラムはForm1_paint()に置き、ここでは、
 1)FormDialogからのデータの読込み
 2)複素数計算を用いたZの漸化式の実行
 3)繰返し回数のチェック
 4)Zの絶対値のチェック
 5)繰返し回数の色への変換(countToColorメソッドの呼び出し)
 6)上記の色による描画
 7)各条件の表示
を実行する。

 図形上でマウスを移動させた時のメソッドForm1_MouseMove()は、マウス位置を求め、もし図形上であれば、それをCの値に換算して画面右上方に表示する。
 図形上でマウスをクリックした時のメソッドForm1_MouseDown()では、マウス位置を求め、「元に戻す」操作が可能なように、それまでのCの中心位置をcr_old、ci_oldに、stepをstep_oldに格納した後に、新しいCの中心位置を計算する。右クリックであった場合には、さらにstepを5分の1にする(図形の倍率を拡大)。これらの値は、FormDialogにも送っておく。
 繰返し回数を色に変換するメソッドcountToColor()は、カウント数と表示色の階調数を受け取って、0から1の範囲に調整して色相(hue)値とし、色相をColorクラスの値に変換するメソッドHueToRGBColor()を呼び出す。

プログラム
◎Form1.hに手で入力した部分を下記に示す。

#pragma once
#include "FormDialog.h"        //追加する

public: static int X0=10,Y0=35;    //図形の表示位置
    static int X1=10,Y1=450;    //設定条件の表示位置
    static int X2=430,Y2=35;    //マウス移動表示位置
    static int SIZE=200;
    double ci,cr,step;
    double ci_old,cr_old,step_old;  //元に戻すための記憶場所

//プログラムが起動した時のメソッド
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {

   FormDialog::cr=-0.75;
   FormDialog::ci=0.0;
   FormDialog::step=1.25/SIZE;
   FormDialog::count_max=256;
   FormDialog::color_number=16;

}

//プログラムの起動時およびInvalidate()により呼び出されるメソ ッド
private: System::Void Form1_Paint(System::Object^ sender,
                          System::Windows::Forms::PaintEventArgs^ e) {

   Graphics^ gr=e->Graphics;

   int count,x,y;
   double zreal,creal,zimag,cimag,temp,value;

   ci=FormDialog::ci;
   cr=FormDialog::cr;
   step=FormDialog::step;

   int count_max=FormDialog::count_max;
   int color_number=FormDialog::color_number;

   for(x=-SIZE;x<=SIZE;x++){
      creal=x*step+cr;
      for(y=-SIZE;y<=SIZE;y++){
         cimag=y*step+ci;
         zreal=0.0;
         zimag=0.0;
         count=0;
         do{
            temp=zreal*zreal-zimag*zimag+creal;
            zimag=2.0f*zreal*zimag+cimag;
            zreal=temp;
            value=zreal*zreal+zimag*zimag;    //Zの絶対値の二乗
            count++;
            if(count>count_max){
               count=-1;
               break;
           }
         }while(value<4.0);    //Zの絶対値に対しては2.0に相当
         Pen^ pen1=gcnew Pen(countToColor(count,color_number));
         gr->DrawRectangle(pen1,X0+SIZE+x,Y0+SIZE-y,1,1);
         //上記2行は、下記に変更した方がベター
         //Brush^ brush1=gcnew SolodBrush(countToColor(count,color_number));
         //gr->FillRectangle(brush1,X0+SIZE+x,Y0+SIZE=y,1,1);
      }
   }

   System::Drawing::Font^ font1=gcnew System::Drawing::Font("MSゴシック",10);
   String^ string1=String::Format("Cの実数部の中心={0}、Cの虚数部の中心={1}",cr,ci);
   gr->DrawString(string1,font1,Brushes::Black,X1,Y1);
   string1=String::Format("ステップ/ピクセル={0}、最大繰返し回数={1}、
                     表示色の階調={2}",step,count_max,color_number);
   gr->DrawString(string1,font1,Brushes::Black,X1,Y1+20);

}

//カウント数を色に変換するメソッド
private: Color countToColor(int n,int base){

   int d;
   if(n<0){      //Zの絶対値が一定値を超えなかった
      if(base>0) return Color::FromArgb(32,32,32);    //暗黒色で表示
      else    return Color::FromArgb(255,128,128);  //ピンクで表示
   }
   d=n % base;
   d*=(256/base);
   if(base<0) return Color::FromArgb(-d,-d,-d);       //白黒で表示
   else     return HueToRGBColor(d/256.1f);      //カラーで表示

}

//色相(0〜1)をColorに変換するメソッド
private: Color HueToRGBColor(float hue){

   int h=(int)(hue*6.0f);    //0からまでの整数
   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 menuSet_Click(System::Object^ sender, System::EventArgs^ e) {

   FormDialog^ form_d=gcnew FormDialog();
   form_d->ShowDialog();

}

//図形上でマウスクリックした時のメソッド
private: System::Void Form1_MouseDown(System::Object^ sender,
                         System::Windows::Forms::MouseEventArgs^ e) {

   int x=e->X;
   int y=e->Y;
   cr_old=cr;
   ci_old=ci;
   step_old=step;
   if(x>X0 && x<X0+2*SIZE && y>Y0 && y<Y0+2*SIZE){   //図形の範囲内
      cr+=(x-X0-SIZE)*step;
      ci-=(y-Y0-SIZE)*step;
      if(e->Button==System::Windows::Forms::MouseButtons::Right){   //右クリック
         step/=5.0;
      }
      FormDialog::cr=cr;
      FormDialog::ci=ci;
      FormDialog::step=step;
      Invalidate();
   }

}

//メニュー「再描画」をクリックした時のメソッド
private: System::Void menuRedraw_Click(System::Object^ sender, System::EventArgs^ e) {

   Invalidate();

}

//図形上でマウスを移動させた時のメソッド
private: System::Void Form1_MouseMove(System::Object^ sender,
                            System::Windows::Forms::MouseEventArgs^ e){

   Graphics^ gr=this->CreateGraphics();

   int x=e->X;
   int y=e->Y;
   double creal,cimag;

   System::Drawing::Font^ font1=gcnew System::Drawing::Font("MSゴシック",10);

   if(x>X0 && x<X0+2*SIZE && y>Y0 && y<Y0+2*SIZE){    //図形の範囲内
      creal=(x-X0-SIZE)*step+cr;
      cimag=-(y-Y0-SIZE)*step+ci;
      gr->FillRectangle(Brushes::White,X2,Y2-10,180,60);   //文字を消す
      String^ string1=String::Format("Cの実数部={0}",creal);
      gr->DrawString(string1,font1,Brushes::Black,X2,Y2);
      string1=String::Format("Cの虚数部={0}",cimag);
      gr->DrawString(string1,font1,Brushes::Black,X2,Y2+30);

   }
   else
   gr->FillRectangle(Brushes::White,X2,Y2-10,180,60);      //文字を消す

}

//メニュー「元に戻す」をクリックした時のメソッド
private: System::Void menuUndo_Click(System::Object^ sender, System::EventArgs^ e) {

   FormDialog::cr=cr_old;
   FormDialog::ci=ci_old;
   FormDialog::step=step_old;
   Invalidate();

}

◎FormDialog.hのためのプログラムを下記に示す。

public: static double cr,ci,step;
    static double creal,cimag;
    static int count_max,color_number;

//ダイアログボックス「条件の設定」が開いた時のメソッド
private: System::Void FormDialog_Load(System::Object^ sender, System::EventArgs^ e) {

   textBox1->Text=cr.ToString();
   textBox2->Text=ci.ToString();
   textBox3->Text=step.ToString();
   textBox4->Text=count_max.ToString();
   textBox5->Text=color_number.ToString();

}

//ボタン「OK」をクリックした時のメソッド
private: System::Void buttonOK_Click(System::Object^ sender, System::EventArgs^ e) {

   cr=double::Parse(textBox1->Text);
   ci=double::Parse(textBox2->Text);
   step=double::Parse(textBox3->Text);
   count_max=int::Parse(textBox4->Text);
   color_number=int::Parse(textBox5->Text);
   Invalidate();
   this->Close();

}

//ボタン「キャンセル」をクリックした時のメソッド
private: System::Void buttonCancel_Click(System::Object^ sender, System::EventArgs^ e) {

   Invalidate();
   this->Close();

}

得られた画面
 プログラムを起動すると、デフォルトでパソコン画面に図2のようなMandelbrot図形の全貌が表示され、下部には、その時の諸条件(初期値)が表示される。図形の内部が暗黒色で描かれているのは、最大繰返し回数(この場合は256回)では、Zの絶対値が一定の値(2.0)に達しなかったことを示す。画面の右には、その時のマウス位置に対応したCの値が表示される(この例では、マウスが図形の外部にあるので、表示されていない)。


図2 プログラムを起動した直後の画面


 Mandelbrot画面上をマウスで探索し、右クリックすると、その部分が拡大される。それを繰り返しながら、「条件の設定」ダイアログボックスで表示色の階調数を変えたり、繰返し回数の上限を変えたりすると、思いがけない美しいパターンに遭遇することがある。図3は、その一例である。マウスを図形の中心に置いたので、その場所のCの値が右上方に表示されている。


図3 マウスと「条件の設定」ダイアログボックスで探索した図形の一例


 メニューから「条件の設定」をクリックすると、図4のようなダイアログボックスが表示される。最初は初期値が入力されているので、必要に応じて変更し、「OK」をクリックすると、その条件に合った新しいMandelbrot図形が描画される。一部しか描画されない時は、メニュー「再描画」をクリックする。図4は、図3を描画するために設定した例を示す。


図4 「条件の設定」ダイアログボックス


色々なMandelblot図形
 Mandelblot図形はフラクタル図形の一つであり、細部にも似たような図形が見られる。図5に例を示すので、各自で探索して頂きたい。表示してある条件を入力すれば、同じ図形が得られる。


Cの実数部の中心=-1.440798784
Cの虚数部の中心=0.002339072
ステップ/ピクセル=3.2E-09
最大繰返し回数=256
表示色の階調=64
Cの実数部の中心=-0.7448104
Cの虚数部の中心=0.11282672
ステップ/ピクセル=1.6E-07
最大繰返し回数=4096
表示色の階調=256
Cの実数部の中心=-0.0745
Cの虚数部の中心=0.9705
ステップ/ピクセル=1E-04
最大繰返し回数=256
表示色の階調=-32
Cの実数部の中心=-0.77262715
Cの虚数部の中心=0.125196656
ステップ/ピクセル=2E-06
最大繰返し回数=4096
表示色の階調=128

図5 色々なMandelbrot図形