Iterate z² + c from every pixel, and colour by how long it takes to run away.
One line of arithmetic, per pixel
var tmp = zr * zr - zi * zi + Cr;
zi = 2.0 * zr * zi + Ci;
zr = tmp;
That is complex multiplication written out: z ← z² + c. Every pixel of the image
starts z at its own position in the complex plane, holds c fixed, and iterates
until either |z| > 2 or the budget runs out.
The Julia set for a given c is the set of starting points whose orbit never escapes.
Everything else is coloured by how long it took.
Three iterations. Points need |z| > 2 to be declared escaped, and three squarings is
not long enough for most of the frame — so most of it is still black.
Julia versus Mandelbrot
The two sets come from the same iteration and differ only in what varies:
| z starts at | c is |
|---|
| Julia | the pixel | fixed for the image |
| Mandelbrot | zero | the pixel |
The Mandelbrot set is the map of which c values give a connected Julia set. So
every point of the Mandelbrot set names a whole Julia image, and the six renderings
here are six points chosen from it:
| Rendering | c | Budget | Character |
|---|
| Spiral | −0.7269 + 0.1889i | 300 | tight double spiral |
| Lightning | 0 + 1.0i | 300 | branching, connected |
| Dendrite | 0.285 + 0.01i | 300 | tree-like, barely connected |
| Frost | −0.74543 + 0.11301i | 400 | near the boundary; extremely filamentary |
| Galaxy | −0.8 + 0.156i | 300 | spiral arms |
| Rabbit | −0.1226 + 0.7449i | 300 | three distinct lobes, connected |
Frost’s c is close to the Mandelbrot boundary, which is why it needs a budget of 400
where the others use 300 — points near the boundary take much longer to decide.
Eight iterations. The outer bands have separated, and each band is exactly the set of
points that escaped on the same step.
The budget is not the set
Twenty-five iterations. The black region is shrinking, and it will keep shrinking with
every increase in budget — because “black” here means has not escaped yet, which is
not the same as will never escape.
The true Julia set has no area at all for most values of c; it is a curve, or a dust.
What the picture shows is a thickened approximation whose thickness is set entirely by
the budget, and the progression above is that thickness coming down.
Eighty. Filaments resolve that were indistinguishable from solid black two stages ago.
Nothing was added: those points were always escaping, just slowly.
The full budget.
Smooth colouring
Escape time is an integer, and colouring by it directly produces visible steps. The
code removes them:
var smooth = iter + 1.0 - Math.Log(Math.Log(Math.Sqrt(mag2))) / Math.Log(2.0);
The correction uses how far past the escape radius the orbit landed. Because z is
being squared each step, the magnitude grows doubly exponentially, and the double
logarithm recovers the fractional part of the escape time from the overshoot. The
result is a continuous function, and the bands become a gradient.
The colour then cycles the palette PaletteCycles times across the normalised escape
time, so the gradient repeats rather than fading out — which is what makes the outer
region read as contours rather than as a wash.
Determinism
Nothing is seeded and nothing is random. Each pixel is an independent fixed-point
computation in double, written directly into the bitmap in scanline order.