# pixelmatch: a dependency-light pixel diff for screenshot tests

> pixelmatch compares two images pixel by pixel, returns a mismatch count and can write a diff image. It is a library, not a test runner, and the README is explicit about what it does not do.

**mapbox/pixelmatch** — The smallest, simplest and fastest JavaScript pixel-level image comparison library

- Repository: https://github.com/mapbox/pixelmatch
- Stars: 6,968 · Forks: 329
- Language: JavaScript
- License: ISC
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/mapbox-pixelmatch

## What pixelmatch solves, and for whom

The README frames the origin plainly: the library was "originally created to compare screenshots in tests." That sentence defines the audience. If you capture a rendered page or a canvas twice and want a machine-readable answer to "did anything change", pixelmatch gives you that answer as a number and, optionally, as an image.

The scope is deliberately narrow. It does not drive a browser, does not store baselines and does not know what a test is. It receives two typed arrays of pixel data plus a width and height, and it returns the number of mismatched pixels. Everything around that call (capture, storage, reporting, pass or fail) belongs to your harness.

That narrowness is the reason the package has no dependencies in the library itself. The README states it is "just a few hundred lines of code" with "no dependencies", operating on "raw typed arrays". The npm manifest tells a slightly more nuanced story: pngjs appears under dependencies, but it is there for the CLI binary, not for the comparison function. The library entry point works on buffers you already hold.

## How the comparison actually runs

The API is one function with five positional arguments and an options object. The README documents the signature as pixelmatch(img1, img2, output, width, height[, options]). The first two arguments are the images to compare, output is where the diff image is written (or null if you do not want one), and width and height describe the images.

The constraint that bites first is stated twice in the README: image dimensions must be equal, and all three images need to have the same dimensions. There is no scaling, no alignment and no cropping. If your two captures differ by a scrollbar or a shifted layout, the comparison is meaningless before it starts.

The return value is the number of mismatched pixels. Internally the library works through the option set: threshold controls sensitivity, includeAA toggles anti-aliased pixel detection, and the color options control how the diff is painted. The README credits three sources for the method, including the OKLab color space for perceptual color difference and the OKLab HyAB metric for very large color differences, plus a 2009 anti-aliased pixel and intensity slope detector. Those citations matter because they explain why a pixel that differs only slightly in color may not be counted, and why anti-aliased edges are ignored by default.

The README also documents a second mode. With windowSize set to a finite N, the return value stops being a total and becomes "the maximum number of differing pixels in any N by N sliding window". The stated motivation is noise: GPU dithering and sub-pixel anti-aliasing scatter stray pixels, which never fill a small square, while a real regression changes a compact area. One detail is easy to miss: N never exceeds either image dimension, so on a 10 by 2 image, windowSize 32 yields a 2 by 2 window. The default is Infinity, which makes the window the whole image, which is the total count.

## Install and a first real comparison

Install from npm. The README gives the command as npm install pixelmatch. Note that the package is published as an ES module ("type": "module" in package.json), so the import syntax below is the expected one.

```bash
npm install pixelmatch
```

The README's Node example pairs pixelmatch with pngjs to read and write PNG files. The shape is: read both PNGs, take width and height from the first, allocate a diff PNG, call pixelmatch, then write the diff.

```js
import fs from 'fs';
import {PNG} from 'pngjs';
import pixelmatch from 'pixelmatch';

const img1 = PNG.sync.read(fs.readFileSync('img1.png'));
const img2 = PNG.sync.read(fs.readFileSync('img2.png'));
const {width, height} = img1;
const diff = new PNG({width, height});

pixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.1});

fs.writeFileSync('diff.png', PNG.sync.write(diff));
```

The README's one-line form shows the same call with the diff buffer passed directly and the option object inline, returning the count:

```js
const numDiffPixels = pixelmatch(img1, img2, diff, 800, 600, {threshold: 0.1});
```

If you would rather not write any JavaScript, the package ships a binary. The README documents it as working with PNG images and taking the two inputs, an output path and the threshold as positional arguments:

```bash
pixelmatch image1.png image2.png output.png 0.1
```

After any of these calls, the diff image uses red for differing pixels by default and yellow for anti-aliased pixels, blended over the original at an alpha of 0.1. If you only want the number, pass null as the output argument and skip the file write.

## Where pixelmatch is the wrong tool

The equal-dimensions rule is the hardest limitation. Any pipeline that produces differently sized captures has to normalise them before pixelmatch sees them, and the README offers no helper for that.

