fractals…マンデルブロ集合を描いてみる
怒濤のごとく押し寄せる年の瀬に翻弄しつつ仕事をこなしています。もっともわたし自身は師たる人間ではありませんから走り回る事はありません。キーボードとマウス武器にモニタにかじりつく日々でございます。
最近、Configurator3のHTMLウイジェット君がとっても面白い事に気がつきました。まあ、アドビの中の人からの「オモシレーカラ、イジッテミロ」的なメールで見てふ〜んと思ったのが始まりなのですが、HTMLからJavascript利用してPSとかIDコントロールしたり、jsxファイル読込んだりで、うまく実装するとHTML上に設けたインターフェース介してインタラクティブにドキュメントを生成するのも出来そうな感じです。jQueryとかも対応出来るみたいでなかなか面白そうです。
さて、本題です。今回はPSを利用したイメージの生成をやってみたのでご紹介してみましょう。
まあ、自動的に描画出来るのって言えば数学的に生成するものが大半ですよね。今回のヤツもそうです。まずはこちらをご覧下さい。
Z(n+1) = Z(n)^2 + c
Z0 = 0
こちらは漸化式と呼ばれる物です。詳細を解説するのは骨がおれるのでご勘弁下さい。ググっていただくと分かりやすい物が山ほどでてきますので。この式自体はマンデルブロ集合の定義です。皆さんもきっと見た事のあるイメージですが、ひょうたんを寝かせてとげとげをいっぱい生やした様なやつです。フラクタルの描画では鉄板的なものですが、これの何が大変かって言うのは計算量です。前出の漸化式は複素平面上にある任意の点が収束又は発散するかを判別する為の物です。ですから、これらをPhotoshopにプロットさせるには複素数を使わずに処理したいので、zn を点 (xn, yn) に、c を点 (a, b) にそれぞれ置き代えて、
Xn = Xn-1^2 – Yn-1^2 + a;
Yn= 2 * Xn-1 * Yn-1 + b;
とした式を利用します。集合の判定ですが、座標[a,b]に対応する値を代入して再帰的にループさせ発散・収束を判定するのですが、nを無限大まで繰り返すと言うわけにはいきませんのでスレッショルドを設定してあげる必要があります。まあ、詳しくはWikipediaでもご参照頂くとしてコードをご覧下さい。
preferences.rulerUnits = Units.PIXELS;
var dc = app.documents.add(601,401);
var RGBColor = new SolidColor();
var xpos, ypos;
var a,b;
var rslt = new Array();
for (b=-2;b<2;b+=0.01){
for (a=-3;a<3;a+=0.01){
rslt = mandel(a, b);
xpos = 300 + Math.round(a*100);
ypos = 200 – Math.round(b*100);
drawPx(xpos,ypos,rslt);
}
}
function mandel(a,b){
var x = 0;
var y = 0;
var x1, y1;
var nmax = 127;
for(var n=0;n<=nmax;n++){
x1 = x * x – y * y + a;
y1= 2 * x * y + b;
if (x1*x1>2) return [n,false];
x=x1; y=y1;
}
return [x,true];
}
function drawPx(x,y,rslt){
var clr;
if (rslt[1]){
RGBColor.red = 255;
RGBColor.green = 255;
RGBColor.blue = 255;
} else {
RGBColor.green = 0;
RGBColor.blue = 128;
clr = 255 – Math.round(rslt[0]*20);
if (clr<0) clr = 0;
RGBColor.red = clr;
}
var bnds = [[x,y],[x+1,y],[x+1,y+1],[x,y+1],[x,y]];
activeDocument.selection.select(
bnds,
SelectionType.REPLACE ,
0,
false
);
activeDocument.selection.fill(
RGBColor,ColorBlendMode.NORMAL,
100,
false
);
}
画像のサイズは600×400です。仕上がりで黒く塗りつぶされている部分がマンデルブロ集合に含まれる部分です。スレッショルドは127に設定してあります。これは127回目までに発散の判定条件に引っかからなければ収束すると判断すると言う事です。しかしながら実際には境界面付近では相当な回数を演算しなければ発散しないケースが多いため、このスレッショルドを低く設定すると画像の精度が低くなります。実際に拡大してみますと黒く塗りつぶされたピクセルが境界付近に散らばっています。これらは全て本体の黒塗り部分に接続されているということが証明されているため離れて見えるのは計算精度の影響が現れていると言う事です。
ちなみに集合の外側のグラデーションは発散するまでのループ回数によって決定しています。しかしながら、このクラスのイメージでもこのスクリプトは3時間以上実行時間を食います。
パラメータを変えて試してみましょう。
b = -0.2 -> -1.0
a = -1.0 -> 0.6
step = 0.002
nmax = 255
と言う具合にスキャンする範囲と精度を変更します。
ご覧のように細部に同様のパターンが出現してきます。
さらに拡大してみます。
b = -0.2 -> -0.4
a = -1.0 -> -0.6
step = 0.0005
nmax = 255
この様に細分化して行くと境界の領域ではどんどん同一のパターンが出てきます。フラクタルパターンの特徴が良く出ていますね。この400×400のパターンで生成するのに約4時間ととんでもなく時間がかかります。
ちなみにPixelBenderでこの処理を行うとほぼリアルタイムでイメージが出来ます。GPU利用でマルチスレッド処理するのでとんでもなく演算が速いです。なぜ捨てたんだ???
まあ、ExtendScriptはイメージの生成は想定していないし対応しようと言う気持ちさえないって事でよろしいかと思うんです。