Smooth spectrum for Mandelbrot Set rendering
Asked Answered
C

7

37

I'm currently writing a program to generate really enormous (65536x65536 pixels and above) Mandelbrot images, and I'd like to devise a spectrum and coloring scheme that does them justice. The wikipedia featured mandelbrot image seems like an excellent example, especially how the palette remains varied at all zoom levels of the sequence. I'm not sure if it's rotating the palette or doing some other trick to achieve this, though.

I'm familiar with the smooth coloring algorithm for the mandelbrot set, so I can avoid banding, but I still need a way to assign colors to output values from this algorithm.

The images I'm generating are pyramidal (eg, a series of images, each of which has half the dimensions of the previous one), so I can use a rotating palette of some sort, as long as the change in the palette between subsequent zoom levels isn't too obvious.

Coldhearted answered 15/12, 2008 at 19:26 Comment(0)
C
4

My eventual solution was to create a nice looking (and fairly large) palette and store it as a constant array in the source, then interpolate between indexes in it using the smooth coloring algorithm. The palette wraps (and is designed to be continuous), but this doesn't appear to matter much.

Coldhearted answered 17/12, 2008 at 10:52 Comment(0)
K
44

This is the smooth color algorithm:

Lets say you start with the complex number z0 and iterate n times until it escapes. Let the end point be zn.

A smooth value would be

nsmooth := n + 1 - Math.log(Math.log(zn.abs()))/Math.log(2)

This only works for mandelbrot, if you want to compute a smooth function for julia sets, then use

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());
}

Then smoothcolor is in the interval (0,max_iter).

Divide smoothcolor with max_iter to get a value between 0 and 1.

To get a smooth color from the value:

This can be called, for example (in Java):

Color.HSBtoRGB(0.95f + 10 * smoothcolor ,0.6f,1.0f);

since the first value in HSB color parameters is used to define the color from the color circle.

Kilimanjaro answered 7/8, 2009 at 9:21 Comment(4)
Thanks for the Julia Set smooth color formula, used it in shadertoy.com/view/XssXDrGent
How do you convert nsmooth to a color value?Descriptive
Color.HSBtoRGB(0.95f + 10 * nsmooth ,0.6f,1.0f); perhaps?Kilimanjaro
I wonder if there is a formula that could be used for both Mandelbrot and Julia set.Newman
M
6

Use the smooth coloring algorithm to calculate all of the values within the viewport, then map your palette from the lowest to highest value. Thus, as you zoom in and the higher values are no longer visible, the palette will scale down as well. With the same constants for n and B you will end up with a range of 0.0 to 1.0 for a fully zoomed out set, but at deeper zooms the dynamic range will shrink, to say 0.0 to 0.1 at 200% zoom, 0.0 to 0.0001 at 20000% zoom, etc.

Martsen answered 16/12, 2008 at 9:22 Comment(0)
G
6

Here is a typical inner loop for a naive Mandelbrot generator. To get a smooth colour you want to pass in the real and complex "lengths" and the iteration you bailed out at. I've included the Mandelbrot code so you can see which vars to use to calculate the colour.

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);
        }

and MapColor below: (see this link to get the ColorFromHSV function)

 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 is "smoothing" the bailout values from 0 to 1 which then can be used to map a colour without horrible banding. Playing with MapColour and/or the hsv function lets you alter what colours are used.

Graveclothes answered 7/9, 2013 at 23:19 Comment(1)
What do you mean with naive generator? How would you make it not "naive"? Do you mean a multithreaded generator?Schwarzwald
R
4

Seems simple to do by trial and error. Assume you can define HSV1 and HSV2 (hue, saturation, value) of the endpoint colors you wish to use (black and white; blue and yellow; dark red and light green; etc.), and assume you have an algorithm to assign a value P between 0.0 and 1.0 to each of your pixels. Then that pixel's color becomes

