In characters
A picture redrawn in glyphs as you watch: the cloud, the hills around Mougins, your camera or a photo of your own, in letters, lines, blocks or stars.
The cloud from the homepage, drawn large on a glow.
// A glyph's darkness is measured, not guessed: each one is drawn into a cell
// in the font it'll be shown in, and its ink counted.
context.fillText(glyph, 15, 30);
const { data } = context.getImageData(0, 0, 30, 60);
for (let i = 3; i < data.length; i += 4) ink += data[i];
// Then a cell's darkness picks a glyph from the ramp. Diffused shading passes
// each cell's rounding error on to its neighbours (Floyd–Steinberg), so a flat
// grey becomes the right mix of its two nearest glyphs.
const chosen = Math.round(work[i]);
const error = work[i] - chosen;
work[i + 1] += (error * 7) / 16;
work[i + cols - 1] += (error * 3) / 16;
work[i + cols] += (error * 5) / 16;
work[i + cols + 1] += error / 16;
// Lines: where brightness changes sharply, a line along the edge, at right
// angles to the change across it.
export function lineFor(gx: number, gy: number) {
const angle = ((Math.atan2(gx, -gy) * 180) / Math.PI + 180) % 180;
if (angle < 22.5 || angle >= 157.5) return "-";
if (angle < 67.5) return "\\";
if (angle < 112.5) return "|";
return "/";
}Ink
Every glyph puts down a different amount of ink. Rather than trust a ramp copied from somewhere, each of the 95 printable ASCII characters is drawn into a cell in JetBrains Mono, the face it's shown in, and its dark pixels are counted; the ramp is 16 of them, chosen to be as evenly spaced in darkness as the alphabet allows, from the space to the darkest. The picture is read at one value per cell and stretched so its darkest and lightest few per cent reach the ends of the ramp, and each cell takes the glyph its darkness picks. A cell is half as wide as it's tall, so there are twice as many columns as rows for the picture to keep its shape.
Shading
Rounded to the nearest glyph, a smooth gradient breaks into bands. Ordered shading rounds against a repeating four-by-four pattern of thresholds instead, so a grey between two glyphs becomes a fine mix of both. Diffused shading carries each cell's rounding error on to the cells to its right and below, as Floyd and Steinberg did for printers, so the mix follows the picture rather than a pattern.
Lines
Lines trades shading for drawing. Where the brightness changes sharply (the sharpest tenth of the picture's changes) the cell gets a line that runs along the edge, a bar, a slash or a dash, picked from the direction of the change, at right angles to it. Everywhere else a short ramp of five steps fills in, light enough that the lines carry the shape.
The cloud and the hills
The first is the cloud from the homepage, drawn large and bold on paper with a soft shadow beneath it: crisp edges for Lines, and greys in the shadow for the shading to work on. The second is the land around Mougins, from the elevation the Riviera scene is built on, as a relief print: the ground smoothed so the big shapes read at the size of a character, lit by a sun high in the north-west, and only the slopes turned away from it inked, the flat ground and the sea left as paper. Letters and Lines are drawn a row at a time in JetBrains Mono, whose characters are exactly six tenths of an em wide, so the rows line up without measuring; Blocks and Stars are the Cloud font's own glyphs, one centred in each cell.
Your pictures
The camera turns on only when it's chosen, and a photo is read only when one is picked. Either way the picture stays in the page: a frame is drawn into a small canvas, read back as brightness and drawn again as characters, about fifteen times a second for the camera, and only while it's on screen. Nothing is uploaded, and the camera stops as soon as you choose another picture or leave.