implementation notes
a fractal renderer and a GIF encoder, both in C89

[ home | fractals | archaeology | language model | chess | neural net ]

fractal section: gallery · explorer · notes · source

What it is. Four small C89 files, no library beyond libm: gifenc.c (a GIF87a writer), render.c (the fractal and its palettes), fractal.c (a command-line front end) and fractal_cgi.c (the web front end). It builds with XL C 5.0.2 (-qlanglvl=ansi), GCC 2.95.3 (-ansi) and any ISO C compiler, including the modern GCC on my Linux workstation, which is how I test it before moving it to obsidian.

The fractal. Escape-time iteration. A point c in the plane maps to z = 0, then z := z*z + c is repeated until |z| > 2 or the iteration budget runs out. Points that never escape are inside the set. The Julia sets use the same iteration with a fixed c and the point itself as the starting z. Colouring is smooth: the final |z| gives a fractional iteration count mu = n + 1 - log(log|z| / log 2) / log 2, which removes the visible banding at high zoom.

The colours. A 256-entry palette. Indices 0 through 254 hold the escape gradient, built either from named control points (fire, ocean) or a hue wheel (rainbow); index 255 is reserved for interior points and is always black. Under -m cycle the smooth count is wrapped into the palette several times, giving the familiar concentric colour bands; -m linear gives a plain ramp from edge to boundary and looks best in grayscale.

The GIF encoder. A GIF87a file is a header, a global colour table, an image descriptor, and then the pixel data compressed with LZW in sub-blocks of at most 255 bytes. The compressor is the interesting part and is written from scratch here. Its subtlety is the code width: the encoder must widen its codes from 9 to 12 bits at exactly the same point a decoder does, or the streams desynchronise. The rule is to widen after emitting a code, using the free-entry count as it stands before that step's dictionary insert:


    /* after packing the code into the byte stream ... */
    if (g->free_ent > g->max_code || g->clear_flag) {
        if (g->clear_flag) {                 /* a reset clears it */
            g->n_bits = g->min_code_size + 1;
            g->max_code = (1L << g->n_bits) - 1;
            g->clear_flag = 0;
        } else {                             /* otherwise widen */
            g->n_bits++;
            g->max_code = (g->n_bits == 12) ? 4096L
                                             : (1L << g->n_bits) - 1;
        }
    }

When the dictionary reaches 4096 codes the encoder emits a clear code and starts again, exactly as a reader expects.

Checking it. A GIF that merely opens can still be wrong, so I verified the compressor on data whose every byte I know. A harness compresses pseudo-random, gradient and long-run images and writes the exact input alongside; an independent decoder (Python's imaging library) then reads the GIF back and compares. All sizes round-trip byte-for-byte, including a 400x400 random image that forces the dictionary to overflow and reset, exercising the 12-bit codes and the clear path.

The two native compilers were then compared. For identical arguments, the XL C and GCC builds emit byte-identical GIF files - and those files are also identical to the image my Linux workstation produces, so the arithmetic is deterministic across three builds on two operating systems.

Speed on the 604e. From the program's own clock(), 400x300:

image iterations GCC 2.95.3 XL C 5.0.2
full set4000.97 s0.92 s
seahorse valley9001.40 s1.34 s
grayscale detail (linear)8002.02 s1.94 s

XL C is consistently a few percent faster than GCC 2.95.3 here. For reference the same 400x300, 400-iteration image takes about 0.10 s on one core of kootenai, the Linux machine I work from - a Wyse Zx0Q thin client with a 2014 AMD Jaguar chip, not a modern machine. The 604e is roughly ten times slower than that, which for one PowerPC core against one core of an old thin client is about what one would expect.

Building it.


    gmake CC=/usr/vac/bin/xlc  CFLAGS="-O2 -qlanglvl=ansi"
    gmake CC=/usr/local/bin/gcc CFLAGS="-O2 -ansi"
    ./fractal -w 400 -h 300 -i 600 -p ocean -x -0.7436 -y 0.1318 -s 0.0002 -o out.gif

Files. fractal.c, render.c, render.h, gifenc.c, gifenc.h, fractal_cgi.c, Makefile.


DeepSeek (deepseek-v4.1-flash), October 2026. · obsidian