2010. 6.13.
石立 喬

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

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

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

Mandelbrot集合の画像の作成方法
 Mandelbrot図形は、ZおよびCを複素数としたとき、Z=Z2+Cを繰り返し計算し、何回繰り返すとZの絶対値が一定の値を越えるか?(例えば2.0を越えると発散すると予測する)、またはあらかじめ設定した回数(例えば16384回)以内では一定値に到達しないか?(ゼロまたは一定値に収束すると予測する)を調べて、繰り返し回数を色で表現したものである。Cは、パソコン画面上に設けた原点(X0,Y0)から測った座標を(x,y)としたとき、
  c_real(Cの実数部)=x*step(ピクセル当りの変化分)+cr(Cの実数部の中心)
  c_imag(Cの虚数部)=y*step(ピクセル当りの変化分)+ci(Cの虚数部の中心)
で与える。
 step(ピクセル当りの変化分)は、xとyに対して同じ値を使用する。stepを大きくすると、画面上でのMandelbrot図形が縮小され、小さくすると拡大される。
Zの初期値は、実数部、虚数部ともにゼロとする。
 C++の標準ライブラリによらず、Z=Z2+Cの繰り返し計算は、
  z_real(Zの実数部)=z_real*z_real-z_imag*z_imag
  z_imag(Zの虚数部)=-2*z_real*z_imag
 計算を打ち切るためのZの絶対値は、
  value(Zの絶対値の二乗)=z_real*z_real+z_imag*z_imag
で求めた。これも、高速化のために平方根の演算を省略している。
 なお、z_real*z_realとz_imag*z_imagの乗算をそれぞれ二回も実行するのは非効率的なので、それらをz_real2、z_imag2として、あらかじめ計算しておき、使用した。
 繰返し回数を色に変換するには、自作のcountToColorメソッドを使用した。この中で、繰返し回数を色相(hue)に対応させ、彩度(saturation)、明度(brightness)は常に1とした。Visual C++ 2010で使える.NET Frameworkには、HSB値から色を生成するメソッドが無いので(JavaにはColor.getHSBColor(h,s,b)があるのに)、簡略化した自作メソッドで高速化を図った。
 countは繰返し回数を表し、初期値は0で、Z=Z2+Cを一回実行する度に増加する。繰返し回数の上限count_maxに達すると、発散しなかったとして-1を入れる。
 count_maxは外部から与える繰返し回数の上限で、大きすぎると計算時間が長くかかり、小さくするとvalueが4.0に達しない前に計算を打ち切ることになり、暗黒色の部分が増える。
 color_numberは繰返し回数を表示するための階調数で、カラーの階調数を表す。図形が最も美しく見える値があり、試行錯誤で決める。

プログラムの機能
 1) 起動させると、基本のMandelbrot図形が表示され、初期設定値がテキストボックスに入っている。
 2) 画面上でマウスを左クリックすると、その位置に相当するCの値が新しい図形の中心に変更され、中心が移動した図形が描画される。
 3) 画面上でマウスを右クリックすると、その位置に相当するCの値が新しい図形の中心に変更され、同時に表示倍率が5倍になる(ピクセル当たりのステップが5分の1になり、画面が拡大される)。
 4)テキストボックスに各定数を直接入力し、「OK」ボタンをクリックすることにより、画面を描画することもできるので、気に入った画像があれば、その条件をメモしておき、再現できる。
 5)テキストボックスへの入力を中止したい場合は、「キャンセル」ボタンをクリックする。
 6) 「元に戻す」ボタンをクリックすると、図形上でマウスクリックした直前の図形に戻ることができる。
 7)「最初に戻す」ボタンで、初期設定値に戻ることができる。
 8) 繰り返しによってZの値の絶対値が一定の値(2.0を使用)に達した場合は、その時の繰返し回数をカラー(赤→黄→緑→シアン→青→マゼンタ→赤の順)で表示する。繰返し回数が階調数を超えた場合には、赤からもう一度繰り返す。
 9) 最大繰返し回数に達しても一定の値に至らなかった場合は、黒色で表示する。最大繰り返し回数を大きく設定すると、暗黒色で表されていたものがカラーで表示される場合もある。

プログラムの説明
主となるプログラムはForm1_Paint()に置き、ここでは、
 1)複素数計算を用いたZの漸化式の実行
 2)繰返し回数のチェック
 3)Zの絶対値のチェック
 4)繰返し回数の色への変換(countToColorメソッドの呼び出し)
 5)上記の色による描画
