Zalgorithm

Creating Mandelbrot fractals with Processing

The Mandelbrot set is a two-dimensional set that is defined in the complex plane as the complex numbers cc for which the function fc(z)=z2+cf_c(z) = z^2 + c does not diverge to infinity when iterated starting at z=0z = 01

I understand that to mean:

The formula that’s used for determining if a complex number is in the Mandelbrot set

The function is simpler than its description. Testing it in the Python REPL (IPython):

start with z=0z = 0:

In [9]: z = complex(0, 0)

In [10]: z
Out[10]: 0j

start with some value for cc (this would be a point from the complex plane in a real implementation):

In [11]: c = complex(2, 3)

In [12]: c
Out[12]: (2+3j)

update the value of zz using the current values of zz and cc:

In [13]: z = z * z + c

In [14]: z
Out[14]: (2+3j)

zz now equals the initial value of cc: znew=00+c=c=2+3jz_{\text{new}} = 0 * 0 + c = c = 2+3j

determine if cc (the initial value from the complex plane) is indicating it will diverge to infinity:

The test is to check if the magnitude (or absolute value) of cc exceeds some threshold. My (tentative) understanding is that if the magnitude of cc is greater than 22, it will eventually (possibly quickly (?)) approach infinity. (I’ll look into this more later.)

In [15]: abs(z)
Out[15]: 3.605551275463989

Since 3.6>23.6 > 2, cc (2+3j) is not in the Mandelbrot set.

I’ll find a value of cc that doesn’t exceed a magnitude of 2 on the first iteration:

In [20]: c = complex(1.3, 0.7)

In [21]: z = complex(0, 0)

In [22]: z = z * z + c

In [23]: abs(z)
Out[23]: 1.47648230602334

Since zz when starting with c=1.3+0.7jc = 1.3+0.7j doesn’t exceed 2 after the first iteration, it’s possible it’s in the Mandelbrot set. To find out, call the function again:

In [24]: c
Out[24]: (1.3+0.7j)  # the initial value of c (it doesn't get updated)

In [25]: z
Out[25]: (1.3+0.7j)  # the value of z that was set in the first iteration

In [26]: z = z * z + c  # update z

In [27]: abs(z)
Out[27]: 3.5497042130295866  # the magnitude of z exceeds 2.0

So 1.3+0.7j1.3+0.7j is also not in the Mandelbrot set.

c=0+0jc = 0+0j (the real/imaginary intersection of the complex plane) is guaranteed to be in the Mandelbrot set. Zero times zero plus zero will always equal zero:

In [28]: z = complex(0, 0)

In [29]: c = complex(0, 0)

In [30]: z = z * z + c

In [31]: abs(z)
Out[31]: 0.0

Testing some more interesting complex numbers

c=0.5+0.5jc = -0.5+0.5j is a good case. The (Python) code below assigns the starting value of 0+0j0+0j to zz as usual, and assigns 0.5+0.5-0.5+0.5 to cc. It then tries running the z=zz+cz = z*z + c function 50 times, to see if the absolute value of zz exceeds 2.02.0:

In [42]: z = complex(0, 0)

In [43]: c = complex(-0.5, 0.5)

In [44]: for i in range(50):
    ...:     z = z*z+c
    ...:     print("iteration:", i, "z:", z)
    ...:     if abs(z) > 2.0:
    ...:         print(c, "is not in the Mandelbrot set")
    ...:         break
    ...:
iteration: 0 z: (-0.5+0.5j)
iteration: 1 z: (-0.5+0j)
iteration: 2 z: (-0.25+0.5j)
iteration: 3 z: (-0.6875+0.25j)
# ...
iteration: 45 z: (-0.5500488745365836+0.23115432457577173j)
iteration: 46 z: (-0.250878557391119+0.24570764784566523j)
iteration: 47 z: (-0.49743219765120045+0.3767144395370289j)
iteration: 48 z: (-0.3944749776955948+0.12522021690831092j)
iteration: 49 z: (-0.3600695946946244+0.401207515456113j)

