Draw My Canvas / studio

Barnsley Fern

Four rules, picked at random, grow a leaf. Put a point at (0, 0), roll a die weighted 1 : 85 : 7 : 7, move the point by the affine map the roll picks, mark where it lands, and repeat. Every mark falls on the same fern, the one Michael Barnsley printed as four rows of numbers in 1988. The plate grows it live as density, draws each of the four maps as a frame over it, and the slider under it turns one map so the whole frond curls.

Plate 38chaos-game

Waiting for the animation above to load…

What lands in the file, and what the width does

One click writes whatever the animation is drawing at that moment to a PNG, with the drawmycanvas.com mark drawn into the picture rather than laid over it. Leave the width box empty and you get the stage exactly as your browser rasterised it — your window’s width times its device pixel ratio, which is about 1,600 px across from a 1280‑px window on a HiDPI laptop and about 353 px from a 390‑px phone.

Type a width instead, or take a preset, and the frame is redrawn into a canvas that wide: the height follows the stage’s own shape and the mark scales with it. The stage is 16:9 on a wide window and 4:3 below 560 px, so a width of 1200 saves 1200×675 on a laptop and 1200×900 on a phone. 1200 px is the width Open Graph and X link cards are cut from — the canonical card is 1200×630, and a card crops the extra height rather than letterboxing it.

Honest limits. Asking for more pixels than the stage was drawn at resamples pixels that were never drawn: a 1920‑px file exported from a 353‑px phone stage is bigger, not sharper. For a big file that is sharp, use Save as wallpaper: it draws the plate again from scratch at exactly 1179×2556 or 1290×2796 (phones), 1920×1080, 2560×1440 or 3840×2160 (4K), so every line is rasterised at that size and the shape is the screen’s, never stretched. Because the plate restarts, a wallpaper is a fresh run of it rather than the exact frame on screen; it is given as long as the stage has been running, up to 20 seconds, to develop. A very large width is a real memory allocation and a browser is allowed to refuse it; when that happens the line above says so plainly and nothing else on the page changes. Stills are PNG only — no JPEG, no WebP. On browsers that can record video, Record a clip saves 5, 10 or 20 seconds of the running animation as MP4 or WebM (whichever this browser can encode) at the same width, with the mark in every frame. A clip is not a seamless loop, it has no audio, and a width bigger than the stage is resampled rather than sharper — only the wallpaper is redrawn at its size. Where a browser cannot record video the button never appears and a PNG is the only export. And nothing is uploaded: the frame or clip is assembled in your own browser, so no frame of this plate ever reaches us.

f2 as printed: turns 2.7° right

The slider turns map f2 by up to 6.9° either way about the foot of the stem and leaves f1, f3 and f4 as printed. The fern regrows from the same dice with the new map. It works on this page only; an embed on another site always shows Barnsley’s fern.

Live on an HTML canvas · vanilla JavaScript · no dependencies Open fullscreen

About this piece

The Barnsley fern is drawn by a game of chance. Put a point at (0, 0). Pick one of four affine maps at random, the first with probability 1%, the second 85%, the third and fourth 7% each. Move the point with that map, mark where it lands, and do it again. After a few rolls every mark lies on the fern. The order the marks arrive in is random, but the picture they fill is not. This plate plays that game live, at about five points per device pixel of the fern’s rectangle (about 800,000 in a 1280-pixel-wide window at normal pixel density, up to a cap of 2.4 million on high-density screens). Each pixel is coloured by how often it was hit, on a log scale along the theme’s ramp: background, then deep cobalt, cobalt, and finally coral where the hits pile up most.

The recipe is drawn on top. The dashed rectangle is the box around the whole fern, and the four solid frames show where each map sends that box: f1 in the text ink, f2 in cobalt, f3 in coral and f4 in deep cobalt. The readout in the bottom-left corner (on screens wide enough to fit it beside the fern) lists each map’s probability next to how often it has actually been picked so far. A fern grows over 7 seconds and is held for 9. Then it grows again from fresh dice, so the shape stays the same and only the grain changes. The stage opens with 40% of the points already in. With reduced motion switched on you get the grown fern as one still frame. The slider still works and redraws it, and nothing animates.

The four maps, as Barnsley printed them

Each map is x′ = a·x + b·y + e, y′ = c·x + d·y + f: a linear squeeze, turn or flip given by a–d, followed by a shift (e, f). These are the four rows the plate uses, digit for digit:

      a       b       c      d      e     f      p     draws
f1   0.00    0.00    0.00   0.16   0     0.00   0.01   the stem
f2   0.85    0.04   −0.04   0.85   0     1.60   0.85   successively smaller leaflets
f3   0.20   −0.26    0.23   0.22   0     1.60   0.07   largest left-hand leaflet
f4  −0.15    0.28    0.26   0.24   0     0.44   0.07   largest right-hand leaflet

