BlazeDiff vs pixelmatch
@blazediff/core runs the pixelmatch algorithm with the pixelmatch call
signature, and it is ~3.2x faster
over our 34 fixtures. The WebAssembly build is ~4.3x faster.
Switching is a one-line import change.
pixelmatch is the reference pixel diff for JavaScript: a few hundred lines, anti-aliasing detection, and a YIQ color delta. BlazeDiff started as a faster implementation of the same algorithm. It skips identical blocks of pixels before it runs the per-pixel delta, so unchanged areas cost almost nothing.
At a glance
| pixelmatch | BlazeDiff | |
|---|---|---|
| License | ISC | MIT |
| Implementations | JavaScript (plus a C++ port) | JavaScript, WebAssembly, native Node, Python, Rust |
| Input | Raw RGBA buffers | Raw RGBA (core, core-wasm), or file paths and encoded bytes (core-native) |
| Decodes images itself | No (CLI reads PNG through pngjs) | core-native and Python decode PNG, JPEG and QOI |
| Color metric | YIQ in 7.x, OKLab + HyAB in 8.0 | YIQ |
| Anti-aliasing | Detected (VyΕ‘niauskas 2009) | Detected, same method |
| Output | Diff pixel count, diff image | Diff pixel count, diff image |
| Beyond pixel counts | No | SSIM, GMSD, MILO scores and region analysis in sibling packages |
Benchmarks
Decode is excluded for the JavaScript and WebAssembly charts, so they measure the diff kernel only. The charts use pixelmatch 7.2.0.
JavaScript (@blazediff/core vs pixelmatch) (image IO excluded)
TOTAL TIME Β· 34 FIXTURES Β· MEAN OF 50 RUNS Β· IO EXCLUDED
Apple M1 Max Β· darwin/arm64 Β· Node v22.14.0 Β· rustc 1.97.1 (8bab26f4f 2026-07-14) Β· 2026-10-22 Β· 234b300-dirty Β· pnpm benchmark core
Per-fixture results (34)
| Fixture | Pixelmatch | BlazeDiff | BlazeDiff vs Pixelmatch |
|---|---|---|---|
| 4k/1 | 325ms | 95.14ms | 3.42x |
| 4k/1 (identical) | 23.07ms | 2.86ms | 8.07x |
| 4k/2 | 326ms | 104ms | 3.12x |
| 4k/2 (identical) | 23.41ms | 3.48ms | 6.72x |
| 4k/3 | 411ms | 126ms | 3.26x |
| 4k/3 (identical) | 34.26ms | 4.31ms | 7.94x |
| blazediff/1 | 1.24ms | 0.69ms | 1.79x |
| blazediff/1 (identical) | 0.49ms | 0.06ms | 8.71x |
| blazediff/2 | 1.79ms | 0.94ms | 1.90x |
| blazediff/2 (identical) | 0.40ms | 0.06ms | 6.40x |
| blazediff/3 | 10.52ms | 7.51ms | 1.40x |
| blazediff/3 (identical) | 1.86ms | 0.23ms | 8.22x |
| blazediff/4 | 13.96ms | 7.36ms | 1.90x |
| blazediff/4 (identical) | 4.39ms | 0.65ms | 6.76x |
| page/1 | 161ms | 94.51ms | 1.70x |
| page/1 (identical) | 69.46ms | 10.40ms | 6.68x |
| page/2 | 524ms | 152ms | 3.44x |
| page/2 (identical) | 48.55ms | 6.87ms | 7.07x |
| pixelmatch/1 | 0.58ms | 0.31ms | 1.91x |
| pixelmatch/1 (identical) | 0.27ms | 0.02ms | 16.17x |
| pixelmatch/2 | 2.19ms | 1.61ms | 1.36x |
| pixelmatch/2 (identical) | 0.07ms | 0.01ms | 8.90x |
| pixelmatch/3 | 0.37ms | 0.23ms | 1.63x |
| pixelmatch/3 (identical) | 0.15ms | 0.02ms | 9.50x |
| pixelmatch/4 | 4.24ms | 2.88ms | 1.47x |
| pixelmatch/4 (identical) | 0.21ms | 0.03ms | 8.12x |
| pixelmatch/5 | 0.18ms | 0.12ms | 1.58x |
| pixelmatch/5 (identical) | 0.07ms | 0.01ms | 8.01x |
| pixelmatch/6 | 0.80ms | 0.57ms | 1.40x |
| pixelmatch/6 (identical) | 0.07ms | 0.01ms | 7.88x |
| pixelmatch/7 | 1.26ms | 0.81ms | 1.55x |
| pixelmatch/7 (identical) | 0.28ms | 0.03ms | 9.40x |
| same/1 | 3.16ms | 0.24ms | 13.26x |
| same/1 (identical) | 3.40ms | 0.24ms | 14.17x |
| TOTAL | 2.00s | 625ms | 3.19x |
JavaScript with output buffer (@blazediff/core vs pixelmatch) (image IO excluded)
TOTAL TIME Β· 34 FIXTURES Β· MEAN OF 50 RUNS Β· IO EXCLUDED
Apple M1 Max Β· darwin/arm64 Β· Node v22.14.0 Β· rustc 1.97.1 (8bab26f4f 2026-07-14) Β· 2026-10-22 Β· 234b300-dirty Β· pnpm benchmark core
Per-fixture results (34)
| Fixture | Pixelmatch | BlazeDiff | BlazeDiff vs Pixelmatch |
|---|---|---|---|
| 4k/1 | 415ms | 142ms | 2.92x |
| 4k/1 (identical) | 76.37ms | 48.86ms | 1.56x |
| 4k/2 | 382ms | 162ms | 2.37x |
| 4k/2 (identical) | 84.56ms | 58.27ms | 1.45x |
| 4k/3 | 493ms | 190ms | 2.59x |
| 4k/3 (identical) | 99.22ms | 66.46ms | 1.49x |
| blazediff/1 | 2.63ms | 1.97ms | 1.33x |
| blazediff/1 (identical) | 1.74ms | 1.26ms | 1.39x |
| blazediff/2 | 2.96ms | 1.95ms | 1.52x |
| blazediff/2 (identical) | 1.42ms | 0.98ms | 1.46x |
| blazediff/3 | 19.56ms | 12.32ms | 1.59x |
| blazediff/3 (identical) | 6.69ms | 4.48ms | 1.49x |
| blazediff/4 | 25.41ms | 18.26ms | 1.39x |
| blazediff/4 (identical) | 15.22ms | 10.37ms | 1.47x |
| page/1 | 351ms | 267ms | 1.31x |
| page/1 (identical) | 242ms | 163ms | 1.49x |
| page/2 | 642ms | 270ms | 2.38x |
| page/2 (identical) | 189ms | 114ms | 1.65x |
| pixelmatch/1 | 1.64ms | 0.68ms | 2.42x |
| pixelmatch/1 (identical) | 0.72ms | 0.35ms | 2.05x |
| pixelmatch/2 | 2.36ms | 1.95ms | 1.21x |
| pixelmatch/2 (identical) | 0.28ms | 0.18ms | 1.57x |
| pixelmatch/3 | 0.79ms | 0.63ms | 1.26x |
| pixelmatch/3 (identical) | 0.52ms | 0.35ms | 1.49x |
| pixelmatch/4 | 4.73ms | 3.43ms | 1.38x |
| pixelmatch/4 (identical) | 0.72ms | 0.49ms | 1.48x |
| pixelmatch/5 | 0.39ms | 0.32ms | 1.25x |
| pixelmatch/5 (identical) | 0.27ms | 0.18ms | 1.48x |
| pixelmatch/6 | 1.01ms | 0.84ms | 1.21x |
| pixelmatch/6 (identical) | 0.27ms | 0.18ms | 1.50x |
| pixelmatch/7 | 2.02ms | 1.54ms | 1.31x |
| pixelmatch/7 (identical) | 1.01ms | 0.67ms | 1.50x |
| same/1 | 7.27ms | 4.72ms | 1.54x |
| same/1 (identical) | 7.26ms | 4.79ms | 1.52x |
| TOTAL | 3.08s | 1.55s | 1.98x |
WebAssembly (@blazediff/core-wasm vs pixelmatch) (image IO excluded)
TOTAL TIME Β· 34 FIXTURES Β· MEAN OF 25 RUNS Β· IO EXCLUDED
Apple M1 Max Β· darwin/arm64 Β· Node v22.14.0 Β· rustc 1.97.1 (8bab26f4f 2026-07-14) Β· 2026-10-22 Β· 234b300-dirty Β· pnpm benchmark core-wasm
Per-fixture results (34)
| Fixture | Pixelmatch | BlazeDiff (core-wasm) | BlazeDiff (core-wasm) vs Pixelmatch |
|---|---|---|---|
| 4k/1 | 325ms | 35.95ms | 9.04x |
| 4k/1 (identical) | 20.79ms | 16.16ms | 1.29x |
| 4k/2 | 321ms | 66.39ms | 4.83x |
| 4k/2 (identical) | 23.47ms | 24.79ms | 0.95x |
| 4k/3 | 406ms | 44.62ms | 9.09x |
| 4k/3 (identical) | 28.54ms | 20.69ms | 1.38x |
| blazediff/1 | 1.26ms | 0.27ms | 4.74x |
| blazediff/1 (identical) | 0.50ms | 0.22ms | 2.24x |
| blazediff/2 | 1.66ms | 0.50ms | 3.35x |
| blazediff/2 (identical) | 0.42ms | 0.17ms | 2.39x |
| blazediff/3 | 10.44ms | 6.25ms | 1.67x |
| blazediff/3 (identical) | 1.91ms | 0.94ms | 2.04x |
| blazediff/4 | 12.32ms | 4.38ms | 2.81x |
| blazediff/4 (identical) | 4.53ms | 2.43ms | 1.87x |
| page/1 | 151ms | 50.48ms | 2.99x |
| page/1 (identical) | 67.52ms | 63.82ms | 1.06x |
| page/2 | 519ms | 75.10ms | 6.91x |
| page/2 (identical) | 49.46ms | 37.86ms | 1.31x |
| pixelmatch/1 | 0.58ms | 0.11ms | 5.09x |
| pixelmatch/1 (identical) | 0.15ms | 0.07ms | 2.14x |
| pixelmatch/2 | 2.28ms | 1.47ms | 1.55x |
| pixelmatch/2 (identical) | 0.08ms | 0.03ms | 2.43x |
| pixelmatch/3 | 0.38ms | 0.12ms | 3.25x |
| pixelmatch/3 (identical) | 0.15ms | 0.07ms | 2.15x |
| pixelmatch/4 | 4.07ms | 2.96ms | 1.38x |
| pixelmatch/4 (identical) | 0.21ms | 0.10ms | 2.05x |
| pixelmatch/5 | 0.19ms | 0.06ms | 3.07x |
| pixelmatch/5 (identical) | 0.08ms | 0.03ms | 2.35x |
| pixelmatch/6 | 0.75ms | 0.65ms | 1.14x |
| pixelmatch/6 (identical) | 0.08ms | 0.03ms | 2.37x |
| pixelmatch/7 | 1.23ms | 0.60ms | 2.07x |
| pixelmatch/7 (identical) | 0.29ms | 0.13ms | 2.27x |
| same/1 | 2.15ms | 1.16ms | 1.85x |
| same/1 (identical) | 1.98ms | 0.96ms | 2.07x |
| TOTAL | 1.96s | 460ms | 4.26x |
Switching
The arguments, the options and the return value are the same as pixelmatch 7.x.
// Before
import pixelmatch from "pixelmatch";
// After
import pixelmatch from "@blazediff/core";
const diffCount = pixelmatch(img1, img2, diff, width, height, { threshold: 0.1 });If your images come from files, @blazediff/core-native also decodes them, so the
pngjs step goes away:
import { compare } from "@blazediff/core-native";
const result = await compare("baseline.png", "current.png", "diff.png", {
threshold: 0.1,
antialiasing: true,
});pixelmatch 8.0 changed its color metric to OKLab + HyAB, so its diff counts differ from 7.x. BlazeDiff matches the 7.x counts. If you upgraded to 8.0 and depend on its exact counts, re-baseline your thresholds after you switch.
Python
The PyPI pixelmatch package is a pure Python port by a different author. The
blazediff wheel runs the Rust core and decodes the files itself. This chart
includes image decode for both sides.
vs pixelmatch (pypi)
TOTAL TIME Β· 34 FIXTURES Β· MEAN OF 10 OR 25 RUNS Β· IO INCLUDED
Apple M1 Max Β· darwin/arm64 Β· Node v22.14.0 Β· rustc 1.97.1 (8bab26f4f 2026-07-14) Β· Python 3.11.14 Β· 2026-10-22 Β· 234b300-dirty Β· pnpm benchmark python-pixelmatch
Per-fixture results (34)
| Fixture | pixelmatch (pypi) | BlazeDiff | BlazeDiff vs pixelmatch (pypi) |
|---|---|---|---|
| 4k/1 | 20.20s | 216ms | 93.42x |
| 4k/1 (identical) | 596ms | 192ms | 3.10x |
| 4k/2 | 23.28s | 265ms | 87.76x |
| 4k/2 (identical) | 734ms | 232ms | 3.16x |
| 4k/3 | 26.30s | 273ms | 96.42x |
| 4k/3 (identical) | 792ms | 241ms | 3.29x |
| blazediff/1 | 261ms | 0.86ms | 305.75x |
| blazediff/1 (identical) | 4.18ms | 0.99ms | 4.22x |
| blazediff/2 | 271ms | 1.00ms | 269.88x |
| blazediff/2 (identical) | 3.81ms | 0.95ms | 4.01x |
| blazediff/3 | 1.50s | 14.68ms | 102.40x |
| blazediff/3 (identical) | 46.32ms | 14.38ms | 3.22x |
| blazediff/4 | 2.48s | 5.17ms | 478.82x |
| blazediff/4 (identical) | 28.76ms | 4.94ms | 5.82x |
| page/1 | 40.25s | 268ms | 150.13x |
| page/1 (identical) | 872ms | 268ms | 3.25x |
| page/2 | 42.52s | 97.96ms | 434.01x |
| page/2 (identical) | 179ms | 55.83ms | 3.20x |
| pixelmatch/1 | 88.66ms | 1.15ms | 77.15x |
| pixelmatch/1 (identical) | 2.86ms | 1.09ms | 2.62x |
| pixelmatch/2 | 179ms | 0.69ms | 258.08x |
| pixelmatch/2 (identical) | 0.89ms | 0.45ms | 1.99x |
| pixelmatch/3 | 86.57ms | 0.94ms | 91.86x |
| pixelmatch/3 (identical) | 2.25ms | 0.83ms | 2.71x |
| pixelmatch/4 | 377ms | 2.66ms | 141.55x |
| pixelmatch/4 (identical) | 5.69ms | 1.98ms | 2.87x |
| pixelmatch/5 | 44.24ms | 0.56ms | 79.23x |
| pixelmatch/5 (identical) | 1.13ms | 0.60ms | 1.89x |
| pixelmatch/6 | 109ms | 1.27ms | 85.25x |
| pixelmatch/6 (identical) | 1.59ms | 0.79ms | 2.00x |
| pixelmatch/7 | 193ms | 0.72ms | 267.35x |
| pixelmatch/7 (identical) | 1.53ms | 0.63ms | 2.44x |
| same/1 | 23.42ms | 3.52ms | 6.65x |
| same/1 (identical) | 25.54ms | 3.14ms | 8.13x |
| TOTAL | 161.46s | 2.17s | 74.30x |
from blazediff import compare
result = compare("baseline.png", "current.png", "diff.png", threshold=0.1)When to keep pixelmatch
- You need the pixelmatch 8.0 features:
windowSizenoise filtering orignoreMask. - You want the smallest possible dependency and diff speed does not matter.
- Your project depends on pixelmatch 8.0 diff counts exactly.