Based on a test of 50 iterations, it can tentatively (in a real sense this time) be said that 0.5+0.5j-0.5+0.5j is in the Mandelbrot set. The values of zz on each iteration are bouncing around the origin 0+0j0+0j. The values are showing bounded chaotic behavior. Note that the magnitude of a complex number is the distance of the number from the origin of the complex plane. In Python I’m calculating it with abs(z). When a complex number is given as the argument to the Python abs function, the magnitude of the number is returned:

iteration: 49 z: (-0.3600695946946244+0.401207515456113j)

In [52]: abs(z)
Out[52]: 0.5390895876215921

The magnitude is essentially the hypotenuse of the number on the complex plane:

iteration: 49 z: (-0.3600695946946244+0.401207515456113j)

In [52]: abs(z)
Out[52]: 0.5390895876215921

In [53]: z.imag
Out[53]: 0.401207515456113

In [54]: z.real
Out[54]: -0.3600695946946244

In [55]: np.sqrt(z.real*z.real + z.imag*z.imag)  # the square root of a^2 + b^2 = c^2
Out[55]: np.float64(0.5390895876215921)

Returning the magnitudes from the Python script

Here’s a similar script that returns the current magnitude of zz, starting from c=0.12+0.75jc = -0.12+0.75j:

In [78]: z = complex(0, 0)

In [79]: c = complex(-0.12, 0.75)

In [80]: for i in range(50):
    ...:     z = z * z + c
    ...:     print(f"iteration: {i}, abs(z): {abs(z)}")
    ...:     if abs(z) > 2.0:
    ...:         print(f"iteration: {i}; {c} is not in the Mandelbrot set")
    ...:         break
    ...:
    ...:
iteration: 0, abs(z): 0.7595393340703298
iteration: 1, abs(z): 0.8782127361863982
iteration: 2, abs(z): 0.011724955561199504
iteration: 3, abs(z): 0.7595269050363009
iteration: 4, abs(z): 0.8780252871118147
iteration: 5, abs(z): 0.011391498992753827
iteration: 6, abs(z): 0.7595241904450459
# ...
iteration: 44, abs(z): 0.011405972274973704
iteration: 45, abs(z): 0.7595245098608843
iteration: 46, abs(z): 0.8780333368437104
iteration: 47, abs(z): 0.011405972274973704
iteration: 48, abs(z): 0.7595245098608843
iteration: 49, abs(z): 0.8780333368437104

Based on 50 iterations, 0.12+0.75j-0.12+0.75j is in the Mandelbrot set.

A complex number that’s not in the Mandelbrot set

c=0.8+0.2jc = -0.8+0.2j escapes the bounds of 2.02.0 after 15 iterations:

In [81]: z = complex(0, 0)

In [82]: c = complex(-0.8, 0.2)

In [83]: for i in range(50):
    ...:     z = z * z + c
    ...:     print(f"iteration: {i}, abs(z): {abs(z)}")
    ...:     if abs(z) > 2.0:
    ...:         print(f"iteration: {i}; {c} is not in the Mandelbrot set")
    ...:         break
    ...:
iteration: 0, abs(z): 0.8246211251235321
iteration: 1, abs(z): 0.233238075793812
iteration: 2, abs(z): 0.8131416604749754
iteration: 3, abs(z): 0.32005852224930614
iteration: 4, abs(z): 0.820739300530877
iteration: 5, abs(z): 0.3944899747490221
iteration: 6, abs(z): 0.8499974263391781
iteration: 7, abs(z): 0.47633146090875766
iteration: 8, abs(z): 0.9179848147566967
iteration: 9, abs(z): 0.588735685816267
iteration: 10, abs(z): 1.0730524976637672
iteration: 11, abs(z): 0.793560584649992
iteration: 12, abs(z): 1.4456959535695484
iteration: 13, abs(z): 1.386852094167046
iteration: 14, abs(z): 2.1324535049646074
iteration: 14; (-0.8+0.2j) is not in the Mandelbrot set

How is the Mandelbrot set used to generate fractal images?

Instead of sampling numbers randomly as I’ve been doing, the numbers used to generate Mandelbrot fractal images are taken from the complex plane, close to the plane’s origin (0+0j0+0j).

The complex plane in the image below covers the range ±15\pm 15 on both its real and imaginary axis. The Mandelbrot set exists in the range 2.5,1-2.5, 1 on the real axis, and somewhere in the range ±1.75\pm 1.75 on the imaginary axis (to be confirmed later).

