Smooth spectrum for Mandelbrot Set rendering

34,930

Solution 1

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.

Solution 2

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.

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
34,930
Nick Johnson
Author by

Nick Johnson

Core developer for the Ethereum Foundation. Enthusiastic about software engineering and computer science, especially when it comes to interesting algorithms and approaches to solving difficult problems.

Updated on April 05, 2020

Comments

  • Nick Johnson
    Nick Johnson about 4 years

    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.

  • Nick Johnson
    Nick Johnson over 15 years
    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.
  • Erik Martino
    Erik Martino almost 10 years
    Thanks for the Julia Set smooth color formula, used it in shadertoy.com/view/XssXDr
  • vcapra1
    vcapra1 over 5 years
    How do you convert nsmooth to a color value?
  • Per Alexandersson
    Per Alexandersson over 5 years
    Color.HSBtoRGB(0.95f + 10 * nsmooth ,0.6f,1.0f); perhaps?
  • Falk
    Falk about 3 years
    I wonder if there is a formula that could be used for both Mandelbrot and Julia set.
  • Nicky
    Nicky over 2 years
    What do you mean with naive generator? How would you make it not "naive"? Do you mean a multithreaded generator?