(H2 - H1) * P + H1 = HP
(S2 - S1) * P + S1 = SP
(V2 - V1) * P + V1 = VP

With that done, just observe the results and see how you like them. If the algorithm to assign P is continuous, then the gradient should be smooth as well.

Ragouzis answered 15/12, 2008 at 20:0 Comment(2)
I can't guarantee a range of 0 to 1.0 for the values simply, though, since that results in the higher-resolution images lacking detail (they end up with all high-iteration values). Some sort of gradual palette rotation is needed.Coldhearted
That's okay. If the range is infinite in size, it can still have a period, even if that period is imposed upon it. Simply choose an appropriate value N, and map values to themselves mod N, and then scale that to the 0-1 range.Ragouzis
C
4

My eventual solution was to create a nice looking (and fairly large) palette and store it as a constant array in the source, then interpolate between indexes in it using the smooth coloring algorithm. The palette wraps (and is designed to be continuous), but this doesn't appear to matter much.

Coldhearted answered 17/12, 2008 at 10:52 Comment(0)
W
0

What's going on with the color mapping in that image is that it's using a 'log transfer function' on the index (according to documentation). How exactly it's doing it I still haven't figured out yet. The program that produced it uses a palette of 400 colors, so index ranges [0,399), wrapping around if needed. I've managed to get pretty close to matching it's behavior. I use an index range of [0,1) and map it like so:

    double value = Math.log(0.021 * (iteration + delta + 60)) + 0.72;
    value = value - Math.floor(value);

It's kind of odd that I have to use these special constants in there to get my results to match, since I doubt they do any of that. But whatever works in the end, right?

Wisla answered 4/4, 2019 at 17:42 Comment(0)
J
-2

here you can find a version with javascript

usage :

var rgbcol = [] ;
var rgbcol = MapColor ( Iteration , Zy2,Zx2 ) ;
point ( ctx , iX, iY ,rgbcol[0],rgbcol[1],rgbcol[2] );  

function

/*
 * The Mandelbrot Set, in HTML5 canvas and javascript.
 * https://github.com/cslarsen/mandelbrot-js
 *
 * Copyright (C) 2012 Christian Stigen Larsen
*/

/*
 * Convert hue-saturation-value/luminosity to RGB.
 *
 * Input ranges:
 *   H =   [0, 360] (integer degrees)
 *   S = [0.0, 1.0] (float)
 *   V = [0.0, 1.0] (float)
 */
function hsv_to_rgb(h, s, v)
{
  if ( v > 1.0 ) v = 1.0;
  var hp = h/60.0;
  var c = v * s;
  var x = c*(1 - Math.abs((hp % 2) - 1));
  var rgb = [0,0,0];

  if ( 0<=hp && hp<1 ) rgb = [c, x, 0];
  if ( 1<=hp && hp<2 ) rgb = [x, c, 0];
  if ( 2<=hp && hp<3 ) rgb = [0, c, x];
  if ( 3<=hp && hp<4 ) rgb = [0, x, c];
  if ( 4<=hp && hp<5 ) rgb = [x, 0, c];
  if ( 5<=hp && hp<6 ) rgb = [c, 0, x];

  var m = v - c;
  rgb[0] += m;
  rgb[1] += m;
  rgb[2] += m;

  rgb[0] *= 255;
  rgb[1] *= 255;
  rgb[2] *= 255;

  rgb[0] = parseInt ( rgb[0] ); 
  rgb[1] = parseInt ( rgb[1] );
  rgb[2] = parseInt ( rgb[2] );  

  return rgb;
}

// https://mcmap.net/q/415786/-smooth-spectrum-for-mandelbrot-set-rendering
// alex russel : http://stackoverflow.com/users/2146829/alex-russell

function MapColor(i,r,c)
{
    var di= i;
    var zn;
    var 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 hsv_to_rgb(hue, 0.8, 1.0);
}
Jimmy answered 21/6, 2016 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.