Complex plane
Complex plane

Mandelbrot fractals are generated by populating a plane with complex numbers in the appropriate range and then keeping track of the number of iterations it takes each number on the plane to show that it’s going to diverge to infinity. In practice this seems to mean, the number of iterations it takes for the magnitude of zz to exceed the threshold of 22.

The iteration counts are recorded in a 2D object (for example an array or a Python list). Numbers (points on the plane) that don’t meet the threshold of 2 within some number of iterations (for example, 50 iterations) are assigned a value of 0 in the iteration counts array.

Iteration counts are then arbitrarily associated with colors. For example, 0 (numbers in the Mandelbrot set) are commonly set to black. 5 iterations could be set to red, 10 iterations set to green… In pratice the color assignment is probably done programatically.

The iterations array is then mapped to pixels on the screen. For example iterations[0][0] will be the top right corner of the screen. If iterations[0][0] = 15, and 15 iterations is associated with the color red, the pixel at [0][0] will be colored red.

It’s easier to demonstrate this than explain it.

A basic Processing implementation

The logic for generating the Mandelbrot set is taken from from the (Matplotlib) “Code #3” example at https://www.geeksforgeeks.org/python/mandelbrot-fractal-set-visualization-in-python/ .

centeredLinspace makes it easier to zoom in on interesting areas of the fractal:

float[] centeredLinspace(float center, float range, int num) {
  float[] result = new float[num];
  float halfRange = range * 0.5;
  float start = center - halfRange;
  float end = center + halfRange;
  float step = (end - start) / (num - 1);
  for (int i = 0; i < num; i++) {
    result[i] = start + i * step;
  }
  return result;
}

The Complex class has methods for add, mult, magnitude.

class Complex {
  float re, im;

  Complex(float re, float 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
      );
  }

  float magnitude() {
    return sqrt(re*re + im*im);
  }
}

Most of the work (computation) happens in the setup function:

void setup() {
  size(1000, 1000);
  colorMode(HSB, 360, 100, 100);
  // fillArray calculates whether or not numbers belong to the set.
  // It returns an array of iteration counts.
  iterations = fillArray(realComponents, imaginaryComponents);
}

mandelbrot.pde (WIP):

int rows = 1000;
int cols = 1000;
int maxIters = 200; // adjust, especially for zoomed in areas
int maxIterColorCutoff = 55;

int[][] iterations = new int[cols][rows];
float[] realComponents = centeredLinspace(-0.75, 3.5, cols);
float[] imaginaryComponents = centeredLinspace(0, 3.5, rows);

void setup() {
  size(1000, 1000);
  colorMode(HSB, 360, 100, 100);
  iterations = fillArray(realComponents, imaginaryComponents);
}

void draw() {
  for (int i = 0; i < iterations[0].length; i++) {
    for (int j = 0; j < iterations[1].length; j++) {
      if (iterations[i][j] == 0) {
        stroke(0, 0, 0); // in the Mandelbrot set
      } else if (iterations[i][j] > maxIterColorCutoff) {  // probably on a boundary; adjust this value
        stroke(360, 89, 29);
      } else { // not in set and (probably) not on the boundary
        float hue = map(iterations[i][j], 1, maxIterColorCutoff, 167, 340);
        stroke(hue, 57, 79);
      }
      point(i, j);
    }
  }
  save("mandelbrot_full.png");
  noLoop();
}

