DIAGRAM EDITION  the stages in plain languageReference edition →

Stage 2 of 14

Reading a fuzzy edge as a measurement

A pixel sitting on the edge of a shape is not blurry, and it is not noise. It is a precise report of how much of that pixel the shape covers — and this stage reads that report, including how much to trust it.

The edge is not fuzzy. It is measured.

Zoom into any smooth curve rendered by a computer and the pixels along its edge are never fully the shape's colour or fully the background's. They sit in between — a dark grey between black ink and white paper, say. It is tempting to treat that in-between pixel as noise: round it up if it is dark enough, round it down if it is not, and get on with finding the edge.

That throws away the most useful number in the whole image. A pixel that is 70% covered by the shape and one that is 30% covered are reporting two different, precise facts about exactly where the edge crosses that pixel — not two shades of "probably an edge here." Rounding either one to solid or empty erases the difference between them. This stage does the opposite: it recovers the fraction, treats it as a real measurement of coverage, and — just as importantly — works out how confident that measurement is.

Recovering the fraction

Every pixel touched by an edge is a blend of two colours: the shape's ink and whatever sits behind it. If the shape's colour and the background colour are both known, an in-between pixel's colour pins down exactly what fraction of it the shape covers — the same way knowing a paint is a mix of white and red lets you work out the ratio just by looking at how pink it is.

A row of pixels crossing an edge 0% 0% 12% 31% 48% 67% 85% 100% 100% 100% — the true edge, which this stage never sees directly
Nobody supplies the dashed line. It is inferred from the coverage percentages, which come straight from how dark each pixel is between the paper colour and the ink colour.

The maths behind this uses all three colour channels — red, green and blue together — rather than reducing everything to a single brightness value first. That matters for coloured edges: a boundary between two saturated colours of similar brightness would be invisible to a brightness-only method but is perfectly readable when all three channels are used.

The number that matters more: how sure are we?

Recovering the coverage fraction is only half the job. The harder, and more useful, half is working out how much to trust each fraction — because not every edge is measured equally well.

Two things make a boundary hard to pin down precisely:

  • Low contrast. A dark grey line on black paper and a black line on white paper might both be one pixel wide, but the grey-on-black edge is measured far less precisely: the same amount of sensor noise represents a much bigger swing in "coverage" when the two colours are close together than when they are far apart.
  • A gentle, spread-out transition. If the colour changes gradually over several pixels rather than sharply over one, there is a wider range of positions the true edge could plausibly sit at.
Same edge, two very different confidences Crisp, high contrast located to within ~a tenth of a pixel Soft, low contrast could be anywhere across several pixels
Same physical distance, opposite confidence. This stage measures that difference explicitly, in pixels, for every point on every boundary.

Both effects are combined into a single number: a positional uncertainty, in pixels, for every point on every boundary. A crisp, high-contrast edge might carry an uncertainty of a few hundredths of a pixel. A soft, low-contrast one might carry an uncertainty of a whole pixel or more.

Why this is the single most important idea in the project

Later stages need to decide how many points to spend describing each boundary — a circle drawn with four points looks polygonal; one drawn with four hundred is wasteful. The obvious approach is a separate rule: "simplify more on boring bits, less on important ones." That is a heuristic, and heuristics need tuning and second-guessing.

This project does not need that rule, because the confidence number already says it. A boundary point measured with a large uncertainty is, by definition, a place where extra precision would be pretending to know something nobody knows. So the curve-fitting stage simply asks each point to stay within its own measured uncertainty, and the simplification falls out for free: it is loose exactly where the measurement was loose, and tight exactly where it was tight. Nothing downstream has to separately decide "this part doesn't matter" — the coverage stage already said so, honestly, as part of taking the measurement.

A faint boundary is measured badly, and the number this stage produces says so — rather than confidently reporting a precise position that happens to be wrong.

When there is nothing to measure against

The whole method depends on knowing the two colours a boundary sits between. Usually that is easy: look at pixels that are fully one colour or fully the other. But a very thin feature — a hairline stroke narrower than a single pixel — never produces a fully covered pixel at all. Every pixel it touches is already a blend.

In that situation the honest answer is: we genuinely cannot tell the difference between a faint, wide stroke and a dark, narrow one — they can produce identical pixels. Rather than guessing a width and stating it with false confidence, this stage recognises the situation (by checking how much of the "covered" area is safely interior, away from any edge) and widens its uncertainty accordingly, so later stages treat that region gently instead of committing to detail that was never really there.

Telling real edges from a compressed file's damage

This stage also has to notice when the input file itself is untrustworthy — for instance, a photograph-style compressed file (JPEG) that adds faint ripples of colour near edges that were never in the original artwork. Left alone, those ripples would be measured just as carefully as real detail, and the tracer would faithfully draw shapes for compression artefacts.

There is an honest limitation here, worth stating plainly rather than glossing over. Two of the checks this stage runs — how wide an edge is, and how much random noise sits in flat areas — are both blind to this particular kind of damage. The ripples do not make an edge wider, and because they cluster in a thin band beside the edge rather than spreading across the whole image, the noise check's "typical" reading stays at its floor as if the image were perfectly clean. Both checks report a clean bill of health on a file that is not clean.

This was a real bug: a compressed logo passed both checks, and the tracer dutifully traced the compression ripples as if they were shapes in the artwork. The fix was not to make either check cleverer — it was to stop asking pixels a question they cannot answer, and instead ask the file itself what format it was saved in. Some formats (JPEG, and one of the two flavours of WebP) are known to damage flat regions this way; the honest fix was to check that fact directly rather than trying to infer it from pixels that do not carry the signal.