starting at (0, 0), every point stays inside
  −2.1820 < x < 2.6558,   0 ≤ y < 9.9983

Read as geometry: f1 flattens the whole fern onto the segment x = 0, 0 ≤ y ≤ 1.6, which is the stem. f2 shrinks the fern to 0.85 of its size, turns it 2.7° clockwise and lifts it 1.6 up the stem, so it lands on the fern minus its bottom pair of leaflets. f3 shrinks it to about a third and turns it about 49° anticlockwise, making the lowest left leaflet. f4 does much the same on the right, but its determinant is negative (−0.109), so that leaflet is a mirror image of the fern.

Michael Barnsley, a British mathematician, published this table as table III.3, “IFS code for a fern”, in his 1988 book Fractals Everywhere (Academic Press). The book grew out of the Fractal Geometry course he taught at the Georgia Institute of Technology, and he shaped the fern after the black spleenwort, Asplenium adiantum-nigrum. The coefficients, the probabilities and the part each map draws are taken from Wikipedia’s article “Barnsley fern”, which reproduces that table and cites the book. The bounding box is the one quoted in Rosetta Code’s “Barnsley fern” task, whose Go entry takes it from an earlier revision of the same Wikipedia article. Both were read on 12 October 2026. The test suite checks the plate’s maps and probabilities against this table, and runs a million seeded points to confirm none of them leaves the box.

Why random choices draw a definite leaf

Every map pulls points closer together: f2 by a factor of 0.85, f3 and f4 to about a third, and f1 all the way down to a line. For any set of shrinking maps like this there is exactly one shape that is the union of its own images under them. That shape is called the attractor, and here it is the fern: four smaller copies of itself, one per frame on the plate. A point moved by randomly chosen maps falls onto the attractor within a few steps and never leaves it. Barnsley’s collage theorem works in the other direction: cover a shape with shrunken copies of itself, read off the maps, and the chaos game gives back something close to the shape. That is how the table was found.

The probabilities do not decide the shape. They decide where the points pile up, and so how fast each part fills in. A common rule of thumb weights each map by how much it shrinks area, which is the size of its determinant: 0.724 for f2, 0.104 for f3 and 0.109 for f4. That would give f2 about 77%, f3 11% and f4 12%. Barnsley’s table gives f2 more, 85%, so the upper frond and its tip fill in quickly while the two lowest leaflets stay sparse for longer. You can see this on the plate. f1 shrinks area to nothing, yet it still needs a small chance: a map that is never picked adds nothing, and without f1 the stem all but disappears (in a test run of two million points, the hits on the stem fell from 16,040 to 94).

The tip is special too. The one point f2 leaves where it is, its fixed point, is (2.6556, 9.9585), the tip of the fern. Applying f2 again and again walks any point up the frond towards it, so the tip is the busiest pixel on the plate and the first to turn coral. Its x is also, to four decimals, the right-hand edge of the bounding box.

Bend it: what the slider changes

The slider under the plate turns f2’s whole output, shift included, by up to 6.9° either way about the foot of the stem, and leaves f1, f3 and f4 exactly as printed. f2 is the map that stacks the fern on a smaller copy of itself, so a turn in f2 compounds. The frond leans by that angle, the next copy up leans by it again, and so on up to the tip, so a few degrees curl the whole leaf. Barnsley’s f2 already turns 2.7° clockwise, which is why his fern leans right. Add 2.7° anticlockwise and f2 becomes a pure shrink-and-lift: the frond grows straight up. Go further and it curls left. At the right-hand end the frond rolls into a hook about 7.9 units wide and 8.9 tall, compared with 4.8 by 10.0 for Barnsley’s own. At the left-hand end it bends right into a crook about 7.4 by 6.9. The frame is refitted to each bend so the whole fern stays on screen, and the readout states the angle.

Reading the plate: what to watch for

  • Every leaflet is a fern. The f3 and f4 frames each hold a complete small fern. Each leaflet of those is again a fern, and so on down. On a laptop stage you can follow it about four levels before the leaflets are only a few pixels across.
  • The readout settles on the table. After a million picks, the 85% map is typically within 0.04 percentage points of 85.00%. That is one standard deviation, √(0.85 × 0.15 / 106). The test suite allows 0.5 points, and the plate’s own generator lands far inside that.
  • The density is uneven, on purpose. The frond’s centre line and the tip are bright, while the lowest leaflets are faint and grainy. That is the 85 : 7 : 7 weighting showing through, not missing detail.
  • The frames nest. The f2 frame almost fills the dashed box, shifted up by the length of the stem and tilted 2.7°. f1’s frame has no width, so it shows as a short line along the bottom of the stem.

How it is computed