int[][] fillArray(float[] xVals, float[] yVals) {
  int[][] result = new int[rows][cols];

  for (int i = 0; i < xVals.length; i++) {
    for (int j = 0; j < yVals.length; j++) {
      Complex c = new Complex(xVals[i], yVals[j]);
      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;
}

float[] linspace(float start, float end, int num) {
  float[] result = new float[num];
  if (num == 1) {
    result[0] = start;
    return result;
  }
  float step = (end - start) / (num - 1);
  for (int i = 0; i < num; i++) {
    result[i] = start + i * step;
  }
  return result;
}

float findCenter(float start, float end) {
  float dif = end - start;
  float halfDif = dif/2;
  return start + halfDif;
}

float[] centeredLinspace(float center, float range, int num) {
  float[] result = new float[num];
  float halfRange = range * 0.5;
  float start = center - halfRange;
  float end = center + halfRange;
  float step = (end - start) / (num - 1);
  for (int i = 0; i < num; i++) {
    result[i] = start + i * step;
  }
  return result;
}

class Complex {
  float re, im;

  Complex(float re, float 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
      );
  }

  float magnitude() {
    return sqrt(re*re + im*im);
  }
}

Images

The full Mandelbrot set (real components in the range -2.5, 1, imaginary components in the range
-1.75, 1.75)
The full Mandelbrot set (real components in the range -2.5, 1, imaginary components in the range -1.75, 1.75)

End of set (centered around Real ~-1.75)
End of set (centered around Real ~-1.75)

The image below was created with:

float[] realComponents = centeredLinspace(-1.77, 0.1, cols);
float[] imaginaryComponents = centeredLinspace(0, 0.1, rows);

End of set zoomed in
End of set zoomed in

The image below was created with:

int rows = 2000;
int cols = 2000;
int maxIters = 3000; // adjust, especially for zoomed in areas
int maxIterColorCutoff = 755;
float pxScale = 0.5;  // allows for having 2x rows and columns than pixels, defaults to 1

int[][] iterations = new int[cols][rows];
float[] realComponents = centeredLinspace(-0.755, 0.025, cols);
float[] imaginaryComponents = centeredLinspace(0.09, 0.025, rows);

Mandelbrot Seahorse Valley
Mandelbrot Seahorse Valley

Zooming in on a seahorse with:

int rows = 2000;
int cols = 2000;
int maxIters = 3500; // adjust, especially for zoomed in areas
int maxIterColorCutoff = 700;
float pxScale = 0.5;  // allows for having 2x rows and columns than pixels, defaults to 1

int[][] iterations = new int[cols][rows];
float[] realComponents = centeredLinspace(-0.748, 0.01, cols);
float[] imaginaryComponents = centeredLinspace(0.09, 0.01, rows);

Seahorse
Seahorse

More zoom:

int rows = 2000;
int cols = 2000;
int maxIters = 3500; // adjust, especially for zoomed in areas
int maxIterColorCutoff = 700;
float pxScale = 0.5;  // allows for having 2x rows and columns than pixels, defaults to 1

int[][] iterations = new int[cols][rows];
float[] realComponents = centeredLinspace(-0.748, 0.005, cols);
float[] imaginaryComponents = centeredLinspace(0.08575, 0.005, rows);

Mandelbrot seahorse more zoom
Mandelbrot seahorse more zoom

This needs to stop:

int rows = 2000;
int cols = 2000;
int maxIters = 4500; // adjust, especially for zoomed in areas
int maxIterColorCutoff = 1300;
float pxScale = 0.5;  // allows for having 2x rows and columns than pixels, defaults to 1

int[][] iterations = new int[cols][rows];
float[] realComponents = centeredLinspace(-0.748, 0.000051, cols);
float[] imaginaryComponents = centeredLinspace(0.08599, 0.000051, rows);

Mandelbrot seahorse, ultra-zoomed
Mandelbrot seahorse, ultra-zoomed

Finally:

int rows = 4000;
int cols = 4000;
int maxIters = 8000; // adjust, especially for zoomed in areas
int maxIterColorCutoff = 7000;
float pxScale = 0.25;  // allows for having 2x rows and columns than pixels, defaults to 1

int[][] iterations = new int[cols][rows];
float[] realComponents = centeredLinspace(-0.7474475, 0.000025, cols);
float[] imaginaryComponents = centeredLinspace(0.085984, 0.000025, rows);

Mandelbrot seahorse valley, real-axis span: 0.000025
Mandelbrot seahorse valley, real-axis span: 0.000025

References

Geeks For Geeks. “Mandelbrot Fractal Set visualization in Python.” Last Updated: September 4, 2023. https://www.geeksforgeeks.org/python/mandelbrot-fractal-set-visualization-in-python/ .

Wikipedia contributors. “Mandelbrot set.” Accessed on: January 21, 2026. https://en.wikipedia.org/wiki/Mandelbrot_set .


  1. Wikipedia contributors, “Mandelbrot set,” Accessed on: January 21, 2026, https://en.wikipedia.org/wiki/Mandelbrot_set↩︎