Every Future has a Founding

Palette morphing between waypoints

A zoom movie whose colouring is set per waypoint, with the palette and the cycle density interpolated across every camera move.

7 minutes read

1352 words

Contents6

Each waypoint of a movie carries its own palette and its own colour cycle density, and the renderer interpolates both across the move between them.

A zoom movie is a list of waypoints and the camera moves between them. Each waypoint carries the two settings that decide colour. One is the palette the frame is drawn in. The other is how many colour cycles the picture spans. The renderer interpolates both across every move, on the same eased parameter that drives the zoom. A frame at a stopped waypoint is drawn exactly as that waypoint defines it. Every frame between two waypoints is part of the way from one setting to the other.

Escape time and the palette coordinate

Escape time assigns each pixel the number of iterations its orbit takes to leave the escape radius. The definition and the iteration are given in the anatomy of the set . That count is an integer, so colouring from it directly produces visible bands at each integer step.

The smooth escape count removes the banding. Write BB for the escape radius, nn for the integer count at which zn|z_n| first exceeds it, and zn|z_n| for the magnitude reached at that step. The smooth count is

ν=nlog2 ⁣(lnznlnB)(1) \nu = n - \log_2\!\left(\frac{\ln |z_n|}{\ln B}\right) \tag{1}

and it varies continuously across the plane.1 A point that lands exactly on the escape radius gives lnzn/lnB=1\ln|z_n| / \ln B = 1 , so the logarithm is zero and ν=n\nu = n . A point that overshoots to zn=B2|z_n| = B^2 gives ν=n1\nu = n - 1 , which is correct because such a point had already escaped one iteration earlier.

This renderer uses B=256B = 256 . A large escape radius makes the correction term small. The fractional part of ν\nu then follows the magnitude reached, not the distance one iteration happened to carry the orbit past the radius.

A palette is a function from a coordinate tt in [0,1)[0, 1) to a colour, cyclic at its ends. The renderer maps ν\nu to tt with

t=frac ⁣(sν+φ)(2) t = \operatorname{frac}\!\left(s\sqrt{\nu} + \varphi\right) \tag{2}

where frac\operatorname{frac} is the fractional part, φ\varphi is a constant offset, and ss is the quantity this page calls the cycle density.

Cycle density

Equation (2) gives ss as the number of complete palette cycles per unit of ν\sqrt{\nu} . It is the colourScale field of a waypoint, and the setting the renderer’s toolbar labels Colour.

The square root in equation (2) is what makes ss a usable control. Escape counts near the boundary grow without bound, so tt would run through the palette faster and faster without it. Taking the square root slows that growth, and ss then sets the rate.

Raising ss puts more colour cycles across the same picture. Lowering it spreads one cycle over a wider range of escape counts. At a shallow view the choice is aesthetic. At depth the escape count between two adjacent pixels can differ by enough that a high ss sends them to unrelated parts of the palette.

The table below measures that. One waypoint at a view height of 21512^{-151} , 5,000,000 iterations, rendered at 640 by 360 in a single palette with only ss changed. The delta column is the mean absolute difference between horizontally adjacent pixels, taken over the largest of the three channels, on a scale of 0 to 255. The rough column is the share of adjacent pairs differing by more than 8.

Cycle density Mean adjacent delta Rough Distinct colours
0.10 6.57 22.7% 372
0.01 1.19 1.4% 355
0.002 0.44 0.4% 329
0.0005 0.22 0.2% 184

At s=0.10s = 0.10 more than a fifth of adjacent pixel pairs jump by more than 8 of 255. That reads as speckle. At s=0.01s = 0.01 the share is 1.4 percent, and 355 of the 372 distinct colours survive. Below s=0.002s = 0.002 the picture starts losing colours. At 0.00050.0005 the distinct-colour count has halved, because the image no longer spans enough of one cycle to show the palette.

The useful range therefore depends on depth, which is the reason ss belongs to the waypoint rather than to the film.

Palettes as tables

