A real-time infinite zoom into the Mandelbrot set, rendered via SDL2 in a window with true 24-bit color.
The program smoothly zooms toward a self-similar Misiurewicz point in seahorse valley until the limits of double-precision arithmetic are reached — then resets and zooms again. Colors flow continuously via a cosine-based RGB palette and a smooth iteration count.
This project focuses on learning:
- complex iteration / dynamical systems
- the Mandelbrot escape criterion
- smooth iteration count
- color palettes from cosine waves
- dynamic precision / iteration budgeting
- SDL2 pixel-buffer rendering
- real-time graphics in C
- Real-time zoom into a self-similar region of the Mandelbrot set
- Smooth iteration coloring (no banding) via continuous escape count
- Cosine RGB palette with a slowly-drifting color cycle
- Iteration count scales automatically with zoom depth
- True 24-bit color pixel rendering via SDL2 framebuffer
- 800 × 600 windowed output
- Keyboard quit handling (ESC / Q)
- 30 fps frame cap with adaptive timing
- Written entirely in C
For every pixel in the window, the program maps the pixel to a complex number c, then iterates z = z² + c starting from z = 0. If |z| ever exceeds 2, the point escapes the set; the number of iterations needed to escape (smoothed for a continuous result) becomes the pixel's color.
For every frame:
- Compute the current zoom scale (shrinks each frame)
- Map each pixel to a complex number around the zoom target
- Iterate
z = z² + cuntil escape or max iterations - Apply smooth iteration count for continuous coloring
- Look up color in a cosine palette
- Write the 32-bit RGBA pixel directly into the SDL surface buffer
- Push the surface to the window
When the zoom reaches the floating-point precision limit (~1e-13), it resets to the starting scale and begins again.
The Mandelbrot set is the set of all complex numbers c for which the iteration
z = 0
z = z² + c
z = z² + c
z = z² + c
...
stays bounded forever. Points outside the set escape to infinity at different speeds — that "speed of escape" gives the iconic colored bands.
For each pixel:
zr = 0; zi = 0;
while iter < max_iter:
zr2 = zr * zr
zi2 = zi * zi
if zr2 + zi2 > 256: break // escaped
zi = 2*zr*zi + imag(c)
zr = zr2 - zi2 + real(c)
iter++
A larger escape radius (256 instead of 4) improves smooth coloring quality.
Plain integer iteration counts produce banded colors. The standard fix is the smooth iteration count:
nu = iter + 1 - log(log(|z|)) / log(2)
This gives a continuous real number instead of an integer, so colors flow smoothly across the fractal without banding.
A simple, beautiful palette comes from three offset cosines:
r = 0.5 + 0.5 * cos(2π * (t + 0.00))
g = 0.5 + 0.5 * cos(2π * (t + 0.33))
b = 0.5 + 0.5 * cos(2π * (t + 0.66))
Each channel is a cosine wave at a different phase — the result cycles through every hue smoothly. Multiplying t by a small factor controls the density of the bands; adding a slowly-growing color_shift makes the palette drift over time.
Each frame, the scale shrinks by a constant factor:
scale *= 0.965;
Around the chosen zoom target (CX, CY), this pulls successive frames deeper into the set. The Misiurewicz point in seahorse valley is self-similar — the same spiral structures appear at every scale.
When scale falls below 1e-13 (the floor of double precision), it resets to 3.0 and the zoom loops.
Deeper zooms need more iterations to resolve detail. The iteration count scales with the log of the zoom level:
max_iter = MAX_ITER_BASE + (int)(-log(scale / 3.0) * 30);
At full zoom (scale = 3) we use ~200 iters; at deepest zoom we use ~800.
Instead of ANSI escape codes in a terminal, each frame renders directly into an 800 × 600 RGBA32 framebuffer exposed by SDL2:
pixels[py * pitch_px + px] = SDL_MapRGB(fmt, r, g, b);
This gives:
- True 24‑bit color — no quantization to a 256‑color palette
- Square pixels — no aspect-ratio fudge factor
- Native window — resizable, composited, vsync-friendly
- Event loop — keyboard quit, window manager close
SDL_UpdateWindowSurface flips the backbuffer to the display.
git clone <this-repo>
cd mandel
make
Or manually:
gcc mandel.c -o mandel -lm $(sdl2-config --cflags --libs)
Dependencies: SDL2, a C compiler (gcc/clang), and the math library.
Install SDL2 via Homebrew:
brew install sdl2
Or apt:
sudo apt install libsdl2-dev
./mandel
Controls:
- ESC or Q — quit
Edit the constants near the top of mandel.c:
WIDTH,HEIGHT— window resolutionCX,CY— zoom target; try some famous spots:- Seahorse Valley:
-0.745, 0.113 - Elephant Valley:
0.275, 0.000 - The Spire:
-0.7458, 0.10499 - Mini-Mandelbrot:
-1.7693831, 0.0042368
- Seahorse Valley:
- Zoom speed — change the
0.965multiplier - Color density — change the
0.025factor onnu
- Complex iteration
- Dynamical systems
- Escape-time fractals
- Smooth coloring
- Cosine color palettes
- Floating-point precision limits
- Dynamic algorithm parameters
- SDL2 surface / pixel-buffer graphics
- Real-time rendering loops
- Event-driven windowing
SDL2.h— window management and pixel-buffer renderingstdio.h—stderrdiagnosticsstdlib.h—EXIT_*macros,NULLmath.h—cosf,log,sqrt,floorf
Benoit Mandelbrot's discovery of the set named after him in 1980 was one of the first iconic visualizations of "computational mathematics" — beautiful, intricate structure emerging from a single line of recursion.
This program is a tiny tribute, fitting that infinite complexity into a window with a few hundred lines of C.