2017. 6.20.
石立 喬

Visual Studio Community 2017の使い方(8)

――― CanvasにMandelbrot図形を描く―――

 電気技術者にとって、カオスやフラクタルは興味ある対象である。電気・電子回路には直接関係がないが、一寸した芸術として、Mandelbrot図形の描画を試みる。Mandelbrot図形はフラクタル図形の一種で、拡大すると再び相似の図形が現れる。図形上をマウスクリックで自由に探索して興味ある図形を捜し求めることができ、TextBoxで希望する条件を設定することもできる。Canvas上に点を打つには、1 x 1の塗りつぶし矩形を描く方法が最善であったこれは、「Visual C++ 2010 Expressの易しい使い方(12)」のUWP版である。

Mandelbrot集合の画像の作成方法
 Mandelbrot図形は、ZおよびCを複素数としたとき、Z=Z2+Cを繰り返し計算し、何回繰り返すとZの絶対値が一定の値を越えるか?(例えば2.0を越えると発散すると予測する)、またはあらかじめ設定した回数(例えば256回)以内では一定値に到達しないか?(ゼロまたは一定値に収束すると予測する)を調べて、繰返し回数を色で表現したものである。Cは、画面上に設けた原点(X0,Y0)からの座標を(x,y)としたとき、
  c_real(Cの実数部)=x*step(ピクセル当りの変化分)+cr(Cの実数部の中心)
  c_imag(Cの虚数部)=y*step(ピクセル当りの変化分)+ci(Cの虚数部の中心)
で与える。ただし、画面の表示上、実際には、yの代わりに-yを用いている。
 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とした。
 countは繰返し回数を表し、初期値は0で、Z=Z2+Cを一回実行する度にインクリメントする。繰返し回数の上限count_maxに達すると、発散しなかったとして-1を入れる。
 count_maxは外部から与える繰返し回数の上限で、大きすぎると計算時間が長くかかり、小さくするとvalueが4.0に達しない前に計算を打ち切ることになり、暗黒色の部分が増える。
 color_numberは繰返し回数を表示するための階調数で、カラーの階調数を表す。図形が最も美しく見える値があり、試行錯誤で決める。

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

Canvas上でのマウスの使用方法
 UWPアプリでマウスを使用するには、いくらかの設定が必要になる。UWPの特徴として、入力デバイスはマウスに限定しないので、一般的な名称のPointerが用いられる。Canvas(CanvasControlの名称をcanvas1として)上でPointerがPressedの状態になると、canvas1_PointerPressedのイベントが発生する。そこで、GetCurrentPoint(canvas1)によりPointerPointが返され、そのプロパティからPointerの座標値を取得できる。
◎XMALコードの設定(図1参照)
 CanvasControl(名称canvas1)に、PointerPressedイベントを追加する。具体的には、他のイベントも含めて(一部省略)、
   <canvas:CanvasContrrol
            x:Name="canvas1" ----------------------- CanvasControlに付けた名前
            Draw="canvas1_Draw" -------------------- Canvasに図形を描くためのイベント、説明済み
            Loaded="canvas1_Loaded" ----------------- Canvasが最初に開かれた時の初期化に使用
            PointerPressed="canvas1_PointerPressed" ---- Canvas内でポインタ部品が押されたイベント
    />
のようになる。「デザイナ」画面上での特別な設定は要らない。
◎MainPage.xaml.cppコードの設定
 1)名前空間の追加(図2参照)
    PointerPointのために、using namespace Windows::UI::Input; を追加する。
 2)canvas1_PointerPressedイベントハンドラの内容を記述(図3参照)
    PointerPoint^ ppt = e->GetCurrentPoint(canvas1);
    float x = ppt->Position.X;
    float y = ppt->Position.Y;
として、ポインタ部品の座標位置を得る(下記参照)。
◎PointerRoutedEventArgsクラス
 名前空間Windows::UI::Xaml::Inputを使用する(テンプレートとしてデフォルトで設定済み)。PointerPressed、PointerEntered、PointerMovedなどのイベントに対して用いられる。メソッドにGetCurrentPointがあり、
    GetCurrentPoint(UIElement relativeTo)
