私は現在、非常に巨大な(65536x65536ピクセル以上の)マンデルブロ画像を生成するプログラムを作成しています。それらを正当化するスペクトルとカラーリングスキームを考案したいと思います。 ウィキペディアの特集マンデルブロ画像 は、特にシーケンスのすべてのズームレベルでパレットがどのように変化したままであるかを示す優れた例のようです。ただし、パレットを回転させているのか、それとも他のトリックを実行してこれを実現しているのかはわかりません。
私はマンデルブロ集合の 滑らかな色付けアルゴリズム に精通しているので、バンディングを回避できますが、このアルゴリズムからの出力値に色を割り当てる方法が必要です。
私が生成している画像はピラミッド型であるため(たとえば、それぞれが前の画像の半分のサイズの一連の画像)、次の画像の間でパレットが変更されている限り、ある種の回転パレットを使用できます。ズームレベルはそれほど明白ではありません。
私の最終的な解決策は、見栄えの良い(そしてかなり大きな)パレットを作成し、それをソースに定数配列として格納し、滑らかな色付けアルゴリズムを使用してその中のインデックス間を補間することでした。パレットは折り返されます(そして連続するように設計されています)が、これはそれほど重要ではないようです。
これは滑らかな色のアルゴリズムです:
複素数z0
から始めて、エスケープするまでn
回繰り返します。終点をzn
とします。
滑らかな値は
nsmooth := n + 1 - Math.log(Math.log(zn.abs()))/Math.log(2)
これはマンデルブロでのみ機能します。ジュリア集合の滑らかな関数を計算する場合は、次を使用します。
Complex z = new Complex(x,y);
double smoothcolor = Math.exp(-z.abs());
for(i=0;i<max_iter && z.abs() < 30;i++) {
z = f(z);
smoothcolor += Math.exp(-z.abs());
}
その場合、smoothcolor
は(0,max_iter)
の間隔にあります。
smoothcolor
をmax_iter
で除算して、0から1までの値を取得します。
値から滑らかな色を取得するには:
これは、たとえば(Javaの場合)次のように呼び出すことができます。
Color.HSBtoRGB(0.95f + 10 * smoothcolor ,0.6f,1.0f);
hSB色パラメータの最初の値は、カラーサークルから色を定義するために使用されるためです。
これは、ナイーブなマンデルブロジェネレーターの典型的な内部ループです。滑らかな色を得るには、実際の複雑な「長さ」と、救済した反復を渡します。色の計算に使用する変数を確認できるように、マンデルブロコードを含めました。
for (ix = 0; ix < panelMain.Width; ix++)
{
cx = cxMin + (double )ix * pixelWidth;
// init this go
zx = 0.0;
zy = 0.0;
zx2 = 0.0;
zy2 = 0.0;
for (i = 0; i < iterationMax && ((zx2 + zy2) < er2); i++)
{
zy = zx * zy * 2.0 + cy;
zx = zx2 - zy2 + cx;
zx2 = zx * zx;
zy2 = zy * zy;
}
if (i == iterationMax)
{
// interior, part of set, black
// set colour to black
g.FillRectangle(sbBlack, ix, iy, 1, 1);
}
else
{
// outside, set colour proportional to time/distance it took to converge
// set colour not black
SolidBrush sbNeato = new SolidBrush(MapColor(i, zx2, zy2));
g.FillRectangle(sbNeato, ix, iy, 1, 1);
}
および以下のMapColor :( ColorFromHSV関数を取得するためのこのリンク を参照)
private Color MapColor(int i, double r, double c)
{
double di=(double )i;
double zn;
double hue;
zn = Math.Sqrt(r + c);
hue = di + 1.0 - Math.Log(Math.Log(Math.Abs(zn))) / Math.Log(2.0); // 2 is escape radius
hue = 0.95 + 20.0 * hue; // adjust to make it prettier
// the hsv function expects values from 0 to 360
while (hue > 360.0)
hue -= 360.0;
while (hue < 0.0)
hue += 360.0;
return ColorFromHSV(hue, 0.8, 1.0);
}
MapColourは、0から1までの救済値を「平滑化」し、ひどいバンディングなしで色をマッピングするために使用できます。 MapColourやhsv関数で遊ぶと、使用する色を変更できます。
スムーズカラーリングアルゴリズムを使用してビューポート内のすべての値を計算し、パレットを最小値から最大値にマップします。したがって、ズームインして高い値が表示されなくなると、パレットも縮小されます。 nとBの定数が同じであると、完全にズームアウトされたセットの範囲は0.0〜1.0になりますが、ズームを深くすると、ダイナミックレンジが縮小し、200%ズームで0.0〜0.1、0.0〜0.0001になります。 20000%ズームなど.
試行錯誤で簡単にできるようです。使用するエンドポイントの色(黒と白、青と黄色、濃い赤と薄緑など)のHSV1とHSV2(色相、彩度、値)を定義でき、を割り当てるアルゴリズムがあると仮定します。各ピクセルに対して0.0から1.0の間の値P。次に、そのピクセルの色は次のようになります
(H2 - H1) * P + H1 = HP
(S2 - S1) * P + S1 = SP
(V2 - V1) * P + V1 = VP
それが終わったら、結果を観察して、どのように気に入っているかを確認してください。 Pを割り当てるアルゴリズムが連続である場合、勾配も滑らかである必要があります。
その画像のカラーマッピングで何が起こっているのかというと、インデックスで「ログ伝達関数」を使用しているということです(ドキュメントによると)。それがどれほど正確にそれをしているのか、私はまだ理解していません。それを作成したプログラムは400色のパレットを使用しているため、インデックス範囲は[0,399)で、必要に応じて折り返します。私はそれの振る舞いにかなり近づくことができました。 [0,1)のインデックス範囲を使用し、次のようにマップします。
double value = Math.log(0.021 * (iteration + delta + 60)) + 0.72;
value = value - Math.floor(value);
結果を一致させるために、そこでこれらの特別な定数を使用しなければならないのは奇妙なことです。なぜなら、それらがそのようなことをしているとは思えないからです。しかし、最終的には何が機能しますか?