PerceptualHash.NET
0.2.0
See the version list below for details.
dotnet add package PerceptualHash.NET --version 0.2.0
NuGet\Install-Package PerceptualHash.NET -Version 0.2.0
<PackageReference Include="PerceptualHash.NET" Version="0.2.0" />
<PackageVersion Include="PerceptualHash.NET" Version="0.2.0" />
<PackageReference Include="PerceptualHash.NET" />
paket add PerceptualHash.NET --version 0.2.0
#r "nuget: PerceptualHash.NET, 0.2.0"
#:package PerceptualHash.NET@0.2.0
#addin nuget:?package=PerceptualHash.NET&version=0.2.0
#tool nuget:?package=PerceptualHash.NET&version=0.2.0
PerceptualHash.NET
PerceptualHash.NET is a cross-platform image hashing library for modern .NET, implemented in the NetImgHash namespace and built on SixLabors.ImageSharp.
Output is verified against the Python imagehash reference implementation by a golden dataset, so hashes are comparable across the two ecosystems.
Status
This repository currently ships the MVP surface:
AverageHash(aHash)DifferenceHash(dHash)ImageHashvalue type with Hamming distance and similarity helpers
PerceptualHash (pHash) and WaveletHash (wHash) are reserved in the public API for follow-up releases.
Target Frameworks
net8.0— in support until November 2026net10.0— LTSnet11.0
net9.0 is not targeted: it went out of support in May 2026.
The frameworks are fixed in Directory.Build.props rather than derived from the
installed SDK, so a local build produces the same set of targets as CI and as the
published package. Building all three needs the .NET 8, 10, and 11 SDKs; to build
a subset, pass -p:TargetFrameworks=net10.0.
Install
dotnet add package PerceptualHash.NET
One runtime dependency: SixLabors.ImageSharp. Nothing else.
Usage
using NetImgHash;
using var stream1 = File.OpenRead("image-original.jpg");
using var stream2 = File.OpenRead("image-edited.jpg");
var hash1 = ImageHasher.Compute(stream1, HashAlgorithm.DifferenceHash);
var hash2 = ImageHasher.Compute(stream2, HashAlgorithm.DifferenceHash);
var distance = hash1.HammingDistance(hash2); // 0 = identical, 64 = every bit differs
var similarity = hash1.Similarity(hash2); // 1.0 = identical, 0.0 = every bit differs
// Hashes round-trip through lowercase hex, so they can be stored and compared later.
var stored = hash1.ToString(); // e.g. "a1b2c3d4e5f60718"
var restored = ImageHash.Parse(stored);
A Hamming distance of 0–5 on a 64-bit hash usually means the same image; above
about 10 usually means a different one. Tune the threshold against your own data.
Compute throws NotSupportedException for HashAlgorithm.PerceptualHash and
HashAlgorithm.WaveletHash, which are reserved but not yet implemented.
Algorithm Specifications
Average Hash
- Convert the image to grayscale.
- Resize to
8x8. - Compute the mean luminance value.
- Set each bit when the pixel is greater than the mean.
- Return the 64-bit result as lowercase hexadecimal.
Difference Hash
- Convert the image to grayscale.
- Resize to
9x8. - Compare each pixel to its neighbor on the left.
- Set each bit when the right-hand pixel is brighter.
- Return the 64-bit result as lowercase hexadecimal.
Compatibility Notes
- The implementation is tuned to stay close to Python
imagehashsemantics foraHashanddHash. - The checked-in golden dataset under
tests/NetImgHash.Tests/TestDatais used to lock hash formatting, bit ordering, and representative image behavior. - EXIF auto-orientation is not part of the hashing pipeline for this MVP.
Test Data
The test project includes a golden dataset with representative images:
- standard RGB image
- grayscale image
- high-resolution resize
- thumbnail
- EXIF-orientation JPEG
- transparent PNG
- JPEG quality variants
- cropped version
- rotated version
The expected results live in tests/NetImgHash.Tests/TestData/expected_hashes.json.
To regenerate the dataset locally with Python:
- Create a local virtual environment.
- Install
pillowandimagehash. - Regenerate the images and manifest.
- Run
dotnet test.
Tests
60 tests, run against each of the three target frameworks — 180 executions.
dotnet test PerceptualHash.NET.sln # all three frameworks
dotnet test PerceptualHash.NET.sln -f net10.0 # just one
| Suite | Tests | What it covers |
|---|---|---|
GoldenDatasetTests |
20 | Exact hash strings for a checked-in dataset, generated by Python imagehash. This is the correctness anchor: it locks bit ordering, hex formatting, and behaviour on greyscale, transparency, EXIF orientation, resizes, crops, rotations and JPEG quality variants. |
ImageHashInvariantTests |
31 | The bit-length invariant, the uninitialized default, mismatched lengths, and hex round-tripping. |
AlgorithmTests |
4 | aHash and dHash bit packing against hand-constructed images. |
ImageHashTests |
5 | Distance, similarity, parsing, formatting. |
CI runs the whole suite on Linux and Windows. The golden dataset asserts exact hash strings, so a platform decoding difference would surface there rather than in production.
Development
dotnet build PerceptualHash.NET.sln
CI builds and tests on Linux and Windows across all three frameworks. Releases are
tag-driven: pushing a v*.*.* tag packs, checks the tag against the package
version, and publishes to NuGet.
Releasing
Publishing uses NuGet Trusted Publishing — nuget.org exchanges a short-lived GitHub OIDC token for a one-hour API key, so no long-lived secret is stored.
One-time setup on nuget.org (Account → Trusted Publishing):
| Field | Value |
|---|---|
| Repository Owner | aelena |
| Repository | imagehash-net |
| Workflow File | release.yml (file name only, no path) |
| Environment | production (the workflow declares it; the two must match) |
| Glob Patterns and Packages | PerceptualHash.NET |
Create a GitHub environment named production in the repository, and add a
secret NUGET_USER holding the nuget.org profile name (not an email address).
A policy is bound to one repository, so each repository needs its own.
To cut a release: set <Version> in NetImgHash.csproj, commit, then
git tag v0.2.0 && git push origin v0.2.0.
Changelog
See CHANGELOG.md.
License
MIT — see LICENSE. No copyleft anywhere in the dependency graph.
On ImageSharp
The single runtime dependency, SixLabors.ImageSharp, is under the
Six Labors Split License,
which is Apache 2.0 or a commercial licence depending on how you consume it. It
is worth being precise about, because the licence text is explicit and the answer
is favourable:
Works are licensed to You under the Apache License, Version 2.0 if […] You are consuming the Work as a Transitive Package Dependency.
If you install PerceptualHash.NET, ImageSharp arrives indirectly through it —
that is a transitive dependency by the licence's own definition, so you receive
ImageSharp under Apache 2.0 regardless of your organisation's size or revenue.
The commercial-licence threshold (for-profit, over $1M USD annual gross revenue) applies to a direct dependency on ImageSharp. It does not reach consumers of this package. This library itself qualifies for Apache 2.0 on a separate clause anyway, being open source.
Not legal advice, but the clause is unambiguous and quoted above so you can check it yourself.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- SixLabors.ImageSharp (>= 3.1.12)
-
net11.0
- SixLabors.ImageSharp (>= 3.1.12)
-
net8.0
- SixLabors.ImageSharp (>= 3.1.12)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.