Add the Julia set. The iteration is identical to Mandelbrot — the same formula, the same escape condition, the same loop structure. What changes is which value is fixed and which value is the pixel. That single swap produces an entirely different class of shapes for every choice of the fixed constant.
What a Julia set is
Gaston Julia[1] developed the theory in 1918, while recovering from wounds sustained in the First World War. He was in his mid-twenties. The paper — Mémoire sur l'itération des fonctions rationnelles — was 199 pages and won the Grand Prix des Sciences Mathématiques from the French Academy of Sciences. Pierre Fatou[2] independently reached the same conclusions. Neither man ever saw a computer rendering of his sets. Julia died in 1978.
The Julia iteration uses the same formula as Mandelbrot:
The difference is in the roles of and :
| Mandelbrot | the pixel being tested | zero |
| Julia | a fixed constant (the mouse click) | the pixel being tested |
Every complex number defines a different Julia set. The set for that is the boundary between pixels whose orbits stay bounded and pixels whose orbits escape — the same question as Mandelbrot, but asked with a fixed map and a variable starting point.
Connected vs disconnected. If is inside the Mandelbrot set, the Julia set for that is connected — one continuous shape. If is outside the Mandelbrot set, the Julia set is a disconnected cloud of points known as Fatou dust. The Mandelbrot set is the index of all connected Julia sets: a point is in the Mandelbrot set precisely when the Julia set for that is connected.
julia.h
#ifndef JULIA_H
#define JULIA_H
int julia(double z_re, double z_im,
double c_re, double c_im,
int max_iter, double *mag_out);
#endifNo SDL2. The function separates its two complex-number inputs into two
pairs: z_re/z_im is the starting point (the pixel), and
c_re/c_im is the fixed constant (the mouse click). Both are complex
numbers but they play different roles in the iteration. mag_out works
identically to mandelbrot: pass a double * to receive the final
squared magnitude for smooth colouring, or NULL if only the iteration
count is needed.
julia.c
#include "julia.h"
int julia(double z_re, double z_im,
double c_re, double c_im,
int max_iter, double *mag_out)
{
double re;
double im;
double new_re;
double sq;
int i;
re = z_re; /* z starts at the pixel — not at zero as in Mandelbrot */
im = z_im;
i = 0;
while (i < max_iter) {
sq = re * re + im * im;
if (sq > 4.0) { /* |z|² > 4: point has escaped */
if (mag_out)
*mag_out = sq; /* squared magnitude for smooth colouring */
return (i);
}
new_re = re * re - im * im + c_re; /* real part of z² + c */
im = 2.0 * re * im + c_im; /* imaginary part — uses old re */
re = new_re;
i++;
}
if (mag_out)
*mag_out = re * re + im * im;
return (max_iter); /* never escaped: point is in the Julia set */
}Parameters. z_re and z_im are the real and imaginary components
of the starting point — in Julia mode, the pixel coordinate being
tested. c_re and c_im are the fixed constant, supplied from
s->julia_re and s->julia_im on the state struct. Both pairs are
passed as separate double values rather than a struct to keep the
interface consistent with mandelbrot.
starts at the pixel. The two lines re = z_re; im = z_im; are
the entire structural difference from mandelbrot. In mandelbrot,
is always initialised to zero and comes from the pixel. Here, is
initialised to the pixel and is the fixed constant. The squaring
loop, the escape check, the mutation order, and the return value are
identical.
Why this swap changes everything. In the Mandelbrot iteration, every pixel starts from the same zero — the only variable is which is being tested. The escape time of the origin under the map defines the Mandelbrot set. In the Julia iteration, is fixed and every pixel starts at its own location in the complex plane. The question becomes: starting from this specific point, does the orbit under the fixed map stay bounded? Pixels near a stable orbit do not escape and form the filled set; pixels near an unstable orbit escape quickly. The shape of the boundary between them is different for every choice of .
The mutation order. new_re saves the correct new real part before
im is overwritten — the same invariant as in mandelbrot. Both
update lines use the values from the current iteration, not the next.
Wire it into render_frame
double mag; was already added to render_frame's variable
declarations on the Colour page. Also update mandelbrot.h to add
double *mag_out to the burning_ship declaration — the
implementation arrives on the next page, but the dispatch needs it now:
int burning_ship(double c_re, double c_im, int max_iter,
double *mag_out);Then replace the single mandelbrot call inside the pixel loop with
the full fractal dispatch:
#include "julia.h"
/* inside render_frame, replacing the single mandelbrot call: */
if (s->fractal == 0)
i = mandelbrot(re, im, MAX_ITER, &mag); /* c = pixel, z₀ = 0 */
else if (s->fractal == 1)
i = julia(re, im, s->julia_re, s->julia_im, /* z₀ = pixel, c = click */
MAX_ITER, &mag);
else
i = burning_ship(re, im, MAX_ITER, &mag);
if (s->colour_scheme == 0)
colour = colour_linear(i, MAX_ITER);
else
colour = colour_smooth(i, MAX_ITER, mag);
s->pixels[y * WIDTH + x] = colour;The dispatch. s->fractal is an integer mode flag: 0 for
Mandelbrot, 1 for Julia, 2 for Burning Ship. Only the first two
arguments to julia differ from mandelbrot — the pixel coordinates
re and im become the starting point , and s->julia_re/
s->julia_im supply the fixed . Every other argument is identical
across all three functions. The colour dispatch below it does not change
at all — colour_linear and colour_smooth see only the iteration
count and the final magnitude, which all three functions produce in the
same format.
The mouse handler. s->julia_re and s->julia_im are set by the
left-click handler in main.c, established on the
event loop
page:
if (ev.type == SDL_MOUSEBUTTONDOWN
&& ev.button.button == SDL_BUTTON_LEFT
&& s.fractal != 1) {
s.julia_re = pixel_to_re(ev.button.x, &s.view);
s.julia_im = pixel_to_im(ev.button.y, &s.view);
s.fractal = 1;
}A left click in Mandelbrot or Burning Ship mode converts the mouse
position to complex coordinates using the current viewport and stores
them on the state struct, then switches the mode to Julia. The
condition s.fractal != 1 prevents a click in Julia mode from
overwriting the constant that is already rendering.
Run it
make re && ./infiniteThe program starts in Mandelbrot mode. Click anywhere on the set. The program switches to Julia mode and renders the Julia set for the clicked point.
Click inside the main cardioid: the Julia set for those values is large and connected — often a deformed disc or ring with spiral arms. The iteration for that is stable; most of the plane stays bounded.
Click in one of the small bulbs attached to the cardioid: the Julia set changes to a disconnected ring of blobs or a cauliflower-like cluster. Each bulb parameterises a Julia set with a different periodic orbit length.
Click on the boundary — the thin filaments along the spike at
(−2.0, 0.0): the Julia set fractures into Fatou dust. The iteration
for that is unstable and almost no starting point stays bounded.
Press M to return to Mandelbrot mode. Click a different point. Press
J to see its Julia set without moving the view. Press C to toggle
smooth colouring — in smooth mode, zoom into the Julia set boundary and
the banding disappears.
The relationship
The Mandelbrot set answers the question: for which values of is the iteration stable? A point is in the Mandelbrot set precisely when the Julia set for that is connected. Clicking a point in the Mandelbrot set is selecting a value and asking: what does stability look like for this map?
Clicking outside the Mandelbrot set selects a where the iteration is unstable — and the Julia set for that is Fatou dust, a fractal cloud with no interior. The boundary of the Mandelbrot set is exactly where connected Julia sets become disconnected ones.
This is not a coincidence or a visual trick. It is the mathematical content of the Mandelbrot set. The set is a catalogue of Julia sets, organised by their connectivity.