The previous chapter ended with one number per pixel. This one is about the other half of every fractal picture: the strip of colors those numbers are looked up in, how to blend it so it doesn't go muddy, how to spread it so nothing is wasted, and how to set it spinning.
Common mix-up: palette cycling does not animate the fractal. Not one orbit is recomputed. The program only rotates the table that turns counts into colors, so every pixel changes color at once and the shape appears to flow.
A palette is a rule that takes a position between 0 and 1 and returns a color. Almost every fractal program builds that rule from a handful of stops: a deep blue at 0, white at 0.42, orange at 0.64, near-black at 0.86. Between stops, it blends. The whole look of a picture comes from where the stops sit, what colors they hold and how the program blends between them.
For speed, the blended strip is usually computed once and stored as a lookup table, often 256 or 1,024 entries. Coloring a pixel is then one multiplication and one array read, which matters when a picture has a million pixels and an animation wants sixty of them a second.
Palettes come in two shapes. A clamped palette runs once from its first color to its last; it suits a quantity with a natural minimum and maximum, like a temperature map. A cyclic palette wraps around, so position 0.999 sits right next to 0.0. Fractal escape counts have no natural maximum, so most fractal palettes are cyclic: the count is divided by a cycle length and only the fractional part is used, as Chapter 15 showed. A cyclic palette needs its last stop to blend smoothly back into its first, or every cycle leaves a hard seam.
The cycle length P sets the palette's density: how many counts one trip through the colors covers. Many programs show its inverse, a density or "color speed" control, but it is the same dial. It has a strong effect, because escape counts are not spread evenly over a picture. Far from the set, ν changes slowly, a few counts across the whole frame. Near the edge it changes fast, sometimes by hundreds of counts between neighboring pixels.
A dense palette, with a short P, paints the slow outer region in rich, fine bands, but near the edge every pixel lands on an unrelated color and the boundary turns to static. A sparse palette, with a long P, keeps the edge calm but leaves the outside one flat color. Deep zooms make this worse: by the time you are a million times in, every pixel may have a count above 5,000, and the interesting part of the picture lives in a range of perhaps 50 counts. Matching P to the view is half the craft of fractal coloring; the histogram method later in this chapter is a way to do it automatically.
The offset slides the whole palette along. It changes no structure, only which color lands where, and it is the cheapest artistic control there is. Animate it and you have the subject of the next section.
On the VGA graphics standard IBM introduced in 1987, the most popular mode for pictures was 320 by 200 pixels with 256 colors. Each pixel in memory was a single byte, not a color but an index, 0 to 255, into a 256-entry table inside the video hardware. That table, the DAC palette, held a red, green and blue value for each entry, 6 bits apiece, so each of the 256 slots could be any of 2¹⁸ = 262,144 colors. A program changed a palette entry by writing its index to one I/O port and three bytes to the next.
That arrangement made one trick nearly free. Rewrite the 768 bytes of the palette and every pixel on the screen changes color in the same instant, without the processor drawing anything. Shift every entry along by one, frame after frame, and the colors appear to stream through the picture. A fractal drawn with its escape counts as palette indexes comes alive: bands pour inward toward the set, or outward from it, while the computer that took minutes to draw the picture sits idle.
This is palette cycling, also called color cycling, and it became one of the signatures of the DOS fractal era. Fractint, the Stone Soup Group's freeware fractal program, first released in 1988, could set its palette spinning at the press of a key. DAZZLE.EXE, James R. Shiflett's 1990 DOS program, made colors in motion its whole point.
Modern screens store full colors per pixel, so there is no hardware palette to rotate. The same effect is now a software step: keep every pixel's smooth count, and recolor from the stored counts each frame with an offset that grows over time. That is exactly what the editor below does. It is still cheap compared with computing the fractal, which is why cycling survives: a picture that took seconds to compute can animate for as long as you like.
An indexed-color image stores, for every pixel, which palette slot to use. Escape counts are natural indexes: a program could write n mod 256 straight into video memory and let the palette decide the rest. That is why classic fractal pictures were banded. With 256 slots and whole-number counts there was nothing between one band and the next to show. The smooth count of Chapter 15 needs more color resolution than an index can carry, which is one reason smooth coloring arrived with full-color displays.
It also explains a habit that survives in modern programs: palettes that are dark at both ends or loop back to their start. On the VGA, a palette that ended on a very different color from its first entry made a visible seam every 256 counts as it cycled. A cyclic palette with no seam can rotate forever.
JellyDazzle, J's audio-reactive kaleidoscope app for Mac, iPhone and iPad, is an homage to DAZZLE.EXE, and palette cycling is part of that heritage. Its fractal patterns, including a Mandelbrot deep zoom, Julia morphs, Burning Ship, Newton basins and a growing fern, do not carry fixed colors of their own. They take their colors from the app's live palette: one of 180 color schemes, every one interpolated in OKLab. Pressing the C key changes it, and every pattern follows.
Two details from the app's recent releases connect to this chapter. Each fractal has a seed that picks its parameters, so a pattern looks different each time it appears; an audit in version 3.6.5 found patterns that ignored their seed, appearing identical every time, and fixed 61 of them. And fractals there render progressively, a band of rows per frame. In 3.6.0 that made them flash, a bright band rolling down the screen several times a second, because the canvas faded between redraws. Version 3.6.1 fixed it by building each sweep off-screen and crossfading linearly between the last two finished pictures, so the colors flow instead of flicker. Chapter 18 covers that rendering side in depth.
The choice of OKLab for the blending is the subject of the next two sections. It is the difference between a scheme that glows evenly from stop to stop and one that sags through grey in the middle.
The obvious way to blend two colors is to average their red, green and blue numbers. Try it with a strong blue, (0, 0, 255), and a strong yellow, (255, 255, 0). The average is (128, 128, 128): a flat, middle grey. Every blend between complementary colors passes through grey this way, because the channels that one color has and the other lacks cancel out on the way. A gradient from blue to yellow made like this has a dull, muddy band in its middle.
Two separate problems are hiding in that grey. The first is gamma. Stored sRGB values are not proportional to light (Chapter 15 showed that 128 gives only about 22 percent of full brightness), so averaging stored values is not averaging light. Blending in linear RGB, after undoing the sRGB curve, fixes that part: the midpoint becomes brighter and the gradient no longer dips in brightness the way it did. But it is still grey, because the second problem remains.
The second problem is that RGB describes how to drive a screen, not how colors look. Equal steps in RGB are not equal steps to the eye. Our vision separates colors into a lightness signal and two opposing color signals, roughly red against green and blue against yellow. To blend the way the eye expects, it helps to blend in a space built on those same three directions, where a straight line from blue to yellow keeps its lightness changing steadily and its color strength from collapsing. Those are perceptual color spaces.
The sRGB standard (IEC 61966-2-1, 1999) maps a stored value u between 0 and 1 to linear light with a small straight segment near black and a power curve above it: linear = u ⁄ 12.92 when u ≤ 0.04045, and ((u + 0.055) ⁄ 1.055)^2.4 above. Over the whole range this behaves much like a power of 2.2, which is the number usually quoted. Going back the other way uses the inverse curve. Every conversion on this page, to linear RGB, CIELAB or OKLab, starts by undoing this curve.
In 1976 the International Commission on Illumination (CIE) standardized CIELAB, written L*a*b*. L* is lightness, from 0 for black to 100 for white. a* runs from green to red and b* from blue to yellow. It was designed so that equal distances would look like roughly equal differences in color, and for decades it was the standard answer to "blend these colors the way a person would". It is still used throughout printing and color measurement.
CIELAB has known flaws for this job. Its hues are not quite straight: a blend from a saturated blue toward white drifts toward purple on the way, because lines of constant hue in CIELAB curve in the blue region. Its lightness is also uneven across hues; a yellow and a blue with the same L* do not look equally light.
In 2020 Björn Ottosson published OKLab, a space with the same structure, one lightness axis L and two color axes a and b, built to fix those problems while staying simple to compute. Converting from linear RGB takes one 3 × 3 matrix, a cube root on each of the three results, and a second 3 × 3 matrix. The cube root mimics the eye's compressive response to light. Ottosson fitted the matrices to modern data on how people judge lightness, color strength and hue, with special attention to keeping hue steady as colors get lighter or darker. The result blends blue to white without turning purple, and keeps lightness changing evenly along a gradient. That is why it has spread so quickly: it is now part of the CSS color specification for the web, and it is the space JellyDazzle blends its schemes in.
Blending in OKLab works the same as blending in RGB: convert both stop colors to OKLab, take the weighted average of their L, a and b, and convert back. The editor below lets you switch the live render between sRGB, linear RGB, CIELAB and OKLab, and always shows the sRGB and OKLab versions of your palette one above the other.
From linear sRGB (r, g, b), Ottosson's first step is l = 0.4122214708 r + 0.5363325363 g + 0.0514459929 b, m = 0.2119034982 r + 0.6806995451 g + 0.1073969566 b, s = 0.0883024619 r + 0.2817188376 g + 0.6299787005 b. Take the cube root of each to get l′, m′, s′. Then L = 0.2104542553 l′ + 0.7936177850 m′ − 0.0040720468 s′, a = 1.9779984951 l′ − 2.4285922050 m′ + 0.4505937099 s′, b = 0.0259040371 l′ + 0.7827717662 m′ − 0.8086757660 s′. The inverse runs the same steps backward. These are the exact numbers this page's JavaScript uses.
Written in polar form, OKLab becomes OKLCh: L, chroma C = √(a² + b²) and hue h = atan2(b, a). Blending in LCh moves around the color wheel instead of across it, which keeps chroma high between complementary colors at the cost of passing through other hues on the way. For fractal palettes, straight OKLab blends between well-chosen stops are usually the better default; LCh is useful for rainbow-like schemes.
Not every OKLab color can be shown. Blending inside the space can land slightly outside the range a screen can make; this page simply clips each channel to 0–1, which is what most palette code does.
The top bar is your palette, blended in the space you choose. Drag the round handles under it to move stops; click on the bar to add a stop there; tap a handle to select it and pick its color. Below, the same stops are blended in sRGB and in OKLab, with the OKLab lightness of each plotted underneath: a steady line is an even gradient, and a dip is mud. The Mandelbrot render is computed once; every change you make, including cycling, only recolors stored smooth counts.
Try the "Blue → yellow" preset: the sRGB strip sags to grey in the middle, and its lightness curve swings far from a straight line between the two stops. "Lightness dip" measures the worst gap between each curve and that straight line. Then set a cycle speed and watch the recolor time: rotating the table costs a few milliseconds, against far more to compute the picture.
Count how many pixels in a picture have each escape count and plot it. The result, a histogram, is lopsided in nearly every fractal view. In a whole-set picture, most pixels escape in the first few steps and a long thin tail stretches out to the iteration limit. In a deep zoom the shape flips around: almost every pixel has a count in the thousands, crowded into a narrow range.
That lopsidedness defeats simple mappings. Map the count to the palette linearly, from 0 to the iteration limit, and in a whole-set view nearly the entire picture lands in the first few percent of the palette. Taking a logarithm first helps a great deal for wide-ranging counts, but it is still a fixed curve that knows nothing about this particular picture.
Histogram coloring lets the picture decide. For each pixel, ask what fraction of all escaping pixels have a smaller count. That fraction, between 0 and 1, becomes the palette position. Because it is a ranking, it spreads the pixels evenly over the palette by construction: a tenth of the pixels get the first tenth of the colors, and so on. The technique is known in image processing as histogram equalization, and the curve it uses is the histogram's running total, the cumulative distribution.
It has a cost. The color of a pixel now depends on every other pixel in the frame, so zooming, panning or changing the iteration limit changes the colors of regions that did not move. In an animation, that shows as colors shifting under you; deep-zoom videos often fix the mapping from one frame instead. And because it ranks plain counts, it brings the bands back unless it is fed the smooth count of Chapter 15 and interpolates between neighboring entries of the running total, as the instrument below does.
Build the histogram on whole counts, h[k] = number of escaping pixels with ⌊ν⌋ = k, and its running total, H[k] = h[0] + … + h[k], divided by the number of escaping pixels. For a pixel with smooth count ν, let k = ⌊ν⌋ and f = ν − k, and use position = H[k − 1] + f · (H[k] − H[k − 1]). Pixels inside one count are spread across that count's share of the palette in proportion to their fraction, so the result is continuous: no bands, and still equalized.
Two refinements are common. Excluding the interior from the count keeps a large black region from distorting the ranking. And blending equalization with a plain linear or log mapping, say 70 percent equalized, keeps some sense of absolute depth that a pure ranking throws away.
The three pictures share one computation: the same smooth counts, the same palette, three different mappings from count to palette position. The chart underneath is the picture's histogram, with each mapping's curve drawn over it: where a curve is steep, that range of counts gets many colors; where it is flat, few. Change the view or the iteration limit and watch the linear picture wash out while the equalized one keeps using its whole palette.
"Palette used" is the share of the palette covered by the middle 90 percent of escaping pixels: from the 5th to the 95th percentile of their palette positions. Equalization aims for 0.90 by construction and falls a little short only where many pixels share nearly the same count; watch how small the linear figure gets. The chart's horizontal axis stops at the 97th percentile so the crowded part stays readable.
Roughly 1 in 12 men and 1 in 200 women of Northern European ancestry have a red–green color vision deficiency, and the figures are lower but far from zero elsewhere. The common forms come from one of the three kinds of cone cells responding differently or being missing: protan (long-wavelength, "red" cones), deutan (medium, "green") and the much rarer tritan (short, "blue"). For a protan or deutan viewer, red, orange, yellow and green lie closer together, and some pairs collapse into one color.
For pretty pictures this mostly changes how a palette looks rather than what it says. But fractal palettes borrow from data visualization, and there it matters. The classic rainbow palette is the worst offender: its lightness goes up and down instead of steadily one way, so it invents bright stripes at yellow and cyan that are not in the data, and its red and green ends merge for many viewers. Palettes designed for science, such as viridis, created by Stéfan van der Walt and Nathaniel Smith for the plotting library matplotlib in 2015, do the opposite: lightness rises steadily from one end to the other, so the order survives in greyscale and for every common kind of color vision.
The rules that follow are simple. Make lightness, not hue alone, carry the main change in a palette. Avoid putting red and green at the same lightness next to each other. And test: the editor above can show your palette and the live render through a simulation of each deficiency, using the model of Machado, Oliveira and Fernandes (2009), which is widely used for exactly this check. The site's own color-blind toggle, in the sidebar, swaps this page's interface colors for a safe set in the same spirit.
| Palette type | Lightness | Good for | Color-blind safe? |
|---|---|---|---|
| Rainbow (hue sweep) | Up and down | Psychedelic looks | Poor: red and green ends merge; false stripes |
| Sequential (viridis-like) | Steadily rising | Showing depth honestly | Good |
| Cyclic, dark–bright–dark | Rises then falls | Fractal bands, cycling | Good if hue changes avoid red against green |
| Two-hue blend in sRGB | Sags in the middle | Nothing in particular | Depends on the hues; the mud is there for everyone |
Machado and colleagues modeled each deficiency as a shift in one cone type's sensitivity and reduced the result, for each severity, to a 3 × 3 matrix applied to linear RGB. For full deuteranopia the matrix rows are (0.367, 0.861, −0.228), (0.280, 0.673, 0.047) and (−0.012, 0.043, 0.969). Each row sums to 1, so greys stay grey. The editor applies the matrix to every entry of the palette table, which is why the simulation costs nothing extra per pixel.
A simulation is a model, not a person: severities vary, and real viewers adapt. It is a good way to catch a palette whose meaning depends on red against green, and not a promise of what any one viewer sees.