Skip to Content
New: blazediff-png - a from-scratch Rust PNG codec, byte-exact to libspng and faster on every fixture. Read more β†’
DocsImage Difference Analysis

Image Difference Analysis

Takes a raw pixel diff and tells you what changed, where, and how much. No ML models - a deterministic pipeline that runs in the same binary as the diff itself. Available in both the native Node binding (@blazediff/core-native) and the WebAssembly build (@blazediff/core-wasm), so you can run it server-side or in the browser.

How it works

  1. Pixel diff β†’ binary change mask
  2. Morphological close β†’ bridge small gaps
  3. Connected components β†’ isolate regions
  4. Per-region evidence extraction:
    • Dual-image gradients - edges in both images + spatial correlation to detect structural preservation
    • Color delta distribution - mean, max, and stddev of YIQ distance to separate uniform recolors from patchy texture changes
    • Background distance - how much changed pixels blend with local unchanged pixels in each image
  5. Six-label decision tree classifies each region
  6. Post-hoc shift detection matches Addition+Deletion pairs

Demo

Image 1

Image 1

Image 2

Image 2

Moderate visual change detected (1.87% of image, 10 regions). Content changed: 3 regions (bottom, center). Content added: 3 regions (right, bottom, bottom-left). Content removed: 2 regions (bottom, center). Content shifted: 2 regions (bottom, top-left).

medium1.87% changed

Regions (10)Hover a region to highlight

bottomΒ·deletionΒ·mixed-regionΒ·(670, 977, 199Γ—89) Β· 0.58%
bottomΒ·content-changeΒ·sparse-distributedΒ·(558, 738, 193Γ—173) Β· 0.34%
bottomΒ·content-changeΒ·mixed-regionΒ·(726, 910, 208Γ—51) Β· 0.30%
centerΒ·content-changeΒ·sparse-distributedΒ·(368, 663, 323Γ—88) Β· 0.27%
bottomΒ·shiftΒ·sparse-distributedΒ·(432, 937, 50Γ—151) Β· 0.11%
rightΒ·additionΒ·sparse-distributedΒ·(1004, 626, 71Γ—130) Β· 0.08%
top-leftΒ·shiftΒ·edge-dominatedΒ·(343, 102, 39Γ—126) Β· 0.07%
centerΒ·deletionΒ·sparse-distributedΒ·(505, 717, 49Γ—88) Β· 0.05%
bottomΒ·additionΒ·sparse-distributedΒ·(694, 1085, 80Γ—33) Β· 0.04%
bottom-leftΒ·additionΒ·edge-dominatedΒ·(253, 1191, 89Γ—37) Β· 0.03%

Usage

Both bindings return the same result shape - summary, regions[] (with position, changeType, percentage, …), and severity. The native binding reads files; the WASM binding takes pre-decoded RGBA buffers.

import { interpret } from "@blazediff/core-native"; const result = await interpret("fixtures/3a.png", "fixtures/3b.png"); console.log(result.summary); for (const region of result.regions) { console.log(`${region.position}: ${region.changeType} (${region.percentage.toFixed(2)}%)`); }

Identical images

Image 1

Image 1

Image 2

Image 2

Images are identical

low0.00% changed

When nothing changed, regions is empty and summary reports no differences - identical for both bindings.

Change types

TypeMeaning
AdditionContent appeared - blends with background in before image, distinct in after
DeletionContent removed - distinct in before, blends with background in after
ShiftContent moved - matched Addition+Deletion pair with similar luminance
ColorChangeRecolor - edge structure preserved across both images, uniform color shift
ContentChangeStructural change - edges differ between images
RenderingNoiseSub-pixel artifacts - filtered from output

Accuracy

Measured against datasets with hand-labeled change regions (see crates/blazediff-interpret-verify/BENCHMARKS.mdΒ  for the full breakdown).

DatasetWhat it testsClassifier-only macro F1End-to-end macro F1
addition_deletionClean object insert / remove on photographs0.9980.888
shiftSub-region translations with pixel-perfect ground truth0.8130.628
inpaintcocoInpaint edits that mix recolor and texture replacement0.4400.260
html_color_pairsRecolors on rendered Tailwind UI screenshots0.9930.786

Read this as: on clear add/delete edits the classifier almost never picks the wrong label (0.998 means 4 mistakes in 904 regions). On synthetic shifts the post-pass pairs about two thirds of moved-block events with near-perfect precision. On real inpainted photos it lands the right label in about four of every nine regions - the ColorChange vs ContentChange boundary is the dominant confusion and the focus area for the next iteration. html_color_pairs isolates that same boundary on 100 rendered Tailwind UI screenshots that differ only in color classes: a dedicated chromatic-recolor branch admits same-luminance hue swaps that YIQ otherwise scores as low-delta, lifting classifier F1 to 0.993 with zero false positives.

End-to-end runs the full detector pipeline before classifying, so the score also reflects detection misses and spurious small regions; classifier-only isolates labeling quality from detection.

Last updated on