Library / SDK
dmtrKovalenko/odiff avatar
dmtrKovalenko/odiff

Odiff: a SIMD-first image comparison library for pixel-perfect screenshot diffs

A very fast SIMD-first image comparison library (with nodejs API)

3,223 stars115 forksZigMIT

At a glance

What is it?
Odiff compares two images pixel by pixel in Zig with SIMD paths for SSE2, AVX2, AVX512 and NEON, and ships a Node.js binding, a CLI and a long-lived server mode. It is a diff engine, not a visual regression service, and the README leaves several operational questions open.
Who is it for?
Adopt Odiff when you already capture screenshots and want a fast, per-pixel comparison step underneath your own test runner or service, especially if you need cross-format comparison or a long-lived process to avoid per-call startup cost.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly Zig, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Odiff actually solves, and for whom

Odiff answers one narrow question: are these two images visually the same, and if not, which pixels differ? The README positions it for "significantly similar images like screenshots, photos, AI-generated images and many more", which is the honest scope. It is not a general image-diffing tool for unrelated pictures; it is built for the case where most of the frame is identical and a small region changed.

The audience is engineers who already have a screenshot pipeline. If you run Playwright or Cypress tests and need to decide whether a rendered page changed, Odiff is the comparison step. If you operate a visual testing service and need a comparison engine behind your own storage and UI, Odiff is that engine. The repository's own list of consumers reflects this: Argos, LostPixel, Visual Regression Tracker and OSnap all use Odiff for comparison rather than exposing it as a product.

That distinction matters when you evaluate it. Odiff gives you a boolean, a reason string, a diff count and a diff percentage. It does not store baselines, it does not review changes, and it does not tell you whether a difference is acceptable. Everything above the pixel comparison is yours to build.

How the comparison works: SIMD kernels, YIQ thresholding and layout handling

The project was originally written in OCaml and is now in Zig, with SIMD optimizations for SSE2, AVX2, AVX512 and NEON. That is the core architectural fact: the pixel loop is compiled into vector kernels per instruction set rather than relying on a scalar loop or a runtime library.

Difference is not raw channel subtraction. The README states Odiff uses the YIQ NTSC transmission algorithm to determine visual difference, and exposes a color difference threshold through the API options. YIQ separates luminance from chrominance, which is a reasonable model for perceived difference in screenshots, but it also means the threshold is a perceptual knob, not a numeric pixel delta. Two images can differ in RGB values and still return match: true if the difference falls under the threshold.

Cross-format comparison is supported directly: .png, .jpeg, .jpg, .webp and .tiff, and the README notes that comparing a .jpg against a .png works without special handling. Diff output, when requested, is always .png. The library also handles images with different layouts, and failOnLayoutDiff lets you turn that from a tolerated case into a failure. Anti-aliasing detection and region ignoring are listed as features, which is the usual escape hatch for text rendering that shifts by a subpixel between runs.

Installing odiff-bin and running a first comparison

The Node.js binding is published as odiff-bin. The README shows the CommonJS require form, and compare() is async, returning an object with match and reason. The third argument is the optional diff output path.

js
const { compare } = require("odiff-bin");

const { match, reason } = await compare(
  "path/to/first/image.png",
  "path/to/second/image.png",
  "path/to/diff.png",
);

After this runs, match is a boolean and reason explains a negative result, for example pixel-diff. The diff image is written only if you pass the third argument.

For the binary itself, the README gives a single command form. Image paths can be any supported format; the diff path is optional and must be .png. If you omit the diff path, odiff prints the generated diff directly in terminals that support the kitty graphics protocol, which the README lists as Ghostty, iTerm2, kitty, WezTerm, Warp, wayst, xterm.js and Konsole.

bash
odiff <IMG1 path> <IMG2 path> [DIFF output path]

The README does not document installation commands for the standalone binary, so the practical route for Node.js users is the odiff-bin package, and the repository's package.json shows the monorepo workspaces layout under npm_packages/*. Note the prepare script copies a locally built ./zig-out/bin/odiff into ./npm_packages/odiff-bin/bin/odiff.exe, which tells you the expected artifact name but not the supported platforms.

Server mode: the part that matters for high-volume test suites

Spawning a process per comparison is the obvious cost in a visual test suite with hundreds of screenshots. Odiff's server mode exists to remove that cost: the README describes it as a way "to reduce a time you have to spend on forking and initializing child process on each odiff call".

From Node.js, ODiffServer is constructed once. The README says it is safe to run from module scope because it lazy loads and cleans up on exits and SIGINTs.

js
const { ODiffServer } = require("odiff-bin");

const odiffServer = new ODiffServer();

const { match, reason } = await odiffServer.compare(
  "path/to/first/image.png",
  "path/to/second/image.png",
  "path/to/diff.png",
);

The underlying protocol is a readline stdio channel carrying JSON messages, started with odiff --server. The server emits a ready signal, then accepts requests keyed by requestId and answers with match, reason, diffCount and diffPercentage.

sh
odiff --server

The README's example response shows a mismatch with diffCount 1090946 and diffPercentage 2.95. That response shape is the useful part for a test harness: diffPercentage gives you a tolerance knob that match alone does not. The README does not document concurrency limits, timeouts or what happens if two requests share a requestId, so treat the protocol as something to wrap carefully.

Where Odiff is the wrong tool

