A C++ port of pixelmatch, the smallest, simplest and fastest JavaScript pixel-level image comparison library. It tracks the JS version: pixelmatch-cpp 8.x matches pixelmatch 8.x exactly, giving the same counts and the same diff images.
This is a header-only library; add include/ to your include path, use it as a CMake subdirectory, or install it and link against mapbox::pixelmatch.
mapbox::Options options;
options.threshold = 0.05;
uint64_t numDiffPixels = mapbox::pixelmatch(img1, img2, width, height, diff.data(), options);Requires CMake 3.15+ and a C++17 compiler. Tests and the benchmark need libpng.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
./build/bench_pixelmatchTo generate an Xcode project: cmake -B build -G Xcode && open build/pixelmatch.xcodeproj.
namespace mapbox {
struct Color {
uint8_t r, g, b;
};
struct Options {
double threshold = 0.1;
bool includeAA = false;
double alpha = 0.1;
Color aaColor = {255, 255, 0};
Color diffColor = {255, 0, 0};
std::optional<Color> diffColorAlt;
bool diffMask = false;
bool checkerboard = true;
const uint8_t* ignoreMask = nullptr;
std::size_t windowSize = 0;
};
uint64_t pixelmatch(
const uint8_t* img1,
const uint8_t* img2,
std::size_t width,
std::size_t height,
uint8_t* output = nullptr,
const Options& options = {}
);
uint64_t pixelmatch(
const uint8_t* img1, std::size_t stride1,
const uint8_t* img2, std::size_t stride2,
std::size_t width,
std::size_t height,
uint8_t* output = nullptr,
const Options& options = {}
);
}img1 and img2 are RGBA images, and they must have the same dimensions. In the first overload, each must point to a buffer of size width * height * 4. The second overload takes each image's row stride in bytes (at least width * 4), for inputs with padded rows. The function returns the number of mismatched pixels.
If output is non-null, the diff image is written to it. It must point to a tightly packed width * height * 4 buffer, whatever the input strides.
pixelmatch is thread-safe and keeps no state between calls.
Options:
threshold— How different two colors must be for a pixel to count as mismatched, from0to1. It's a per-pixel perceptual color difference, where1is the difference between black and white, not a share of the image; to allow some percentage of the image to differ, compare the returned count againstwidth * heightinstead. Smaller values make the comparison more sensitive.includeAA— Iftrue, disables detecting and ignoring anti-aliased pixels.alpha— Blending factor of unchanged pixels in the diff output. Ranges from0for pure white to1for original brightness.aaColor— The color of anti-aliased pixels in the diff output.diffColor— The color of differing pixels in the diff output.diffColorAlt— An alternative color for dark-on-light differences, to tell "added" parts from "removed" ones. If not set, all differing pixels usediffColor.diffMask— Draw the diff over a transparent background (a mask), rather than over the original image. Only differing pixels are written tooutput; anti-aliased pixels aren't drawn.checkerboard— Blend semi-transparent pixels against a checkerboard pattern when comparing (true) rather than plain white (false), avoiding false matches between colors that only look alike over one background.ignoreMask— One byte per pixel, a tightly packedwidth * heightbuffer whatever the input strides. Pixels with a non-zero value are excluded from the comparison and drawn as unchanged in the diff output.windowSize— If non-zero, return the maximum number of differing pixels in anyN×Nsliding window instead of the total count (see below).
Normally the return value is the total number of differing pixels. With windowSize = N you get the highest number of diff pixels in any N×N square instead. Anti-aliased pixels are only included if includeAA is true.
This helps with noise. GPU dithering and sub-pixel anti-aliasing scatter stray pixels all over the image, so they never fill up one small square, while a real regression usually changes a compact area.
Compare the result against a pixel count: that number is how big a difference you let through, and N is how closely packed it has to be.
mapbox::Options options;
options.windowSize = 16;
if (mapbox::pixelmatch(img1, img2, width, height, nullptr, options) > 28) { /* changed */ }N never exceeds either image dimension, so on a 10×2 image windowSize = 32 gives you a 2×2 window. Unlike in JS, where the default is Infinity and 0 means a 1×1 window, here 0 (the default) means windowing is off and you get the total count.
- Color differences now use the OKLab HyAB metric, like pixelmatch 8, so mismatch counts change.
thresholdkeeps its range and default (0.1), but you may want to re-tune it. - Semi-transparent pixels are blended against a checkerboard by default; set
checkerboard = falseto blend against white as before. - The positional
thresholdandincludeAAarguments are replaced by anOptionsstruct:pixelmatch(img1, img2, w, h, out, 0.05)becomespixelmatch(img1, img2, w, h, out, {0.05}). - C++17 is now required.