Skip to Content
New: codec-png-native and codec-png-wasm bring the blazediff-png codec to Node and WebAssembly, faster than spng and jSquash. Read more β†’
DocsSimilarity

Structural Image Comparison

Pixel diffing answers β€œhow many pixels changed”. Structural metrics answer β€œhow different does this look”. They score a neighborhood rather than a pixel, so compression artifacts and sub-pixel rendering noise barely move them while a real change does.

Reach for one when exact matching is too strict: screenshots that get re-encoded somewhere in the pipeline, or the same page rendered by two different machines. Keep a pixel diff in the pipeline too, because both metrics work on luminance and are close to blind to color-only changes.

BlazeDiff ships two: GMSD, which compares edges and is the cheaper of the two, and SSIM, the classic structural index, with a faster Hitchhiker’s variant.

New to these? Read what SSIM measures, how GMSD works, or which one to pick.

Installation

npm install @blazediff/gmsd @blazediff/ssim

GMSD (Gradient Magnitude Similarity Deviation)

Scores gradient (edge) similarity. Returns 0 for identical images. Lower is better, typically in the 0 to 0.35 range.

Fixture A

Image 1

Fixture B

Image 2

Result

import gmsd from "@blazediff/gmsd"; const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const result = gmsd(img1, img2); // { kind: "compared", score }

Pass an output image in the options to get the similarity map back as a grayscale image, which shows which edges disagreed. GMSD reference.

SSIM (Structural Similarity Index)

Scores luminance, contrast and structure. Returns 1 for identical images. Higher is better, in the 0 to 1 range. The Hitchhiker’s variant swaps the Gaussian window for non-overlapping rectangular windows over integral images, which runs 2x to 5x faster depending on the image at near-identical accuracy.

Fixture A

Image 1

Fixture B

Image 2

SSIM Map

import ssim from "@blazediff/ssim/ssim"; const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const { width, height } = img1; const output = { data: new Uint8Array(width * height * 4), width, height }; const result = ssim(img1, img2, { output });

The output image here receives the SSIM map: dark areas are where the two images disagree. @blazediff/ssim also ships MS-SSIM, which scores at five scales for images that get viewed at more than one size. SSIM reference.

Picking a threshold

Do not copy a number from a table. Run your own baselines twice with no code change, see what score the noise alone produces, then set the gate above it. As starting points: 0.05 for GMSD when you control the render, 0.15 when screenshots pass through lossy compression, and 0.98 for SSIM on full-page shots.

Next

Last updated on