Zalgorithm

Processing Mandelbrot improvements

Related to: notes / Mandelbrot set Processing implementation

Todo:

Using the double type #

This code fixes the floating point errors by using the double type.

Using a cumulative distribution function to map iterations to colors #

It improves the way that iteration counts are mapped to colors with a Cumulative distribution function:

Cumulative distribution function: the probability that some distribution will have a value equal or less than some value.

I’m doing this in three steps:

Create a histogram of how many numbers in the range (0, maxIter) have each iteration count:


int[] histogram = new int[maxIter + 1];
for (int i = 0; i < iterations.length; i++) {
  for (int j = 0; j < iterations[i].length; j++) {
    if (iterations[i][j] > 0) histogram[iterations[i][j]]++;
  }
}

Then creating a cumulative array (how many numbers have i iterations or fewer):

cumulative = new int[maxIter + 1];
cumulative[0] = histogram[0];
for (int i = 1; i <= maxIter; i++) {
  cumulative[i] = cumulative[i-1] + histogram[i];
}
totalEscaped = cumulative[maxIter];

totalEscaped is then a count of the total number of values that are accounted for in the cumulative array. It’s used to normalize the values into a probability:

void draw() {
  for (int i = 0; i < iterations.length; i++) {  // i indexes the imaginary axis
    for (int j = 0; j < iterations[i].length; j++) {  // j indexes the real axis
      if (iterations[i][j] == 0) {
        stroke(0, 0, 0);
      } else { // not in set
        int iter = iterations[i][j];
        // normalized is the probability
        float normalized = (float)cumulative[iter] / totalEscaped;
        float hue = map(normalized, 0, 1, 40, 360);
        stroke(hue, 74, 55);
      }
      point(j, i);
    }
  }

The result is that more common values (the lower iterations) are now assigned more distinct values by the map function. For example, this will ideally make it possible to distinguish the dividing line between bands of points that escaped after 3 iterations and bands of points that escaped after 4 iterations.

Improved code #

int rows = 1000;
int cols = 1000;

int maxIters = 10000; // adjust, especially for zoomed in areas
int maxIter = 0;
int totalEscaped;

int[][] iterations = new int[rows][cols];
int[] cumulative;

double aspect = 0.00000160 * 2.0;

double halfAspect = aspect / 2;
double centerReal = -1.769110375463767385;
double centerImag = 0.009020388228023440;
double startReal = centerReal - halfAspect;
double endReal = centerReal + halfAspect;
double startImag = centerImag + halfAspect;
double endImag = centerImag - halfAspect;
double[] imaginaryComponents = linspace(startImag, endImag, rows, "imaginary");
double[] realComponents = linspace(startReal, endReal, cols, "real");

void setup() {
  size(1000, 1000); // cols, rows
  colorMode(HSB, 360, 100, 100);
  iterations = fillArray(imaginaryComponents, realComponents);
  for (int i = 0; i < iterations.length; i++) {
    for (int j = 0; j < iterations[i].length; j++) {
      if (iterations[i][j] > maxIter) {
        maxIter = iterations[i][j];
      }
    }
  }
  println("maxIter", maxIter);
  int[] histogram = new int[maxIter + 1];
  for (int i = 0; i < iterations.length; i++) {
    for (int j = 0; j < iterations[i].length; j++) {
      if (iterations[i][j] > 0) histogram[iterations[i][j]]++;
    }
  }

  cumulative = new int[maxIter + 1];
  cumulative[0] = histogram[0];
  for (int i = 1; i <= maxIter; i++) {
    cumulative[i] = cumulative[i-1] + histogram[i];
  }
  totalEscaped = cumulative[maxIter];
}


void draw() {
  for (int i = 0; i < iterations.length; i++) {  // i indexes the imaginary axis
    for (int j = 0; j < iterations[i].length; j++) {  // j indexes the real axis
      if (iterations[i][j] == 0) {
        // 0 means "(probably) in the Mandelbrot set"; these points iterated maxIters times without diverging
        stroke(0, 0, 0);
      } else { // not in set
        int iter = iterations[i][j];
        float normalized = (float)cumulative[iter] / totalEscaped;
        float hue = map(normalized, 0, 1, 40, 360);
        stroke(hue, 74, 55);
      }
      point(j, i);
    }
  }
  String centerStr = String.format("%.8f", centerReal) + "_" + String.format("%.8f", centerImag) + "_" + String.format("%.8f", aspect);
  save("mandelbrot_" + centerStr + ".png");
  noLoop();
}

int[][] fillArray(double[] imagVals, double[] realVals) {
  int[][] result = new int[rows][cols];

  for (int i = 0; i < imagVals.length; i++) {
    for (int j = 0; j < realVals.length; j++) {
      Complex c = new Complex(realVals[j], imagVals[i]);
      Complex z = new Complex(0, 0);
      boolean diverged = false;
      for (int iter = 0; iter < maxIters; iter++) {
        if (z.magnitude() >= 2) {
          result[i][j] = iter;
          diverged = true;
          break;
        } else {
          z = z.mult(z).add(c);
        }
      }
      if (!diverged) {
        result[i][j] = 0;
      }
    }
  }

  return result;
}

double[] linspace(double start, double end, int num, String plane) {
  println("start (" + plane + "):", start);
  println("end: (" + plane + "):", end);
  println("range:", end - start);
  double[] result = new double[num];
  if (num == 1) {
    result[0] = start;
    return result;
  }
  double step = (end - start) / (num - 1);
  println("step:", step);
  for (int i = 0; i < num; i++) {
    result[i] = start + i * step;
  }
  return result;
}

class Complex {
  double re, im;

  Complex(double re, double im) {
    this.re = re;
    this.im = im;
  }

  Complex add(Complex other) {
    return new Complex(re + other.re, im + other.im);
  }

  Complex mult(Complex other) {
    return new Complex(
      re * other.re - im * other.im,
      re * other.im + im * other.re
      );
  }

  double magnitude() {
    return Math.sqrt(re*re + im*im);
  }
}

Images #

The filenames in the images below map to:

All images are 1000x1000 pixels (or 1000x1000 points on the complex plane)

mandelbrot_-1.76911032_0.00902039_0.00000320.png
mandelbrot_-1.76911032_0.00902039_0.00000320.png

mandelbrot_-0.50000000_0.00000000_3.00000000.png
mandelbrot_-0.50000000_0.00000000_3.00000000.png

mandelbrot_-0.74699998_0.10000000_0.00500000.png
mandelbrot_-0.74699998_0.10000000_0.00500000.png

start (imaginary): 0.0035000001080334187
end: (imaginary): -0.0035000001080334187
range: -0.007000000216066837
step: -7.007007223290127E-6
start (real): -1.9442999516613781
end: (real): -1.9372999514453113
range: 0.007000000216066837
step: 7.007007223290127E-6
maxIter 9213

mandelbrot_-1.94079995_0.00000000_0.00700000.png
mandelbrot_-1.94079995_0.00000000_0.00700000.png

start (imaginary): 7.812500552972779E-5
end: (imaginary): -7.812500552972779E-5
range: -1.5625001105945557E-4
step: -1.564064174769325E-7
start (real): -1.9426480960901245
end: (real): -1.942491846079065
range: 1.5625001105945557E-4
step: 1.564064174769325E-7
maxIter 9882

mandelbrot_-1.94256997_0.00000000_0.00015625.png
mandelbrot_-1.94256997_0.00000000_0.00015625.png

start (imaginary): 1.1718750556610757E-6
end: (imaginary): -1.1718750556610757E-6
range: -2.3437501113221515E-6
step: -2.3460962075296812E-9
start (real): -1.9427046573639473
end: (real): -1.942702313613836
range: 2.3437501113221515E-6
step: 2.3460962075296812E-9
maxIter 19796

mandelbrot_-1.94270349_0.00000000_0.00000234.png
mandelbrot_-1.94270349_0.00000000_0.00000234.png

start (imaginary): 0.7399999871850014
end: (imaginary): 0.5399999842047691
range: -0.20000000298023224
step: -2.0020020318341566E-4
start (real): -0.3500000014901161
end: (real): -0.14999999850988388
range: 0.20000000298023224
step: 2.0020020318341566E-4
maxIter 9961

mandelbrot_-0.25000000_0.63999999_0.20000000.png
mandelbrot_-0.25000000_0.63999999_0.20000000.png

start (imaginary): 0.6649999860674143
end: (imaginary): 0.6149999853223562
range: -0.05000000074505806
step: -5.0050050795853916E-5
start (real): -0.27500000037252903
end: (real): -0.22499999962747097
range: 0.05000000074505806
step: 5.0050050795853916E-5
maxIter 99627

mandelbrot_-0.25000000_0.63999999_0.05000000.png
mandelbrot_-0.25000000_0.63999999_0.05000000.png

References #

Wikipedia contributors. “Cumulative distribution function.” Accessed on: January 29, 2026. https://en.wikipedia.org/wiki/Cumulative_distribution_function .