The first limitation is conceptual. Odiff is a comparison engine with no baseline management. If you want a screenshot diff workflow, you still need to decide where reference images live, when they are updated, and who approves a change. The README points at Argos, LostPixel, Visual Regression Tracker and OSnap for that layer, which is an implicit admission that Odiff does not cover it.

The second is the threshold model. Because difference is judged through YIQ with a color difference threshold, the output is not a strict pixel equality check. Teams that need bit-exact comparison, for example verifying a lossless image encoder's output, are using the wrong tool: the anti-aliasing detection and perceptual threshold will classify genuinely different bytes as a match.

The third is operational. The README documents options such as noFailOnFsErrors, which returns { match: false, reason: '...' } instead of throwing when a file is missing. That is convenient in a test runner and dangerous in a pipeline that treats match: false as a real regression, because a missing baseline and a changed page become indistinguishable unless you inspect reason. There is also no documented rollback or versioning story for diff outputs; the repository layout shows stray files such as diff_output.png and test_output.png at the top level, and the README is silent on cleanup.

Odiff against Pixelmatch and against hosted services

The natural comparison is Pixelmatch, the JavaScript pixel-level diff library that most Node.js visual testing setups used before Odiff existed. The difference in approach is where the work happens. Pixelmatch runs in JavaScript, decoding images in-process and comparing them in the same runtime as your tests. Odiff is native code compiled to SIMD kernels in Zig, invoked either as a subprocess or through a Node.js binding, with a server mode to amortize process startup. The practical consequence is that Odiff moves CPU cost out of the JS event loop, which matters when a test suite compares many large screenshots, and adds a native binary to your install and deployment.

The second comparison is against the services built on top of it. Argos, LostPixel and Visual Regression Tracker are not alternatives to Odiff's engine; they are alternatives to building your own harness. They store baselines, render diffs for review and integrate with CI. If your question is "how do I review visual changes with my team", Odiff alone does not answer it. If your question is "how do I compare two images quickly inside a system I already control", the services are a layer of overhead you may not want.

For Playwright users there is a middle path: the playwright-odiff package registers a toHaveScreenshotOdiff matcher, so the comparison integrates with Playwright's own snapshot conventions while Odiff does the pixel work.

Licence, maintenance and upgrade cost

Odiff is MIT licensed, and the root package.json declares the same for the monorepo. MIT imposes no copyleft obligation on your own code, but the usual caveat applies: the repository includes image assets such as the demo tiger, donkey and cypress screenshots, and the README does not state their provenance. If you redistribute the repository contents rather than just depending on the published package, check those separately. This is not legal advice.

On maintenance, the facts are concrete: the repository is not archived, and the last push was on 2026-08-24. Recent releases include v4.5.0 on 2026-07-23, v4.4.4 on 2026-07-22 and a dev tag v4.4.3-dev.2 the day before. The README claims backward compatibility and 100% test coverage, and the presence of dev-tagged releases suggests a pre-release channel exists.

Upgrade cost is dominated by the native artifact. The prepare script copies a built binary into npm_packages/odiff-bin/bin/odiff.exe, and the Docker test script pins mcr.microsoft.com/playwright:v1.61.1-noble. That pin is the strongest signal in the repository about the environment the maintainers test against. If you run on a different platform or a different Playwright base image, verify the binding loads before you upgrade, because the README does not publish a platform support matrix.

Editorial conclusion

Adopt Odiff when you already capture screenshots and want a fast, per-pixel comparison step underneath your own test runner or service, especially if you need cross-format comparison or a long-lived process to avoid per-call startup cost. Do not adopt it as a complete visual regression platform: it has no baseline storage, no review UI and no hosting model, so teams expecting that workflow should look at Argos, LostPixel or Visual Regression Tracker, which use Odiff as their comparison engine. Before committing, verify three things on your own images: how the YIQ-based threshold behaves on your anti-aliased text, whether failOnLayoutDiff matches how your screenshots shift, and whether the Node.js binding loads on your target platform, since the README does not document a per-platform install matrix.

Frequently asked questions

What is the best software for comparing images?

There is no single answer, but for near-identical images such as screenshots, Odiff is built for exactly that case: it compares two images pixel by pixel and reports match, reason, diffCount and diffPercentage. For a full workflow with baseline storage and review, the README points to services such as Argos, LostPixel and Visual Regression Tracker that use Odiff as their comparison engine.

How can I identify differences between two images?

Run odiff with the two image paths and an optional diff output path, for example odiff <IMG1 path> <IMG2 path> [DIFF output path]. The diff output is always written as .png, and if you omit the path the diff is printed directly in terminals that support the kitty graphics protocol.

Can AI compare two images?

Odiff is not an AI model. It is a deterministic pixel comparison tool written in Zig with SIMD optimizations, using the YIQ NTSC transmission algorithm to judge visual difference. The README does note that it is designed for near-identical images including AI-generated ones.

How do I compare two photos together?

Pass both photo paths to the odiff command or to the compare() function in odiff-bin, which returns match and reason and can write a diff image. Supported inputs are .png, .jpeg, .jpg, .webp and .tiff, and cross-format comparison such as .jpg against .png is supported.

Official sources

  1. dmtrKovalenko/odiff on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dmtrkovalenko-odiff.svg)](https://hysenlabs.com/projects/dmtrkovalenko-odiff)