Documentation · API reference · Install · Choosing an algorithm · Releases · Changelog · Python binding
A perceptual image hashing library in C: nine algorithms, native SIMD-accelerated JPEG/PNG/WebP decoders, and a hash of a 400×400 photo in 0.05 ms after a 0.24 ms decode (numbers under "Performance" below).
The documentation site explains how each algorithm works, with figures and
measurements made by this code on a synthetic and a photo corpus, and carries the API
reference generated from include/libphash.h.
- Try it in two minutes — download a prebuilt archive and run an example: Try it.
- Build it into a program — Building & Installation, then Usage and the examples.
- Choose an algorithm, or understand one — Choosing an algorithm on the site, and a page per algorithm from there.
Upgrading from 1.x? See the changelog for what changed and the migration guide for what to do about it — 2.0.0 changes some hash values silently (no error, no warning), most importantly because EXIF auto-orientation is now on by default.
Finding duplicate and near-duplicate images in a collection you control. Deduplicating an archive, clustering a catalog, keying a cache, answering "have I stored this before" inside a trusted pipeline. Every design decision in this library is made for that job.
It is not built to withstand someone trying to fool it. Every hash here is deterministic and unkeyed — the same file and settings give the same value on any machine (for JPEG, with the same decoder; see Comparing hashes), with no shared secret — and that property, which is what makes deduplication work at all, is also what makes the hashes straightforward to attack on purpose. Anyone who benefits from a wrong answer can construct a visually different image with a matching hash, or perturb an image so it stops matching its own copy.
So: do not use libphash as a content-moderation filter, a copyright blocklist, or an
integrity check on untrusted input. Those are authentication problems, the literature
solves them with keyed algorithms whose key is load-bearing rather than optional, and
none is implemented here. Reaching for a neural embedding instead does not close the gap —
published collision attacks cover learned hashes too.
The reasoning, the citations, and what follows from this choice for how the algorithms are verified are in Perceptual hashing and Algorithm provenance.
- Python: python-libphash (
pip install python-libphash), the official binding, released for every libphash release - More bindings (Node.js, Rust, Go) are in development.
- Multiple Algorithms:
aHash,dHash,pHash(DCT-based),wHash(Wavelet),mHash,BMH,Radial,ColorHash, andColorMoments. Every one of them is traced to its source in the references, and every known divergence from that source is written down in Algorithm provenance. - High-Performance Decoders: Built-in support for
libjpeg-turbo,libpng, andlibwebpwith SIMD acceleration (NEON/SSE) andmmapoptimization. - Broad Format Fallback: JPEG/PNG/WebP are decoded by the SIMD-accelerated native backends above; anything else — BMP, GIF, TGA, PSD, HDR, PIC, PNM — falls back to the bundled
stb_imagedecoder automatically, no configuration needed. Not covered: TIFF (unsupported bystb_image) and animated GIF beyond the first frame (only the first frame is hashed). Animated WebP is rejected outright (not decoded to a frame) when the native WebP backend is in use. - Fast Grayscale Loading: Native decoders can perform grayscale conversion during decompression: 47 ms instead of 54 ms for a 20-megapixel JPEG, and one byte per pixel instead of three.
- Zero-Fragmentation Arena: Optimized context-based Arena Allocator for internal operations, ensuring predictable performance in high-load environments.
- Decompression-Bomb Protection: Images are rejected with
PH_ERR_IMAGE_TOO_LARGEbefore any pixel buffer is allocated if they exceed a configurable pixel-count limit (256 Mi = 268,435,456 pixels by default; tune or disable viaph_context_set_max_pixels()). - Automatic EXIF/WebP Orientation: on by default — a hash describes what a viewer displays, not the raw sensor buffer. Opt out with
ph_context_set_auto_orient(ctx, 0)if you need hashes of the stored (unrotated) pixels, e.g. to match hashes stored by 1.x; see the migration guide. - Batch API:
ph_hash_files()/ph_hash_buffers()hash many images across an optional internal thread pool, andph_compute_multi()computes several of the fouruint64_talgorithms (aHash/dHash/pHash/wHash) in one call sharing the same grayscale conversion. - Detailed error codes:
ph_error_tdistinguishes an unsupported format, corrupt data, an unavailable decoder, an I/O failure, and an oversized image, instead of one generic failure — seeinclude/libphash.h. - Digest helpers:
ph_digest_to_hex()/ph_digest_from_hex()/ph_hash_to_hex()/ph_hash_from_hex()for storing/transmitting hashes as text,ph_similarity()/ph_similarity_digest()for a normalized [0,1] score alongside the raw distance functions. - FFI-Friendly: Clean C API with opaque pointers, flat structs and fixed-width enums, designed for FFI bindings from any language.
- Cross-Platform: Linux (x86-64, arm64, 32-bit x86), macOS arm64 and Windows x86-64, built and tested by CI on every push, with the same hash values on all of them — see Supported platforms.
Time to hash an image that is already loaded, and to decode it, on an Apple M3 Pro (CMake Release build with the bundled decoders, one thread, minimum of 5–300 runs):
| 400×400 JPEG | 5472×3648 JPEG (20 Mpx) | |
|---|---|---|
| decode | 0.24 ms | 56 ms |
| aHash, pHash, wHash, BMH | 0.05 ms each | 2.6 ms each |
| dHash | 0.06 ms | 6.3 ms |
| ColorHash, ColorMoments | 0.13, 0.25 ms | 17, 31 ms |
| mHash, Radial | 0.94, 0.61 ms | 34, 49 ms |
A hash's cost grows with the image's pixel count, since the reduction to its working size
reads every pixel; for large JPEGs ph_context_set_decode_scale() cuts both. The batch API
spreads files over every CPU. How the time grows with the image, by format, and what each
setting saves is in Performance.
| Configuration | Decoders | Dependencies | For |
|---|---|---|---|
| Full (CMake default) | libjpeg-turbo, libpng, libwebp |
none outside the repository (vendored submodules) | the fastest decoding; what the prebuilt archives contain |
| Minimal (Makefile, or CMake without submodules) | stb_image |
none | the smallest dependency footprint; WebP only through a system libwebp (make USE_WEBP=1) |
Download the archive for your platform from the
Releases page (static build, for
example libphash-<version>-linux-x86_64.tar.gz), unpack it, and build the
basic_hash.c example against it:
tar xf libphash-<version>-<platform>.tar.gz
cc basic_hash.c -o basic_hash \
$(PKG_CONFIG_PATH=libphash-<version>-<platform>/lib/pkgconfig pkg-config --cflags --libs libphash)
./basic_hash photo.jpg # prints the pHash, e.g. "pHash: 7eb66192bc394247"compare_two_images.c prints how far apart two images
are; the other examples are listed in examples/README.md.
Every route — archives, CMake, the Makefile, add_subdirectory(), Python — with the
difference between the Full and Minimal builds and how to check the result:
Installing.
Each tagged release (vX.Y.Z) publishes prebuilt archives on the
GitHub Releases page for
linux-x86_64, linux-arm64, macos-arm64, and windows-x86_64 — both a
static (libphash-X.Y.Z-<platform>.tar.gz/.zip) and a shared
(libphash-X.Y.Z-<platform>-shared.tar.gz/.zip) build, each containing
include/, lib/ (plus LICENSE and THIRD-PARTY-NOTICES.md), and a
SHA256SUMS.txt covering every archive in the release. Every archive is
compiled with the full vendored decoder set (libjpeg-turbo, libpng,
libwebp, zlib-ng) and smoke-tested against a clean extraction before
publishing — see .github/workflows/release.yml. This is the quickest path
for FFI bindings or any consumer that doesn't want to build the vendored
decoders itself. Each archive also carries a build provenance attestation:
gh attestation verify <archive> --repo gudoshnikovn/libphash confirms it was
built by this repository's release workflow (see SECURITY.md, "Verifying a
release artifact").
CPU baseline. The x86-64 archives need SSE4.2 and POPCNT (the x86-64-v2 level: every x86-64 CPU since 2009–2011); the arm64 archives need ARMv8-A with Advanced SIMD, which every arm64 CPU has. Nothing requires AVX or AVX2; libjpeg-turbo, libwebp and zlib-ng detect wider instruction sets at run time and use them where the CPU has them.
Best for managing bundled high-performance decoders and system integration.
git clone --recursive https://github.com/gudoshnikovn/libphash.git
cd libphash
cmake --preset release
cmake --build --preset release
cmake --install build/release --prefix "$HOME/.local"Fast and simple for minimal builds or static linking.
# Build static library
make -j8
# Run tests
make test
# Install libphash.a, the headers and libphash.pc (default PREFIX=/usr/local)
make install PREFIX=$HOME/.local#include <libphash.h>
#include <stdio.h>
int main(void) {
ph_context_t *ctx = NULL;
uint64_t hash = 0;
if (ph_create(&ctx) != PH_SUCCESS) {
fprintf(stderr, "ph_create failed\n");
return 1;
}
// Enable fast grayscale loading (skips RGB conversion)
ph_context_set_load_grayscale(ctx, 1);
ph_error_t err = ph_load_from_file(ctx, "photo.jpg");
if (err == PH_SUCCESS) {
err = ph_compute_phash(ctx, &hash);
if (err == PH_SUCCESS)
printf("pHash: %016llx\n", (unsigned long long)hash);
else
fprintf(stderr, "hash failed: %s\n", ph_get_error_string(err));
} else {
fprintf(stderr, "load failed: %s\n", ph_get_error_string(err));
}
ph_free(ctx);
return 0;
}Full, compiling versions of this and five more — comparing two images, the digest
algorithms and their metrics, batch hashing, error handling, a CMake consumer — are in
examples/. CI builds and runs them against the current header in
every run.
libphash links several backend libraries when built with the default vendored
decoder set, so a plain -lphash is not enough on its own. Use whichever of these
matches how you built/installed it:
# pkg-config (works for either build system, after `make install`/`cmake --install`)
cc main.c $(pkg-config --cflags --libs libphash) -o my_app# CMake, after `find_package`-able install (see "Recommended (CMake)" above)
find_package(phash 2 REQUIRED)
target_link_libraries(my_app PRIVATE phash::phash)Both forms pull in whatever the installed build was actually configured with
(-lphash_jpeg -lpng16 -lwebpdecoder -lz, or nothing extra for a minimal/stb_image-only
build) — you don't need to track that list by hand. See the migration guide if you're
moving a 1.x integration that linked by hand onto either of these.
This project is licensed under the MIT License. See the LICENSE file for the full text.
libphash bundles several high-performance libraries to ensure zero-dependency builds:
- libjpeg-turbo (v3.2.0): IJG, BSD-3-Clause, zlib.
- libpng (v1.6.59): libpng License 2.0.
- libwebp (v1.6.0): WebP License (BSD 3-Clause).
- zlib-ng (v2.3.3): zlib License.
- stb_image (v2.30): Public Domain / MIT.
- stb_image_resize2 (v2.18): Public Domain / MIT.
For detailed licensing information regarding these components, please refer to THIRD-PARTY-NOTICES.md.