Installing¶
libphash is a C library with one public header, libphash.h. There are four ways to get
it into a program: a prebuilt archive, a CMake build from source, a Makefile build from
source, or the source tree inside your own CMake project. Python has its own package.
This page covers each of them, what the two builds differ in, and how to check the
result.
flowchart TD
Q{"Your platform has<br/>a prebuilt archive?"} -->|yes| A["Prebuilt archive"]
Q -->|no, or you build<br/>everything yourself| S{"Your project<br/>uses CMake?"}
S -->|"yes, vendor the source"| D["add_subdirectory()"]
S -->|"yes, install once"| C["CMake build"]
S -->|no| M["Makefile build"]
P(["Python"]) --> W["pip install python-libphash"]
Two builds: Full and Minimal¶
The library decodes images itself. Which decoders it has depends on how it was built:
| Build | Decoders | Dependencies | Made by |
|---|---|---|---|
| Full | libjpeg-turbo, libpng with zlib-ng, libwebp; stb_image for everything else | none outside the repository: the decoders are git submodules under vendor/ |
the prebuilt archives, cmake --preset release |
| Minimal | stb_image for every format | none | the Makefile, cmake --preset minimal, a CMake build of a checkout without submodules |
What the Full build adds:
- Speed. libjpeg-turbo decodes JPEG to gray 1.4 to 6.4 times faster than stb_image, and libpng with zlib-ng decodes photographic PNG 1.25 to 1.7 times faster on Linux (measured). Decoding takes most of the time of a cheap hash on a large image (Performance).
- WebP. stb_image has no WebP decoder: a Minimal build answers a WebP file with
PH_ERR_DECODER_UNAVAILABLE. The Makefile can link a system libwebp instead (below). - Scaled JPEG decoding.
ph_context_set_decode_scale()decodes a JPEG at ½, ¼ or ⅛ of its size through libjpeg-turbo; stb_image always decodes at full size.
Both builds read BMP, GIF (the first frame), TGA, PSD, HDR, PIC and PNM through stb_image. Neither reads TIFF.
The two builds round JPEG's inverse DCT differently, so a JPEG reaches the hash as slightly different pixels and its hash can differ in a few bits between them. Hashes stored from one build are compared with hashes from the other by distance, not by equality (Same hash on every machine). Every other difference between builds — operating system, architecture, compiler, SIMD level, static or shared — leaves every hash the same.
Prebuilt archive¶
Each release on the releases page carries a Full build for four platforms, static and shared:
| Platform | Static | Shared |
|---|---|---|
| Linux x86-64 | libphash-2.0.2-linux-x86_64.tar.gz |
libphash-2.0.2-linux-x86_64-shared.tar.gz |
| Linux arm64 | libphash-2.0.2-linux-arm64.tar.gz |
libphash-2.0.2-linux-arm64-shared.tar.gz |
| macOS arm64 | libphash-2.0.2-macos-arm64.tar.gz |
libphash-2.0.2-macos-arm64-shared.tar.gz |
| Windows x86-64 | libphash-2.0.2-windows-x86_64.zip |
libphash-2.0.2-windows-x86_64-shared.zip |
An archive unpacks into one directory named like the archive, with include/ (the two
headers), lib/ (the library, and in a static archive the decoder libraries it needs),
lib/cmake/phash/ (the CMake package), lib/pkgconfig/libphash.pc, LICENSE and
THIRD-PARTY-NOTICES.md. The shared Windows archive keeps phash.dll in bin/ and its
import library in lib/. The directory can be moved anywhere: both the CMake package and
libphash.pc find the files relative to themselves.
CPU. The x86-64 archives need SSE4.2 and POPCNT (the x86-64-v2 level, which every x86-64 CPU since 2009–2011 has); the arm64 archives need ARMv8-A, whose Advanced SIMD every arm64 CPU has. Nothing needs AVX: libjpeg-turbo, libwebp and zlib-ng use wider instructions when the CPU has them, decided at run time.
Checking a download. SHA256SUMS.txt in the same release covers every archive and
shows the download is intact. The build provenance attestation shows that the archive was
built by this repository's release workflow from the tagged commit; checking it needs the
GitHub CLI:
Get-FileHash libphash-2.0.2-windows-x86_64.zip -Algorithm SHA256
gh attestation verify libphash-2.0.2-windows-x86_64.zip --repo gudoshnikovn/libphash
Expand-Archive libphash-2.0.2-windows-x86_64.zip -DestinationPath .
Get-FileHash prints the hash; compare it with the archive's line in
SHA256SUMS.txt.
Both checks fail on an archive that is not byte for byte the one the release workflow built; Verifying a release artifact explains what each one proves. The archives are reproducible: building the tagged commit the same way gives the same bytes (Reproducible archives).
Then link against the unpacked directory as described in Using an installed libphash.
CMake build from source¶
The CMake build is the Full one. It needs CMake 3.21 or newer, a C17 compiler (GCC, Clang
or MSVC) and the decoder submodules. On x86-64, libjpeg-turbo also wants NASM or Yasm for
its SIMD code; without one, the configure warns SIMD extensions disabled and JPEG
decoding works, slower.
git clone --recursive https://github.com/gudoshnikovn/libphash.git
cd libphash
cmake --preset release
cmake --build --preset release
ctest --preset release
cmake --install build/release --prefix "$HOME/.local"
--recursive fetches the four decoders; in a clone made without it,
git submodule update --init --recursive does the same. The release preset refuses to
configure without them rather than build a Minimal library under the Full name.
cmake --install writes the headers, libphash.a and the decoder libraries, the
phash::phash CMake package and libphash.pc under the prefix. On Linux and macOS a
prefix you cannot write to, such as the default /usr/local on most systems, needs sudo
for the install step only.
A prefix holds one build configuration: libphash.a has the same name in a Debug and a
Release build, so a second cmake --install into the same prefix replaces the first, CMake
package and libphash.pc included. With a multi-config generator (Visual Studio, Xcode)
pick it with cmake --install <build dir> --config Debug; Release and Debug go to
prefixes of their own.
The presets a consumer is likely to want:
| Preset | Builds | In |
|---|---|---|
release |
Full, static library | build/release |
shared |
Full, shared library | build/shared |
minimal |
Minimal: stb_image only, no submodules needed | build/minimal |
Every preset uses its own build directory, so two of them can sit side by side. A shared
build installs libphash.so.2 (Linux), libphash.2.dylib (macOS) or phash.dll
(Windows) with the decoders linked inside it, so nothing else is installed with it. The
other presets are CI configurations: sanitizers, Valgrind, 32-bit x86, fuzzing
(Presets).
Options for a configure of your own, cmake -B build -D<option>=<value>:
| Option | Default | What it changes |
|---|---|---|
PHASH_BUILD_SHARED |
OFF |
a shared library instead of a static one |
PHASH_USE_LIBJPEG_TURBO |
ON |
JPEG through the bundled libjpeg-turbo; OFF — through stb_image |
PHASH_USE_LIBPNG |
ON |
PNG through the bundled libpng; OFF — through stb_image |
PHASH_USE_ZLIB_NG |
ON |
libpng inflates with the bundled zlib-ng; OFF — with the system zlib |
PHASH_USE_WEBP |
ON |
WebP through the bundled libwebp; OFF — no WebP |
PHASH_ENABLE_THREADS |
ON |
a worker pool for batch hashing; OFF — batches run on the calling thread, and no thread library is linked |
PHASH_OPTIMIZE_NATIVE |
OFF |
-march=native: a library for this CPU only, not for others of its architecture |
PHASH_STRICT_DEPS |
OFF |
a missing decoder submodule fails the configure, instead of a warning and stb_image in its place (the release preset turns it on) |
PHASH_BUILD_TESTS |
ON |
the test programs ctest runs |
The decoder options are independent of each other; zlib-ng is used only by libpng. The full list, with the Makefile's equivalents, is in Build systems and their defaults.
Makefile build from source¶
The Makefile builds the Minimal library with no dependencies and no submodules, on Linux and macOS:
git clone https://github.com/gudoshnikovn/libphash.git
cd libphash
make -j8
make test -j8
make install PREFIX="$HOME/.local"
make install writes libphash.a, the two headers and libphash.pc under PREFIX
(default /usr/local; DESTDIR stages the install elsewhere), and make uninstall with
the same PREFIX removes them. The Makefile builds only the static library and installs
no CMake package: a CMake project links it through pkg-config.
WebP through a system libwebp:
make -j8 USE_WEBP=1 EXTRA_CFLAGS="$(pkg-config --cflags libwebp)" \
EXTRA_LDFLAGS="$(pkg-config --libs-only-L libwebp)"
make install PREFIX="$HOME/.local" USE_WEBP=1
The installed libphash.pc then asks for -lwebp -lwebpdecoder; if libwebp lives outside
the linker's default path (Homebrew's /opt/homebrew/lib), the program's link line needs
its directory too. Other switches — PHASH_ENABLE_THREADS=0, CC=gcc, EXTRA_CFLAGS —
are in Build systems and their defaults.
Inside your own CMake project¶
A CMake project can carry the libphash source tree, as a git submodule or a copy, and build it with everything else:
The phash target brings its include directory and decoders with it. Set
PHASH_BUILD_TESTS to OFF if you don't run its tests; the other options above apply
here too. Your project's own BUILD_SHARED_LIBS and BUILD_TESTING stay as you set them.
The decoder submodules have to be checked out inside the copy; without them libphash
builds with stb_image and says so in one configure warning.
Using an installed libphash¶
Both an archive and an install from source are used the same way:
export PKG_CONFIG_PATH="<prefix>/lib/pkgconfig"
cc main.c -o my_app $(pkg-config --cflags --libs libphash)
<prefix> is the install prefix or the unpacked archive. libphash.pc lists every
library the build needs, so the link line never has to name them. For the static Linux
archive and for a Minimal build on macOS they are
phash::phash carries the include path, the decoder libraries of a static build and
the definitions the headers need. find_package(phash 2) accepts any 2.x.
A shared library is found at run time without LD_LIBRARY_PATH or DYLD_LIBRARY_PATH:
the program records the library's directory (rpath) at link time, through either route.
On Windows there is no rpath: phash.dll has to sit next to the program or on PATH.
Windows. Use the CMake package. The archives' libphash.pc is not tested there. A
build system other than CMake that links the static library on Windows defines
PHASH_STATIC_DEFINE before including libphash.h; without it the header declares every
function as imported from a DLL.
Python¶
python-libphash is the official
Python binding, maintained together with the library:
It is a separate project, released for every libphash release. From 2.0.1 on,
its version is the libphash version it contains followed by a revision of the binding:
python-libphash 2.0.1.N contains libphash 2.0.1, so pip install "python-libphash==2.0.1.*"
pins the library. The 1.x releases are numbered independently and state the libphash
version they contain in their README. Hashes are comparable only between the same
libphash version and settings, so a collection hashed partly from C and partly from Python
needs both on the same libphash. Bindings for other languages are planned.
Check the installation¶
examples/basic_hash.c
loads one image and prints its pHash:
On the repository's tests/data/photo.jpeg it prints pHash: 7eb66192bc394247 from
every build, Full and Minimal: this photograph's hash happens to come out the same under
both JPEG decoders.
To see what a library was built with, print
ph_get_build_info() — the program in
examples/cmake_consumer/
does that:
/* The smallest program against an installed libphash: prints the version and the build
* it links. See CMakeLists.txt next to this file for the build. */
#include <libphash.h>
#include <stdio.h>
int main(void) {
ph_context_t *ctx = NULL;
if (ph_create(&ctx) != PH_SUCCESS) {
fprintf(stderr, "ph_create failed\n");
return 1;
}
printf("libphash %s (header %s)\n", ph_version(), PH_VERSION_STRING);
printf("%s\n", ph_get_build_info());
ph_free(ctx);
return 0;
}
libphash 2.0.2 (header 2.0.2)
version=2.0.2 jpeg=libjpeg-turbo png=libpng webp=libwebp zlib=zlib-ng threads=on simd=neon mock=off
That is a Full build on arm64; a Minimal one says jpeg=stb png=stb webp=none zlib=none.
The first line catches a header and a library from different installs: the two versions
differ. A program that has to branch on a decoder asks
ph_can_use_jpeg(),
ph_can_use_png() and
ph_can_use_webp() instead of parsing that line.
Next: Quick start hashes and compares images.