The plate keeps one 32-bit counter per device pixel of the fern’s rectangle. The dice come from a small seeded generator (mulberry32), so the gallery tile and the embed grow the same fern from the same seed. Each frame adds points in proportion to the wall-clock time since the last one (the total divided by 7 seconds, with frame gaps clamped at 0.1 s), so a 30 Hz screen and a 120 Hz one grow at the same speed. Counts become colours through a 256-step table, with u placed on the ramp at 0 for the background, 0.5 for the deep cobalt, 0.86 for the cobalt and 1 for the coral:

u = log(1 + hits) / log(1 + 20 × average hits per lit pixel)

Only the fern’s rectangle is written to the canvas, in one putImageData call. The frames are ordinary strokes on top, each with a background-coloured halo so it reads over the leaves. A theme change repaints the same counts in the other inks, and the canvas is only redrawn when points were added.

Honest limits

A caricature of a plant. A real black spleenwort is not exactly self-similar and stops branching after a few levels. This fern repeats forever, and every leaflet is a perfect mirror of every other at its level.

The colours measure the dice, not the fern. Brightness shows how often the chaos game visits a pixel, which comes from the 1 : 85 : 7 : 7 weights. Change the weights and the same fern would be lit differently.

Pixels, not infinity. At five points per pixel the faint lowest leaflets stay grainy, and nothing smaller than a pixel is ever seen. Very large screens are capped at 2.4 million points, so they are grainier still.

The slider is one map and this page only. It turns f2 alone, and it reaches the stage through postMessage from this page. An embed on another site shows Barnsley’s fern unbent. The bounding box and the frequency checks in the test suite apply to Barnsley’s maps, not to bent ones.

A 30-line version

The snippet below grows a still fern from a million points in 30 lines of HTML and plain JavaScript, with no library. It uses the same table and the same log-density colouring, in one ink. The test suite runs it against a stub canvas and checks that its maps are the plate’s and that it marks pixels only inside the fern’s box. It leaves out the frames, the readout, the growing animation, the bend, sharp drawing on high-density screens (it draws in CSS pixels) and the reaction to theme changes. Because nothing moves, there is no reduced-motion case to handle.

Things worth changing: set f2’s b and c to 0 and the frond stands up straight. Set f1’s p to 0 and give its 0.01 to f2: the stem all but vanishes. Swap the signs of f4’s a and c and its leaflet stops being a mirror image.

<canvas id="fern" style="position:fixed; inset:0"></canvas>
<script>
const dark = matchMedia('(prefers-color-scheme: dark)').matches;
const [ground, ink] = dark ? [[11, 13, 18], [150, 180, 255]] : [[231, 226, 213], [40, 72, 205]];  // the site's cobalt
// Barnsley's four maps: x' = a x + b y + e,  y' = c x + d y + f, each picked with probability p.
const MAPS = [ //  a      b      c      d     e     f      p
  [ 0.00,  0.00,  0.00, 0.16, 0, 0.00, 0.01],  // f1 the stem
  [ 0.85,  0.04, -0.04, 0.85, 0, 1.60, 0.85],  // f2 the whole fern, smaller and turned
  [ 0.20, -0.26,  0.23, 0.22, 0, 1.60, 0.07],  // f3 the largest left-hand leaflet
  [-0.15,  0.28,  0.26, 0.24, 0, 0.44, 0.07],  // f4 the largest right-hand leaflet
];
const cv = document.getElementById('fern'), ctx = cv.getContext('2d');
const W = cv.width = innerWidth, H = cv.height = innerHeight;
const s = Math.min(H / 10.6, W / 5.2), ox = W / 2 - 0.24 * s;  // the fern is 10 tall, 4.84 wide, centred on x = 0.24
const hits = new Uint32Array(W * H); let x = 0, y = 0;
for (let i = 0; i < 1e6; i++) {
  let r = Math.random(), k = 0;
  while (k < 3 && r >= MAPS[k][6]) r -= MAPS[k++][6];  // walk the probabilities until r falls inside one
  const [a, b, c, d, e, f] = MAPS[k];
  [x, y] = [a * x + b * y + e, c * x + d * y + f];
  const u = Math.floor(ox + x * s), v = Math.floor(H - 0.3 * s - y * s);
  if (u >= 0 && u < W && v >= 0 && v < H) hits[v * W + u]++;
}
const img = ctx.createImageData(W, H);
for (let p = 0; p < W * H; p++) {
  const t = Math.min(1, Math.log1p(hits[p]) / Math.log(40));  // log density, 0 = empty, 1 = 39 hits or more
  for (let ch = 0; ch < 4; ch++) img.data[4 * p + ch] = ch === 3 ? 255 : ground[ch] + (ink[ch] - ground[ch]) * t;
}
ctx.putImageData(img, 0, 0);
</script>
Paste it into an empty .html file.

Curious how the loop and canvas fit together? Read how it works →

More from the gallery

All 38 animations in the gallery →