Build a tool where someone drops in a picture and gets it back redrawn in lines and cubes: a grid where every cell's tone picks ONE mark in ONE colour, and bars of the same tone join into long strokes. From across the room it reads as the picture; up close it is pure pattern. Make it a WebGL2 fragment shader over the image, so it is instant at any size. THE PAGE The first thing on the stage is a drop zone: "Drop a picture here — or click to choose one". Paste works too. Once a picture is in, the zone steps aside and an Upload button swaps pictures. The frame takes the picture's own shape by default. Keep a downscaled copy (long edge ~1600 px, JPEG) in local storage so a reload keeps the picture; a shared link carries settings, never the picture. Before uploading to the GPU, draw every picture through a 2D canvas (long edge ≤ 4096). Drawing applies the camera's EXIF rotation; a raw texture upload does not, and phone portraits arrive sideways and stretched. THE SHADER, PER PIXEL 1. Cell: cell = floor(px / cellSize). cellSize = frameWidth / columns wide, × cellHeight tall ("cell height" = height ÷ width, 1 = square). 2. The picture under the cell, AVERAGED: sample at the cell's centre with textureLod at mip level log2(texels per cell). Generate mipmaps on upload. Alpha < 0.5 means no picture: the cell is empty (so a cut-out PNG gives a clean subject on the ground — the reference images are cut-outs). 3. Tone: lum = 0.299 r + 0.587 g + 0.114 b, then LEVELS, then clamp((v − 0.5)·contrast + 0.5), then pow(v, gamma), invert, and a brightness trim last. LEVELS is the step that decides whether real photos work. Fixed tier edges only suit pictures graded for them; a normal photo lands mostly in one tier. Offer: fixed; auto (stretch the 2nd–98th percentile); equalise (each tone replaced by its mid-rank, so every tier covers an equal share). Measure once per placement on a ~256 px copy, over covered pixels only; pass the percentiles as uniforms and the 256-step CDF as a 256×1 texture. Default to equalise. 4. Tier = floor(tone · N), N from 2 to 6. Each tier has a mark, a colour and a weight. Palette colours are assigned from the first non-empty tier upward. Weight is a share of the cell: flat (one value), or a ramp w = (min + (1 − min)·t^curve)·scale over the inked tiers — min 0.125, curve 0.77, scale 0.66 reproduces bar widths of 1/8, 1/2, 3/4, 1 of a cell. THE MARKS LINEAR — column, row, connect. A bar of the tier's weight, centred across the cell. Sample the tiers of the cell before and after it along its axis: - same tier → the bar runs straight through that edge (make it extend past the cell so neighbours fuse with no seam); - different → it stops half a gap short (full gap 4.5% of a cell) with a rounded cap of radius caps × half its width (caps ≈ 0.44). Draw it as a rounded-rectangle signed distance, anti-aliased over one pixel. This joining is the whole look. Offer a switch to turn it off and compare. connect is the maze: the bar runs DOWN if the cell above or below has the same tier, ACROSS if only a cell beside it does, and is a SQUARE when it stands alone. A maze frame = the darkest tone as columns, the next as rows, the next as connect, all in one ink, the light empty. CUBIC — square (side = weight × the cell's short side), checker (inks only cells where x + y is even; rectangle of weight × cell), diamond (|x|+|y| ≤ weight·m/2 — at weight 1 neighbours touch at their corners), dot, ring (square outline, stroke 18% of its size), solid (fills the cell, ignores weight). GROUND AND COLOUR A ground colour. Each tier's palette colour, or the picture's own colour per cell. An underlay (≤ 90%) mixes the photo in under the marks — capped so a cell with no mark still differs from the untouched photo. KEEP THE PANEL SMALL Offer five STYLES, each a fixed recipe, and only four controls: style, detail (× the style's cell count), contrast, balance (a lightness trim). Measured recipes (dark → light): - Yellow: column, row, joined bar, empty — black on #f7d358, square cells, flat weight 0.66, run gap 0.17. - Blue: empty, column 0.25, column 0.68 — #0000ff / #6a9cff on black, cells 1.75× taller than wide, square ends. - Figure: checker 0.85, column 0.35, row 0.4 — beige / orange / white on #1b1d20; on a photo (no cut-out) add a fourth, empty tier for the lightest. - Rooster: checker 0.55, diamond 1.0, column 0.8, empty — #0000ff / #5b8ff9 / #e4e5e7 on white. - Base: empty + four columns at 1/8, 1/2, 3/4, 1 × 0.66, round caps 0.44. Read each cell's tone one mip level wider than the cell so regions hold together. Detect cut-outs (transparent border): place them with room around (contain, ~0.82), premultiply on upload and divide by coverage, and treat cells under 80% coverage as empty so a subject lift's soft fringe drops out. CREDIT This is a study of the image treatment in the Base brand system — Mouthwash Studio's Base launch, with an image tool by John Provencher, after Karel Martens (his monoprints: translucent overprinted colour, grids of found shapes, and his Icon Viewer, where resolution alone decides whether you see a pattern or a person — the columns control is that dial). None of their code or artwork is part of this; build the mechanism, bring your own palette, and credit the reference in whatever you make.