Skip to main content

imagefp

Decide whether two images are the same picture — even when they were re-encoded, alpha-flattened onto a background, cropped/padded differently, or resized — without ever falsely claiming a match it isn't sure of.

Built for pipelines that re-save or re-export documents (PowerPoint, Word, Excel, PDF) where the same source image commonly reappears in a different container format, with different compression, or with its transparency flattened away. Generic perceptual-hash libraries don't handle the alpha-flattening or EMF-metadata-churn cases; this one does.

Install

pip install imagefp

Raster comparison (.png, .jpg, .gif, .bmp, .tiff, .webp, ...) requires Pillow, which is installed automatically as a dependency. EMF metafile comparison has no extra dependency (WMF is not supported yet).

Quick start

from imagefp import images_match

with open("logo.png", "rb") as f:
    data_a = f.read()
with open("logo_export.jpg", "rb") as f:
    data_b = f.read()

match, reason = images_match(data_a, "logo.png", data_b, "logo_export.jpg")
print(match, reason)
# True "same picture -- shape 0/30, colour 3/18, aspect 0.000/0.11"

images_match is the one function most callers need. It takes the raw bytes and the original file/part name (so it knows whether it's looking at a raster or a vector metafile) for each image, and returns:

(match: bool, reason: str)

reason is always populated, win or lose, so you can log why two images were or weren't considered the same.

How it decides "same picture"

  1. Classify each file as raster (.png, .jpg, .webp, ...) or metafile (.emf, .wmf) by extension.
  2. Rasters are compared with a perceptual fingerprint:
    • Apply EXIF orientation, then crop to actual content (ignore blank padding/margins; ink is detected from alpha or from a flat border colour).
    • Composite any transparency onto white, the same way PowerPoint does when it re-saves an image — so a transparent logo and its flattened export land in the same colour space.
    • Downsample to a 16×16 grid (colour thumbnail + a separate "ink mask" of where there's real content vs. background).
    • Compare in three gates, cheapest first: aspect ratio, then ink-mask shape, then colour — measured only over cells where both images actually have ink, so background/padding differences can't skew it.
  3. Metafiles (.emf only) are compared by hashing drawing records, skipping the header (resize bounds), skipping non-EMF+ metadata comments, but including EMF+ comment payloads where Office stores GDI+ drawing data — so the same chart re-exported with new timestamps still matches, while an edit to the drawing (including EMF+ content) does not. The file must be a complete EMF (through EMR_EOF); truncated files are rejected.
  4. Anything uncertain returns "different." Undecodable bytes, a raster-vs-metafile mismatch, missing bytes, unsupported WMF, or an incomplete/corrupt EMF — all reported as not a match, never as an uncertain match.

What it does not do

  • It cannot detect a match after a resize that rewrites an EMF's actual drawing coordinates (as opposed to just its header bounds) — that would require rendering the metafile to pixels, which is out of scope.
  • .wmf files are legacy 16-bit metafiles and are not supported yet (images_match returns an explicit “WMF is not supported” reason). Only .emf metafiles are compared. Open an issue if you need WMF.
  • This is a "is this the same picture" tool, not a general reverse-image search or similarity ranker — it returns a boolean, not a similarity score for ranking many candidates (though the reason string exposes the underlying distances if you want to build that yourself).

API reference

Function Purpose
images_match(data_a, name_a, data_b, name_b, *, shape=30.0, thumb=18.0, aspect=0.11) The main entry point. Returns (match, reason).
kind(name) "raster", "metafile", or "other", by file extension.
describe(data) Build a Descriptor (thumbnail + ink mask + aspect) for one raster image.
same_image(a, b, ...) Run the three-gate comparison on two Descriptors directly.
emf_signature(data) SHA-256-derived signature of an EMF's drawing records, or None.
thumb_distance, shape_distance, colour_distance The individual distance metrics, if you want to tune thresholds yourself.

All public names are also importable directly from the imagefp package, e.g. from imagefp import Descriptor, kind, emf_signature.

Tuning thresholds

The three gates each take a threshold, exposed as keyword arguments on images_match:

images_match(data_a, name_a, data_b, name_b,
             shape=30.0,   # max ink-mask distance
             thumb=18.0,   # max masked colour distance
             aspect=0.11)  # max |log(aspect ratio difference)|

Lower values are stricter (fewer false positives, more false negatives); higher values are looser. The defaults were tuned for documents re-exported by PowerPoint/Office tooling — you may want to widen thumb slightly for very lossy JPEG re-compression, or tighten aspect if your corpus has many same-content images at deliberately different aspect ratios (e.g. a banner vs. a thumbnail of unrelated art that happens to share a palette).

Development

git clone https://github.com/tejanshsachdeva/imagefp.git
cd imagefp
pip install -e ".[dev]"
pytest

License

MIT — see LICENSE.

Release files for imagefp 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for imagefp 0.6.0
File Size Uploaded
imagefp-0.6.0.tar.gz 16.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for imagefp 0.6.0
File Interpreter ABI Platform
imagefp-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 27.8 kB

Release files / imagefp-0.6.0.tar.gz

Download URL imagefp-0.6.0.tar.gz
Size 16.1 kB
Tags Source
SHA-256 checksum
How to use checksums
3a36b566cf234e2181d36fd84a3e8cbd4b0b4529d15a8e136e07a7fa511b9e11
BLAKE2b-256 checksum
How to use checksums
e9584314af58c8867c040b9f34060cb0a2fd4f8056b9d8d2816743a174e89842
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / imagefp-0.6.0-py3-none-any.whl

Download URL imagefp-0.6.0-py3-none-any.whl
Size 11.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
126b5fb2b0ec6675d0b4f8e4bc63d2232f9d4d6ce1cb8936b29128f769e61a27
BLAKE2b-256 checksum
How to use checksums
43c7f2d939af7facd31e7e4e9e744fd593053d25464192086a48765b5851e3e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page