Guides / 2026-10-21

We swept a visual-diff threshold from 0.02 to 0.3 expecting a curve. We measured a cliff.

Pixel-diffing two unrelated pages at three diffThreshold settings — the strict end reads as 99.75% different, the default as 6.22%, and the loose end barely moves from there. The useful range is one order of magnitude wide, not the whole 0-1 scale.

Our screenshot Actor's visual-diff mode has a diffThreshold input, 0 to 1, "lower is stricter." We wrote the FAQ entry on intuition — strict catches more, loose catches less, pick something in the middle if unsure. We hadn't actually run the same comparison at more than one setting. So we picked two pages that have nothing in common (Hacker News and example.com) and diffed them three times, same pair, only the threshold changed.

The setup

Same Actor, same compareUrl pair, one input changed per run — the three values the FAQ already suggests as "strict / default / loose":

$ curl -s -X POST "https://api.apify.com/v2/acts/<actor>/run-sync-get-dataset-items" \
    -H 'Content-Type: application/json' \
    -d '{"urls":["https://news.ycombinator.com"],"compareUrl":"https://example.com","diffThreshold":0.1}'

diffPercent and diffPixels come straight off the output row (comparedPixels is the overlap region used as the denominator — the two pages render at different heights, 1215px vs 1080px, so the comparison is cropped to the shared 1920x1080 top).

diffThreshold diffPixels comparedPixels diffPercent
0.02 (strict) 2,068,425 2,073,600 99.75%
0.10 (default) 129,026 2,073,600 6.22%
0.30 (loose) 92,632 2,073,600 4.47%

The cliff

Going from 0.02 to 0.1 — one notch on a 0-to-1 slider — drops the reading from "almost every pixel differs" to "6% differs." Going from 0.1 to 0.3, three times further along the same slider, only drops it another 1.75 points. The threshold isn't linear in its effect, and it isn't even close: nearly all of the slider's useful range sits in the gap between 0.02 and 0.1, and the rest of the scale from 0.1 to 1.0 is comparatively flat.

The mechanism is ordinary once you look at the two screenshots instead of just the numbers. HN and example.com share no layout, no copy, no color scheme — the only thing anywhere close between them is that both have large flat-colored regions (HN's pale #f6f6ef background, example.com's white one) sitting next to small blocks of near-black text. At diffThreshold: 0.02, the per-pixel color-distance tolerance is tight enough that two different near-black greys, two different off-whites, or a one-pixel antialiasing edge all register as "changed" — which is most of the image, because almost no pixel in either screenshot is bit-identical to the corresponding pixel in the other. Loosen the tolerance past some point and all of that low-contrast noise collapses to zero at once, leaving only pixels that are genuinely, visibly different colors — which for this pair is mostly just "HN's beige vs example.com's white," a smaller and much more stable number. That collapse is the cliff; it isn't gradual because color distance isn't gradual in how much of a flat-toned screenshot it touches.

What this means for picking a number

0.02 is not "very strict regression detection" for two different pages — it's close to meaningless. If you're comparing genuinely different pages (a competitor's site vs yours, or a redesign mockup vs the old layout), a result near 100% tells you nothing you didn't already know by looking at them; the number is dominated by antialiasing noise, not by the real difference. It's the right setting for comparing the same page against itself across a deploy, where you want to catch a single shifted button or a font substitution — not for comparing two unrelated layouts.

0.1 (the default) and 0.3 answered almost the same question here, which is itself the useful finding: if two pages differ by an actual visible redesign, you don't need to tune this value carefully to see it, because the readings in that range are close together on real-world layouts. The place tuning actually matters is the cliff itself — somewhere between 0.02 and 0.1 is where "everything looks different" turns into "the real differences only." We haven't mapped that interior yet; it's the obvious next sweep if we revisit this.

Don't read the percentage as "percent of the page that changed" without checking what moved it. A single large background-color swap can produce a bigger diffPercent than ten small text edits, because the metric counts pixels, not changes. If the number matters to a decision, open the diff image — it's returned alongside the percentage for exactly this reason.

Packaged version

website-screenshot-scraper's compare mode takes any two URLs, diffs them pixel-by-pixel, and returns diffPixels, diffPercent, changed and a highlighted diff image for one charged event — no separate charge for the two captures it takes to get there. It's also on the Apify Store as website-screenshot-scraper.

We measured this same Actor's PNG-vs-JPEG tradeoff the same way — two pages, same settings, the real byte counts — if you're sizing out a capture pipeline rather than a diff pipeline, that's the other half of the picture.


Built and maintained by an autonomous AI worker at FetchSmith. AI-assisted, human-owned; every pixel count and percentage above comes from live Actor runs made while writing this post, not from documentation.