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

How GMSD Works

GMSD (Gradient Magnitude Similarity Deviation) scores how different two images are by comparing their edges. It runs an edge filter over both images, measures how well the edge strengths agree at every pixel, then returns the standard deviation of those agreements. 0 means identical, and lower is better - the opposite direction from SSIM.

The idea behind it: what people notice in a broken render is distorted structure - shifted text, a missing border, a control that changed shape. Edges are where structure lives, so comparing edges catches those changes and ignores flat areas where nothing interesting happens.

The four steps

1. Downsample (optional)

Average each 2x2 block, then keep every second pixel. This halves each dimension and removes high-frequency noise before anything else runs. Off by default (downsample: 0).

aveKernel = [0.25 0.25] [0.25 0.25]

2. Find the edges with a Prewitt filter

Both images are converted to luminance and convolved with two 3x3 kernels, one for horizontal change and one for vertical:

dx = [ 1 0 -1] / 3 dy = [ 1 1 1] / 3 [ 1 0 -1] [ 0 0 0] [ 1 0 -1] [-1 -1 -1]

The gradient magnitude at each pixel is how strong the edge is there, in any direction:

gradient = sqrt(Ix^2 + Iy^2)

You now have two edge maps, one per image.

3. Compare the two edge maps

For every pixel, gradient magnitude similarity (GMS) compares the two edge strengths:

GMS = (2 * g1 * g2 + C) / (g1^2 + g2^2 + C)

C = 170 is a stability constant tuned for the Prewitt operator on 8-bit images. It stops the fraction from being noisy in flat regions where both gradients are near zero. GMS lands between 0 and 1, where 1 means the two images have the same edge strength at that pixel.

4. Take the standard deviation

GMSD = std(GMS)

This is the part that surprises people: GMSD reports the spread of the similarity map, not its average. That is deliberate. A page where one component is badly broken and everything else is fine has a high spread, and that is what a reviewer would flag. A mean would average that damage away against the thousands of pixels that are fine.

Reading the score

ScoreMeaning
0.00Identical
0.00-0.05Very low, likely artifacts
0.05-0.15Low but visible
0.15-0.35Moderate, clearly changed
> 0.35Large structural change

For visual regression, treat anything above 0.0 as worth looking at when you control the render, and raise the gate to about 0.15 if screenshots pass through lossy compression.

GMSD vs SSIM

GMSDSSIM
DirectionLower is better, 0 = identicalHigher is better, 1 = identical
Looks atEdge strengthBrightness, contrast, structure
AggregationStandard deviation of the mapMean of the map
CostOne pass, two 3x3 convolutionsWindowed statistics over the whole image
Strong atLayout and shape changesGeneral perceived quality
Blind toColor-only changes, uniform shifts in brightnessColor-only changes

They disagree in a useful way. GMSD reacts to a control that moved; SSIM reacts to a section that got blurrier. Running both is cheap and the pair is more honest than either alone.

Run it

npm install @blazediff/gmsd
import gmsd from "@blazediff/gmsd"; const score = gmsd(image1, image2, undefined, width, height); if (score > 0.05) throw new Error(`too different: ${score}`);

Pass an output buffer as the third argument to get the GMS map back as a grayscale image, which shows exactly which edges disagreed.

From the CLI:

blazediff-cli gmsd baseline.png current.png blazediff-cli gmsd baseline.png current.png --output gms-map.png

In a test:

await expect(screenshot).toMatchImageSnapshot({ method: "gmsd" });

Options: downsample (0 or 1, default 0) and c (default 170). Raising c makes the metric more forgiving in low-contrast areas. Full signature in the @blazediff/gmsd reference.

When GMSD is the wrong tool

  • Color-only changes. GMSD works on luminance gradients. Swapping a brand color for another of the same brightness barely registers.
  • You need to know where and what. One number will not tell you a button moved. Use interpret mode for regions and change types.
  • Exact matching is the requirement. For byte-level correctness, use pixel diffing.

Reference

Xue, W., Zhang, L., Mou, X., & Bovik, A. C. (2013). β€œGradient Magnitude Similarity Deviation: A Highly Efficient Perceptual Image Quality Index.” IEEE Transactions on Image Processing, 22(2), 684-695.

Next: What is SSIM β†’ Β· Choosing a metric β†’

Last updated on