A palette is baked to a lookup table of 1024 entries in sRGB, and sampled between entries by linear interpolation. Three kinds of palette bake to the same table. One is defined by a cosine formula, one by a list of colour stops, and one is generated from a seed. The blend operates on the tables, so any two of them pair in any combination.

Enlarging the table does not reduce the speckle described above. Rebuilding with 8192 entries and re-rendering the same two views leaves no byte differing by more than 1. The mean adjacent delta is unchanged to two decimals. The table is already sampled between entries, so further entries interpolate a function that was smooth. The speckle comes from the escape count varying quickly between adjacent pixels, and ss is the control over it.

Interpolating between waypoints

One eased parameter uu in [0,1][0, 1] runs along each camera move. It drives the zoom, the pan, the iteration ceiling, the cycle density and the palette blend together. A second, separately eased parameter would let colour lead or lag the camera, which a viewer reads as an effect of its own.

The cycle density interpolates geometrically. For densities s0s_0 and s1s_1 at the two waypoints,

s(u)=exp ⁣(lns0+u(lns1lns0))(3) s(u) = \exp\!\left(\ln s_0 + u\left(\ln s_1 - \ln s_0\right)\right) \tag{3}

Equation (3) is a straight line in lns\ln s , so equal fractions of the move multiply the density by equal factors. Band density is perceived multiplicatively, so a linear ramp from 0.2 to 1.6 would spend most of the move in the dense half. The iteration ceiling interpolates the same way and for the same reason.

The palette interpolates on the baked tables, entry by entry, in Oklab. The transform and the measurements behind that choice are on the colour spaces page . Blending the stored bytes darkens the middle of a blend, measured at 61 percent of the luminance the two ends average to. Oklab holds the apparent rate of change constant instead, which is what a viewer watching a blend over several seconds is judging.

At u=0u = 0 and u=1u = 1 the table is copied, not blended. A held waypoint is therefore byte-identical to the same waypoint rendered as a single image. A film whose waypoints all name one palette produces the frames it produced before any of this existed.

Example film

The film below has four waypoints, each in a different palette. It travels from the neck of the main cardioid to a point on the boundary and back.

Palette morphing across four waypoints
Plays from Youtube once you press it
Waypoint Palette Cycle density Iterations Centre View height
0 ember 0.0563 10,000 0.750208+0i-0.750208 + 0i 2.62.6
1 fire 0.0496 10,000 1.2555510.381250i-1.255551 - 0.381250i 6.289×1036.289 \times 10^{-3}
2 ultraviolet 0.0629 100,000 1.2581440.381665i-1.258144 - 0.381665i 2.843×1062.843 \times 10^{-6}
3 random:b427b313 0.0704 10,000 0.750208+0i-0.750208 + 0i 2.62.6

Waypoint 3 returns to the coordinates of waypoint 0 and differs only in its palette. That palette is generated from the seed b427b313, so it is in no built-in list. Waypoint 2 is 914,600 times narrower than waypoint 0.

Each waypoint is held for 3 seconds. The film runs 8026 frames at 60 frames per second, 133.76 seconds, at 3840 by 2160. Of those frames 6772 are drawn. One leg plays in reverse and two clips repeat, and a repeated frame is encoded from the one it repeats.

Mandelbrot --movie COLOR.fmovie --res 3840x2160 --out color.mp4 \
           --speed 0.5 --ramp 5 --progress
Four frames from the film, one at each waypoint, in ember, fire, ultraviolet and a seeded palette
The four stopped waypoints, top left to bottom right. Each is drawn exactly as its waypoint defines it.

The move from waypoint 0 to waypoint 1 runs 27.46 seconds. The strip below samples it, and its two ends are the held frames.

Five frames across one move, the palette changing from ember to fire
One move sampled at five points. The ends are the two held waypoints, drawn without any blend.

References


  1. Renormalizing the Mandelbrot escape. Linas Vepstas. Retrieved 8 September 2026. https://linas.org/art-gallery/escape/escape.html (opens in a new tab)  ↩︎

Tags