Skip to content

elvis-epx/pictdiff

Repository files navigation

pictdiff

This program implements image comparison by generating a picture for visual inspection. It also prints a rough diff metric number, zero means the images are exactly equal.

For example, given two images:

Original image Original image

The difference image generated by the utility looks like this:

Original image

You can compare the images in inverse order, which generates a similar but differently tinted diff image:

Original image

I just want to use it!

The Node.js version has been published in NPMJS, you can install via npm:

sudo npm install -g pictdiff

Usage:

pictdiff old.png new.png diffmap.png

How to interpret the diff image

The diff "blank" image is white. Colorization of the diff image is proportional: pixels are darker as the differences are more intense.

Differences are also tinted to reflect the color changes. For example, if "img1" is generally more red than "img2", then "diff" will be cyan. On the other hand, if "img2" is more red than "img1", then "diff" will be red-tinted.

Differences in alpha (opacity) have twofold effects. They are added to the total absolute difference, which is rendered as gray in diff image. This guarantees that alpha differences will be clearly revealed, even if the color did not change. And, of course, the number returned by the utility will be non-zero.

On top of that, the color channels are multiplied by alpha, so the color differences are mitigated when both images are quite transparent. In the extreme case, two completely transparent images (alpha=0) will be considered equal, even if one is "red" and another is "blue".

Example: these two images are generally clear, except for a transparent area. Given the white background of this page, it is difficult to see the images, but they are there:

Original image Original image

The map generated by pictdiff reveals the transparency differences clearly:

Original image

In this case the inverse map (diff'ing the two images in inverse order) would be equal because the difference is not colored, so either positive or negative differences create darker areas.

Implementations

There is a Python reference implementation, and implementations in other languages: Rust, Go and Node.js.

How to use the Python flavor:

./pictdiff.py img1.png img2.png diff.png

Apart from the "diff" image, the utility also prints a difference metric:

pictdiff/img $ ../pictdiff.py old.png new.png diff.png
2089964

This is a very rough metric, and the number gets big very fast. It is mostly useful to idenfity images that are exactly equal:

pictdiff/img $  ../pictdiff.py old.png old.png diff.png
0

Rust flavor

The Python flavor is quite slow, especially for big imgaes. If you can install the Rust toolchain, you can run the Rust flavor instead:

cargo run --release img1.png img2.png diff.png

After it runs the first time successfully, you can copy the binary from target/release/pictdiff to /usr/local/bin or another convenient location.

Go flavor

Assuming you have the Go toolchain installed and configured:

go build pictdiff.go
./pictdiff old.png new.png diff.png

Run the executable "pictdiff" created in the folder. The command "go run" also runs it, albeit more slowly.

Node.js flavor

Assuming you have Node.js installed, you can run the Javascript flavor locally from the source:

npm install
node pictdiff.js old.png new.png diffmap.png

You can also install pictdiff from npmjs, as shown at the top of te page.

Motivation

The 'compare' tool from ImageMagick almost does what I want, but it marks any difference as a red pixel. The threshold can be configured, but the generated diff image is an all-or-nothing comparison. I needed a diff image for quick inspection of changes, and the image should show the magnitude of these differences, as well as how color was affected.

Performance

The Python flavor is slow. It was the first one and is the reference implementation. Alternative flavors must generate exactly the same results given the same images.

The following table shows the relative performance of each flavor handling big images (img/big_a.png and img/big_b.png, 4019x2309), run in a Macbook Pro M3. Measurements taken after a couple warm-up runs.

Flavor Processor time
Python 3.13.2 1x
Node.js 20.11.1 9x
Go 1.24.1 cpu time 6x-12x (*)
Go 1.24.1 wall clock (**) 7x-14x
Rust 1.86.0 52x

(*) Compression level of PNG encoder makes a big difference here. Default compression is notoriously slow, but compresses more than the other flavors. "Best speed" is much faster and final size is not outrageously different.

(**) Looks faster when measured by wall-clock because of goroutines. Non-Go flavors are all single-threaded.

The same tests run in Linux, processor is 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz (eight cores):

Flavor Processor time
Python 3.12.3 1x
Node.js 20.5.1 10x
Go 1.24.1 cpu time 9x-19x (*)
Go 1.24.1 wall clock 12x-27x
Rust 1.86.0 32x

(*) Again, run with two levels of PNG encoding compression.

Bugs

Not tested with 48-bit images. (Python Imaging Library does not open 48-bit TIFFs; Rust image crate does not support 48-bit yet.)

Contact

Elvis Pfützenreuter - [email protected] - https://epxx.co

About

Compare two pictures of same size, generating a diff image for visual inspection

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •