MAKE THE MATHEMATICS YOURSELF

Mandelbrot: the parameter universe in C

A complete program. Standard C, a compiler, and the rule behind the shape.

Output generated by this C program: Mandelbrot: the parameter universe
Generated by the C program below Download image ↗
FROM SOURCE TO SHAPE

Run it in three steps.

  1. Save the source.

    Download mandelbrot.c into a folder on your computer.

  2. Compile it.

    In that folder, run this with GCC or Clang:

    cc -std=c11 -O2 mandelbrot.c -lm -o mandelbrot
  3. Make the image.
    ./mandelbrot

    The program writes mandelbrot.ppm, a portable image file. Open it with a PPM-capable image viewer or convert it to PNG.

On Windows with GCC, name the executable mandelbrot.exe and run it from the same folder.

THE RULE IN THE PROGRAM

How the picture is built

Every pixel selects a different quadratic rule and follows its critical starting point, zero. The central cardioid contains rules with an attracting fixed point; neighboring bulbs support longer attracting cycles. Their boundaries gather parameters where behavior changes, creating the intricate outline. Squaring doubles angles and changes magnitudes; adding c shifts the result before the next iteration. Outside colors measure escape speed; dark pixels that have not escaped are only a finite approximation to the set.

Make it your own

Pass width, height, and an iteration limit when running the program.

./mandelbrot 1600 1000 300 large.ppm

This is the parameter plane, not a picture of one orbit. Failure to escape within the cap does not prove membership. Analytic tests can certify the interiors of the main cardioid and period-two bulb; the remaining dark pixels are unresolved by this finite computation. Each run produces one image; use Graphic mode for the interactive animation.

The complete source

mandelbrot.c · 102 lines · no graphics libraries
/* Arithmos: draw the Mandelbrot set using only standard C and libm.
 *
 * Compile: cc -std=c11 -O2 mandelbrot.c -lm -o mandelbrot
 * Run:     ./mandelbrot
 * Options: ./mandelbrot WIDTH HEIGHT ITERATIONS OUTPUT.ppm
 * Example: ./mandelbrot 1600 1000 300 mandelbrot.ppm
 *
 * The output is a binary PPM (P6) image. It needs no graphics library.
 * Dark pixels have not escaped within the iteration limit; that alone
 * does not prove they belong to the Mandelbrot set.
 */
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* Read a positive integer, rejecting malformed or excessive values. */
static int read_int(const char *text, int minimum, int maximum) {
    char *end;
    errno = 0;
    long value = strtol(text, &end, 10);
    if (errno || end == text || *end || value < minimum || value > maximum) {
        fprintf(stderr, "Expected an integer from %d to %d: %s\n",
                minimum, maximum, text);
        exit(EXIT_FAILURE);
    }
    return (int)value;
}

/* Interpolate through the website's mint, blue, rose and gold palette. */
static void gradient(double smooth, unsigned char rgb[3]) {
    const double stops[4][3] = {
        {91, 227, 201}, {128, 146, 240},
        {218, 138, 220}, {244, 200, 127}
    };
    double t = fmod(log1p(fmax(0.0, smooth)) * 0.35, 1.0) * 3.0;
    int band = (int)t;
    double blend = t - band;
    double brightness = fmin(1.0, 0.11 + smooth / 12.0);
    for (int channel = 0; channel < 3; ++channel) {
        double color = stops[band][channel] * (1.0 - blend)
                     + stops[band + 1][channel] * blend;
        rgb[channel] = (unsigned char)lround(color * brightness);
    }
}

int main(int argc, char **argv) {
    if (argc != 1 && argc != 5) {
        fprintf(stderr, "Usage: %s [WIDTH HEIGHT ITERATIONS OUTPUT.ppm]\n", argv[0]);
        return EXIT_FAILURE;
    }
    int width = argc == 5 ? read_int(argv[1], 2, 4096) : 1200;
    int height = argc == 5 ? read_int(argv[2], 2, 4096) : 800;
    int limit = argc == 5 ? read_int(argv[3], 1, 2000) : 250;
    const char *filename = argc == 5 ? argv[4] : "mandelbrot.ppm";
    FILE *image = fopen(filename, "wb");
    if (!image) { perror(filename); return EXIT_FAILURE; }
    if (fprintf(image, "P6\n%d %d\n255\n", width, height) < 0) {
        fclose(image); return EXIT_FAILURE;
    }

    /* Choose a view of the complex plane; keep both axes at one scale. */
    const double center_real = -0.7, center_imag = 0.0;
    double span_x = fmax(3.2, 2.4 * width / height);
    double span_y = span_x * height / width;

    for (int py = 0; py < height; ++py) {
        for (int px = 0; px < width; ++px) {
            /* Each pixel chooses c = cr + ci*i. */
            double cr = center_real + ((px + 0.5) / width - 0.5) * span_x;
            double ci = center_imag + (0.5 - (py + 0.5) / height) * span_y;
            double zr = 0.0, zi = 0.0; /* Begin with z = 0. */
            int step = 0;

            /* z <- z*z + c, written in real and imaginary parts.
             * (a + bi)^2 = (a*a - b*b) + (2*a*b)i
             * Preserve the old real part until both updates are ready.
             */
            while (step < limit && zr * zr + zi * zi <= 4.0) {
                double next_real = zr * zr - zi * zi + cr;
                zi = 2.0 * zr * zi + ci;
                zr = next_real;
                ++step;
            }

            unsigned char rgb[3] = {9, 16, 27};
            if (zr * zr + zi * zi > 4.0) {
                /* Smooth the escape count to avoid hard color bands. */
                double magnitude = sqrt(zr * zr + zi * zi);
                double smooth = fmax(0.0, step + 1.0 - log2(log2(magnitude)));
                gradient(smooth, rgb);
            }
            if (fwrite(rgb, 1, 3, image) != 3) {
                perror("Writing image"); fclose(image); return EXIT_FAILURE;
            }
        }
    }
    if (fclose(image) != 0) { perror("Closing image"); return EXIT_FAILURE; }
    printf("Wrote %s (%d x %d, %d iterations)\n", filename, width, height, limit);
    return EXIT_SUCCESS;
}
All 40 explorations, ready to compile.Download all C examples ↓