The second limitation is semantic. A mismatch count cannot tell you whether the change is a bug. A moved button, a new cookie banner and a genuine rendering regression all produce nonzero counts. pixelmatch narrows the noise with anti-aliased pixel detection and, if you opt in, windowed counts, but the judgement stays with you.

The third limitation is that the library is not a test framework. The README documents no baseline management, no image storage, no retry logic and no reporter. If you want "run this suite across browsers and fail the build", pixelmatch is one function inside a larger system you still have to build or buy.

Finally, the PNG path depends on pngjs. The library function takes typed arrays, but the CLI and the README's Node example both go through PNG decoding. If your images are in another format, that decoding is your problem, not the library's.

## Compared with Resemble.js and Blink-diff

The README names two inspirations directly: Resemble.js and Blink-diff. The stated difference is not accuracy but surface area. pixelmatch is described as a few hundred lines with no dependencies, working on raw typed arrays, so it can run in Node and in browsers.

That difference has practical consequences. Resemble.js and Blink-diff are built around higher-level workflows: they take images and produce reports, and they carry the machinery for that. pixelmatch gives you the comparison and the diff buffer, and nothing else. If you want a report, you write it. If you want to run the same comparison in a browser as part of a page-level check, the typed-array interface is what makes that possible, since the README's browser example reads two ImageData objects and writes a third via putImageData.

The trade-off is real in both directions. A library that does one thing will not guess what you meant when two images have different dimensions; a framework might crop or scale and hide the problem. With pixelmatch, that failure is loud.

## Maintenance, releases and the ISC licence

The repository is not archived, and the last push was on 2026-09-15. The most recent release listed is v7.2.0 on 2026-04-29, preceded by v7.1.1 on 2026-04-28 and v7.1.0 on 2025-02-21. The gap between v7.1.0 and v7.1.1 is over a year, which suggests a project that releases when there is something to release rather than on a schedule. The README links to the releases page as the changelog, so upgrade notes live there rather than in the repository.

On cost: the library has no runtime dependencies, so an upgrade does not pull a tree of transitive packages. The CLI's pngjs dependency is the exception. The test script runs eslint via a pretest hook, then tsc and node --test, which means type definitions are checked as part of the suite; index.d.ts is published in the files list, so TypeScript consumers get types from the package itself.

Licensing is ISC, a permissive licence. The practical implication is that you can use pixelmatch in closed-source products without a copyleft obligation, but this is a description of the licence identifier, not legal advice. If your organisation has a policy list of approved licences, ISC is normally on it, and the LICENSE file is at the repository root.

## Conclusion

Adopt pixelmatch when you already produce two images of equal dimensions and want a mismatch count plus a diff image inside your own test harness. Do not adopt it if you need cross-browser screenshot capture, baseline storage or a test runner; it supplies none of those. Before wiring it into CI, verify that your two inputs really share width and height, and pick a threshold and, if GPU noise is a problem, a windowSize value against your own fixtures.

## FAQ

### What is pixelmatch and what does it do?

pixelmatch is a JavaScript pixel-level image comparison library, originally created to compare screenshots in tests. It takes two images as raw typed arrays plus a width and height, writes an optional diff image, and returns the number of mismatched pixels.

### How do I install pixelmatch?

The README gives the npm command npm install pixelmatch. For browser use it also documents importing directly from a CDN with a module script pointing at https://esm.run/pixelmatch.

### What does the pixelmatch threshold option control?

The README describes threshold as the matching threshold, ranging from 0 to 1, where smaller values make the comparison more sensitive. Its default is 0.1.

### Is there a pixelmatch alternative I should consider?

The README names Resemble.js and Blink-diff as inspirations and contrasts itself with them: pixelmatch is a few hundred lines with no dependencies working on raw typed arrays, while those libraries are built around higher-level image comparison workflows.

### Why does pixelmatch report no differences when my images look different?

The README states that anti-aliased pixels are detected and ignored by default, and that perceptual color difference metrics are used for comparison. Setting includeAA to true disables the anti-aliased pixel handling, and lowering threshold makes the comparison more sensitive.

## Sources

- [Issues](https://github.com/mapbox/pixelmatch/issues)
- [License: ISC](https://github.com/mapbox/pixelmatch/blob/main/LICENSE)
- [mapbox/pixelmatch on GitHub](https://github.com/mapbox/pixelmatch)
- [README](https://github.com/mapbox/pixelmatch/blob/main/README.md)
- [Releases](https://github.com/mapbox/pixelmatch/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mapbox-pixelmatch
