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

Browser and Edge Image Diffing

@blazediff/core-wasm is a ~32KB WebAssembly build of the same Rust diff core, compiled to wasm32 with v128 SIMD. It runs in browsers, Web Workers, Deno, Bun, Cloudflare Workers, and any other wasm host, with no native dependency and no network call. On a 4K pair it is about 90% faster than pixelmatch.

Measured speed

From the pixel benchmarks, M1 Max, image IO excluded, ~50.8% faster than pixelmatch on average across the fixture set:

Fixturepixelmatchcore-wasmImprovement
4k/1332.26ms33.18ms90.0%
4k/2333.33ms68.37ms79.5%
4k/3423.14ms44.65ms89.4%
page/2513.06ms73.42ms85.7%

Counts agree with pixelmatch to within about 0.05% - both use a YIQ-style perceptual delta, so they classify the same pixels apart from a handful of edge cases.

This is CPU SIMD (v128), not GPU acceleration. There is no WebGPU path. The speed comes from 128-bit vector compares and a block-based scan that skips unchanged regions, so it needs a host with SIMD enabled - every current browser and edge runtime qualifies.

You decode, it compares

The API takes pre-decoded RGBA buffers. It does not ship a PNG decoder, which is most of why the module is 32KB. In a browser, the platform already has one:

async function toRGBA(blob: Blob) { const bitmap = await createImageBitmap(blob); const canvas = new OffscreenCanvas(bitmap.width, bitmap.height); const ctx = canvas.getContext("2d")!; ctx.drawImage(bitmap, 0, 0); const { data } = ctx.getImageData(0, 0, bitmap.width, bitmap.height); return { data: new Uint8Array(data.buffer), width: bitmap.width, height: bitmap.height }; } const a = await toRGBA(baselineBlob); const b = await toRGBA(currentBlob); const output = new Uint8Array(a.data.length); const changed = diff(a.data, b.data, output, a.width, a.height, { threshold: 0.1, });

getImageData is the slow part of that snippet, not the diff. The ImageDecoder API avoids the canvas round-trip where it is available.

Full setup in Rust + WASM in JavaScript β†’.

Runtime support

RuntimePackageNotes
Browser main thread@blazediff/core-wasmBlocks the thread. Prefer a Worker
Web Worker@blazediff/core-wasmRecommended for anything above 1080p
Cloudflare Workers@blazediff/core-wasmNo filesystem, pass buffers
Deno@blazediff/core-wasmAlso on JSR
Bun@blazediff/core-wasmnpm or JSR
Node server@blazediff/core-nativeFaster still, handles decode itself
Pure JS, no wasm@blazediff/coreDrop-in for pixelmatch

On the main thread a 4K diff is 30-70ms, which is several dropped frames. Put it in a Worker and post the result back.

Edge constraints worth knowing

  • Bundle size. ~32KB of wasm fits comfortably inside a Workers bundle.
  • No filesystem. Everything is buffers. That is already the API.
  • CPU time limits. A 4K diff at 33-68ms is fine for most edge budgets. Diffing a batch in one request is not - fan out instead.
  • Cold start. The module instantiates once per isolate. Instantiate at module scope, not per request.
  • Memory. Decoded RGBA is width x height x 4. A 4K pair plus an output buffer is around 100MB, which exceeds some edge memory limits. Skip the output buffer when you only need the count.

The rest of the ecosystem

Same algorithm, different hosts:

Package / crateRuntimeRegistry
@blazediff/coreNode, browsernpm, JSR
@blazediff/core-wasmBrowser, edge, any wasm hostnpm, JSR
@blazediff/core-nativeNode, Bunnpm
blazediff (crate)Rustcrates.io
blazediff (PyPI)PythonPyPI
@blazediff/ssim, @blazediff/gmsdNode, browsernpm, JSR
@blazediff/react, @blazediff/uiBrowsernpm

All MIT licensed. The JS packages are dual-published to npm and JSR, so Deno and Bun can resolve them natively.

Showing the result

@blazediff/ui is a framework-agnostic renderer for image-diff views and @blazediff/react wraps it for React: <SwipeMode /> for a drag slider, <TwoUpMode /> for side by side, plus <OnionSkinMode /> and <DifferenceMode />. Useful when the client-side diff needs to be shown, not just counted.

React components β†’ Β· Vanilla β†’

Next

Last updated on