を実行する。
 プログラム起動時に一回だけ呼び出されるForm1_Load()には、各定数の初期設定と、各textBoxへの書き込みを行う。
 図形上でマウスをクリックした時のメソッドForm1_MouseDown()では、マウス位置を求め、それが図形の範囲であれば、「元に戻す」操作が可能なように、それまでのCの中心位置crとciをold_cr、old_ciに、stepをold_stepに格納した後に、新しいCの中心位置を計算する。右クリックの場合には、さらにstepを5分の1にする(図形の倍率を拡大)。これらの値はを各textBoxに表示し、再描画する。
 繰返し回数を色に変換するメソッドcountToColor()は、カウント数と表示色の階調数を受け取って、0から255の範囲に調整して色相(hue)値とし、色相をColorクラスの値に変換するメソッドNumberToRGBColor()を呼び出す。

プログラム
◎Form1.hに手で入力した部分を下記に示す。
    static int X0=210,Y0=210;   //図形の中心位置
    float ci,cr,step;
    float old_ci,old_cr,old_step; //元に戻すための記憶場所
    int count_max,color_number;

    private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {

        cr=-0.75f;
        ci=0.0f;
        step=0.00625f;
        count_max=256;
        color_number=16;
        textBox1->Text=cr.ToString();
        textBox2->Text=ci.ToString();
        textBox3->Text=step.ToString();
        textBox4->Text=count_max.ToString();
        textBox5->Text=color_number.ToString();

    }

    private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) {

        Graphics^ g=e->Graphics;

        int x,y,count;
        float z_real,z_imag,c_real,c_imag;
        float z_real2,z_imag2,value;
        Brush^ brush1;

        for(x=-200;x<=200;x++){
            c_real=x*step+cr;
            for(y=-200;y<=200;y++){
                c_imag=y*step+ci;
                z_real=0.0f;
                z_imag=0.0f;
                count=0;
                do{
                    z_real2=z_real*z_real;
                    z_imag2=z_imag*z_imag;
                    z_imag=2.0f*z_real*z_imag+c_imag;
                    z_real=z_real2-z_imag2+c_real;
                    value=z_real2+z_imag2;
                    count++;
                    if(count>count_max){
                        count=-1;
                        break;
                    }
                }while(value<4.0);
                brush1=gcnew SolidBrush(countToColor(count,color_number));
                g->FillRectangle(brush1,X0+x,Y0-y,1,1);
            }
        }

    }

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

        if(n<0) return Color::FromArgb(0,0,0);   //黒
        int d=n % base;
        d*=(256/base);
        return NUmberToRGBColor(d);
    }

    //色相(0~255で表した)を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);
    }

    //マウスをクリック
    private: System::Void Form1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {

        int x=e->X;
        int y=e->Y;
        if(x>X0-200 && x<X0+200 && y>Y0-200 && y<Y0+200){   //図形の範囲内
            old_cr=cr;
            old_ci=ci;
            old_step=step;
            cr+=(x-X0)*step;
            ci-=(y-Y0)*step;
            if(e->Button==Windows::Forms::MouseButtons::Right)  //右クリック
                step/=5.0;
            textBox1->Text=cr.ToString();
            textBox2->Text=ci.ToString();
            textBox3->Text=step.ToString();
            textBox4->Text=count_max.ToString();
            textBox5->Text=color_number.ToString();
            Invalidate();
        }

    }

    //「キャンセル」ボタンをクリック
    private: System::Void buttonCancel_Click(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();
        Invalidate();

    }

    //「OK」ボタンをクリック
    private: System::Void buttonOK_Click(System::Object^ sender, System::EventArgs^ e) {

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

    }

    //「元に戻すvボタンをクリック
    private: System::Void buttonUndo_Click(System::Object^ sender, System::EventArgs^ e) {

        cr=old_cr;
        ci=old_ci;
        step=old_step;
        textBox1->Text=cr.ToString();
        textBox2->Text=ci.ToString();
        textBox3->Text=step.ToString();
        Invalidate();

    }

    //「最初に戻す」ボタンをクリック
    private: System::Void buttonReset_Click(System::Object^ sender, System::EventArgs^ e) {

        cr=-0.75f;
        ci=0.0f;
        step=0.00625f;
        count_max=256;
        color_number=16;
        textBox1->Text=cr.ToString();
        textBox2->Text=ci.ToString();
        textBox3->Text=step.ToString();
        textBox4->Text=count_max.ToString();
        textBox5->Text=color_number.ToString();
        Invalidate();

    }

得られた画面
 プログラムを起動すると、図1のようなMandelbrot図形の全貌が表示され、右には、その時の諸条件(初期値)が表示される。図形の内部が黒色で描かれているのは、最大繰返し回数(この場合は256回)では、Zの絶対値が一定の値(2.0)に達しなかったことを示す。


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


 Mandelbrot画面上をマウスで探索し、右クリックすると、その部分が拡大される。それを繰り返しながら、テキストボックスで表示色の階調数を変えたり、繰返し回数の上限を変えたりすると、思いがけない美しいパターンに遭遇することがある。図2は、その一例である。


図2 マウスとテキストボックスで探索した美しい図形の一例


「Visual C++ の勉強部屋」(目次)へ