<CarlosDev/>

Part 4 of 4

Matteflow: text that flows around a dancer

A canvas demo — extract a foreground silhouette with a color-distance matte, then flow article text around it every frame with pretext.

In this record3 sections

The demo

Load any video of a person against a plain background. The demo extracts the foreground using a color-distance matte and flows article text around the moving silhouette — every frame, using pretext for line placement.

This demo runs in your browser and needs JavaScript. The text around it explains what it shows.

Best results: videos with a person against a solid or lightly textured background (studio green/white screen, plain wall). The sliders let you tune the wrap strength, subject scale, and position.


How it works

This demo is a compressed port of Matteflow by summerKK — an editorial layout prototype that demonstrates exactly why pretext exists. The original has four modules; here they’re ported into one React component and the small pure modules it draws with.

Step 1: color-distance matte

Each video frame is drawn to an offscreen canvas and the pixel data is read once:

offCtx.drawImage(video, 0, 0, canvasWidth, canvasHeight)
const { data } = offCtx.getImageData(0, 0, canvasWidth, canvasHeight)

The background color is estimated by sampling the frame edges (corners + borders). Then each pixel gets an alpha value based on its color distance from that background:

const distance = Math.sqrt((r - bg.r) ** 2 + (g - bg.g) ** 2 + (b - bg.b) ** 2)
const alpha = clamp((distance - threshold) / feather, 0, 1)

A pixel close to the background color gets alpha ≈ 0 (transparent). A pixel far from it gets alpha ≈ 1 (foreground). The demo fixes both numbers (threshold and feather are constants in the renderer), so its sliders do not touch the matte: Wrap sets how strongly the text bends around the silhouette, Scale sizes the figure, and V-Shift / H-Shift move it inside the frame.

Step 2: wrap profile

The alpha mask is divided into horizontal bands. For each band, the leftmost and rightmost foreground pixels define the subject width and horizontal center at that height:

// For each band (vertical slice of the figure)
const bandWidth = (rightPixel - leftPixel) / frameWidth
const bandCenter = (leftPixel + rightPixel) / 2 / frameWidth - 0.5
profile.push({ width: bandWidth, offset: bandCenter })

This profile — 10 numbers describing the silhouette shape — is computed every frame. It drives where the text exclusion zones are.

Step 3: text layout with pretext

For each text line, the wrap regions are computed from the profile:

const regions = computeWrapRegions({
  y,
  lineHeight,
  stageCenterX,
  stageWidth,
  profile,
  wrapStrength,
  gutter,
  articleLeft,
  articleWidth,
  stageTop,
  stageHeight,
})

computeWrapRegions returns one or two column regions — the areas to the left and right of the subject that are wide enough for text. Then layoutNextLine from pretext places text into each region:

for (const region of regions) {
  const line = prepared.nextLine(cursor, region.width)
  if (!line) break
  lines.push({ text: line.text, x: region.x, y })
  cursor = line.end
}

This runs per frame. The expensive part — font measurement — happened once when the component mounted. The per-frame work is arithmetic: profile computation + line break logic. The result is text that genuinely follows the moving shape.


Why this matters beyond the demo

The Matteflow demo is not a real-world feature. But the pattern it demonstrates is:

Text layout as a pure function of content + geometry, evaluated at will.

When layout() costs 0.09 ms, you can call it in your render loop without a second thought. You can rebuild the entire text layout every frame in response to any change — container resize, content update, obstacle movement — and the user never notices the cost.

Without pretext, this demo would require either:

  • Pre-computing layouts for every possible video frame (infeasible)
  • Running DOM measurement off-thread (not possible; layout is main-thread only)
  • Accepting the reflow cost and watching the frame rate drop (the wrong answer)

The library’s value is not just speed. It is that text measurement becomes a value you compute — not a side effect you wait for.


This piece is based on the pretext README and Cheng Lou’s original library. The Matteflow concept is from summerKK/Matteflow.

Filed under

  • JavaScript
  • Performance
  • Web
  • Typography
Back toPretext: skip the browser's most expensive operation