でPointerPointクラスが戻される。ここでは、UIElenemtにcanvas1を入れる。
◎PointerPointクラス
 この使用には、名前空間Windows::UI::Inputが必要であるので追加する。プロパティには、下記がある。
    Position ------ 値はPoint構造体
    Properties ---- 値はPointerPointPropertiesクラス
◎Point構造体
 フィールドには、float Xとfloat Yがある。
◎PointerPointPropertiesクラス
 名前空間Windows::UI::Inputを使用する。プロパティには、IsLeftButtonPressed、IsRightButtonPressed等がある(値は、trueまたはfalse)ので、右クリックを検出できる。

作成したXAMLコード
 すでに前稿で説明たように、Canvas使用のためにMicrosoft.Graphics.Canvas.UI.Xamlを名前空間として指定する。Cancasの右にStackPanelを設定し(「デザイン」画面へ「ツールボックス」から持ってきて、詳細はエディタで編集)、TextBlockとTextBoxを交互に配置し、さらにその下にButtonを並べる。間隔を開けるために、Margin="0, 5, 0, 0"などを明示的に用いている。



図1 作成したXAMLコード


プログラムの説明
 Mandelbrot図形を描画するプログラムはcanvas1_Draw(図3参照)に記述し、ここでは、
 1)複素数計算を用いたZの漸化式の実行
 2)繰返し回数のチェック
 3)Zの絶対値のチェック
 4)繰返し回数の色への変換(countToColorメソッドの呼び出し)
 5)上記の色による描画
を実行する。
 色による描画は、試行の結果、FillRectangleメソッドで点を打つのが最も良いことが分かった(図13参照)。引数は、
    FillRectangle(
       float x, ---------------- 点を打つX座標
       float y, ---------------- 点を打つY座標
       float w, ---------------- 矩形の幅、点を打つには1にする
       float h, ---------------- 矩形の高さ、点を打つには1にする
       Windows::UI::Color color --- 点の色
    )
である。
 プログラム起動時に一回だけ呼び出されるcanvas1_Loaded(図6参照)には、各定数の初期設定と、各TextBoxへの書き込みを行う。
 図形上でマウスをクリックした時のメソッドcanvas1_PointerPressed(図5参照)では、マウス位置を求め、それが図形の範囲であれば、「一つ戻す」操作が可能なように、それまでのCの中心位置crとciをcr_oldとci_oldに、stepをstep_oldに格納した後に、新しいCの中心位置を計算する。図5の行番号120で、「ci -=」としているのは、Y方向の極性を逆にするためである。右クリックの場合には、さらにstepを5分の1にする(図形の倍率を拡大)。これらの値はを各TextBoxに表示し、再描画する。
 繰返し回数を色に変換するメソッドcountToColor(図4参照)は、カウント数countと表示色の階調数color_numberを受け取って、0から255の範囲に調整して色相(hue)値とし、色相をColorクラスの値に変換する。


図2 プログラムの最初の部分でヘッダーファイルと名前空間を追加する




図3 Mandelbrot図形を描くcanvas1_Drawメソッド



図4 発散までの回数(count)を色に変換するメソッド



図5 マウスクリックで各種データを変更し、canvas1を呼び出すイベントハンドラー



図6 プログラム開始時に最初に実行するメソッドで初期設定をする



図7 TextBoxへ入力したデータをキャンセル(cancel)する場合と実行する(OK)する場合のイベントハンドラー



図8 一つ前に戻す(undo)場合と最初の条件に戻す(reset)場合のイベントハンドラー


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


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


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


図10 Pointerで探索し、TextBoxで最適化した図形の一例


 「Visual C++ 2010 Expressの易しい使い方(12)」で良かったと思われる各種条件(データ)をTextBoxにキー入力して「入力OK」をクリックしたところ、同じ図形が再現できた(図11参照)。


図11 あらかじめ面白いと知られているデータを入れて求めた結果


Canvas上への点の打ち方について
 線を引いたり矩形や円を描いたりするメソッドはあるが、点を打つメソッドはない。そこで、どのメソッドで代用するのが良いかを調べた。図12は使用したメソッドで、図13はその結果である。図13では、6番目の結果が最も良く、本文で使用したように、
   cd->FillRectangle(x, y, 1, 1, color);
を用いるのが最適である(それしかない)。


図12 試してみた10種類のメソッド



図13 得られた結果、番号は図11のメソッドに対応する










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