python-libphash 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_libphash-1.0.0/LICENSE +21 -0
- python_libphash-1.0.0/MANIFEST.in +4 -0
- python_libphash-1.0.0/PKG-INFO +124 -0
- python_libphash-1.0.0/README.md +104 -0
- python_libphash-1.0.0/native/libphash/.clang-format +18 -0
- python_libphash-1.0.0/native/libphash/.git +1 -0
- python_libphash-1.0.0/native/libphash/.gitignore +25 -0
- python_libphash-1.0.0/native/libphash/CMakeLists.txt +48 -0
- python_libphash-1.0.0/native/libphash/LICENSE +21 -0
- python_libphash-1.0.0/native/libphash/Makefile +51 -0
- python_libphash-1.0.0/native/libphash/README.md +76 -0
- python_libphash-1.0.0/native/libphash/include/libphash.h +168 -0
- python_libphash-1.0.0/native/libphash/src/core.c +73 -0
- python_libphash-1.0.0/native/libphash/src/hashes/ahash.c +28 -0
- python_libphash-1.0.0/native/libphash/src/hashes/bmh.c +36 -0
- python_libphash-1.0.0/native/libphash/src/hashes/color_hash.c +56 -0
- python_libphash-1.0.0/native/libphash/src/hashes/common.c +98 -0
- python_libphash-1.0.0/native/libphash/src/hashes/dhash.c +25 -0
- python_libphash-1.0.0/native/libphash/src/hashes/mhash.c +33 -0
- python_libphash-1.0.0/native/libphash/src/hashes/phash.c +57 -0
- python_libphash-1.0.0/native/libphash/src/hashes/radial.c +103 -0
- python_libphash-1.0.0/native/libphash/src/hashes/whash.c +53 -0
- python_libphash-1.0.0/native/libphash/src/image.c +66 -0
- python_libphash-1.0.0/native/libphash/src/internal.h +34 -0
- python_libphash-1.0.0/native/libphash/tests/photo.jpeg +0 -0
- python_libphash-1.0.0/native/libphash/tests/photo_color_changed.jpeg +0 -0
- python_libphash-1.0.0/native/libphash/tests/photo_copy.jpeg +0 -0
- python_libphash-1.0.0/native/libphash/tests/photo_rotated_90.jpeg +0 -0
- python_libphash-1.0.0/native/libphash/tests/test_bmh.c +41 -0
- python_libphash-1.0.0/native/libphash/tests/test_color.c +55 -0
- python_libphash-1.0.0/native/libphash/tests/test_core.c +16 -0
- python_libphash-1.0.0/native/libphash/tests/test_hashes.c +14 -0
- python_libphash-1.0.0/native/libphash/tests/test_image_hashes.c +39 -0
- python_libphash-1.0.0/native/libphash/tests/test_internal.c +36 -0
- python_libphash-1.0.0/native/libphash/tests/test_macros.h +37 -0
- python_libphash-1.0.0/native/libphash/tests/test_mhash.c +38 -0
- python_libphash-1.0.0/native/libphash/tests/test_radial.c +72 -0
- python_libphash-1.0.0/native/libphash/tests/test_whash.c +37 -0
- python_libphash-1.0.0/native/libphash/vendor/stb_image.h +8748 -0
- python_libphash-1.0.0/pyproject.toml +35 -0
- python_libphash-1.0.0/setup.cfg +4 -0
- python_libphash-1.0.0/setup.py +12 -0
- python_libphash-1.0.0/src/libphash/__init__.py +24 -0
- python_libphash-1.0.0/src/libphash/_build.py +97 -0
- python_libphash-1.0.0/src/libphash/_native.pyi +39 -0
- python_libphash-1.0.0/src/libphash/context.py +94 -0
- python_libphash-1.0.0/src/libphash/exceptions.py +36 -0
- python_libphash-1.0.0/src/libphash/py.typed +0 -0
- python_libphash-1.0.0/src/libphash/types.py +58 -0
- python_libphash-1.0.0/src/libphash/utils.py +28 -0
- python_libphash-1.0.0/src/python_libphash.egg-info/PKG-INFO +124 -0
- python_libphash-1.0.0/src/python_libphash.egg-info/SOURCES.txt +58 -0
- python_libphash-1.0.0/src/python_libphash.egg-info/dependency_links.txt +1 -0
- python_libphash-1.0.0/src/python_libphash.egg-info/not-zip-safe +1 -0
- python_libphash-1.0.0/src/python_libphash.egg-info/requires.txt +6 -0
- python_libphash-1.0.0/src/python_libphash.egg-info/top_level.txt +1 -0
- python_libphash-1.0.0/tests/test_digests.py +27 -0
- python_libphash-1.0.0/tests/test_distances.py +39 -0
- python_libphash-1.0.0/tests/test_hashes.py +23 -0
- python_libphash-1.0.0/tests/test_loading.py +37 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gudoshnikovn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-libphash
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-performance perceptual hashing library (CFFI bindings)
|
|
5
|
+
Author-email: gudoshnikovn <gudoshnikov-na@yandex.ru>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gudoshnikovn/python-libphash
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: C
|
|
10
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: cffi>=1.15.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy; extra == "dev"
|
|
18
|
+
Requires-Dist: ruff; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# python-libphash
|
|
22
|
+
|
|
23
|
+
High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash), a C library for perceptual image hashing.
|
|
24
|
+
|
|
25
|
+
[](https://opensource.org/licenses/MIT)
|
|
26
|
+
[](https://www.python.org/downloads/)
|
|
27
|
+
|
|
28
|
+
## Overview
|
|
29
|
+
|
|
30
|
+
`libphash` provides multiple algorithms to generate "perceptual hashes" of images. Unlike cryptographic hashes (like MD5 or SHA256), perceptual hashes change only slightly if the image is resized, compressed, or has minor color adjustments. This makes them ideal for finding duplicate or similar images.
|
|
31
|
+
|
|
32
|
+
### Supported Algorithms
|
|
33
|
+
|
|
34
|
+
* **64-bit Hashes (uint64):**
|
|
35
|
+
* `ahash`: Average Hash
|
|
36
|
+
* `dhash`: Difference Hash
|
|
37
|
+
* `phash`: Perceptual Hash (DCT based)
|
|
38
|
+
* `whash`: Wavelet Hash
|
|
39
|
+
* `mhash`: Median Hash
|
|
40
|
+
* **Digest Hashes (Multi-byte):**
|
|
41
|
+
* `bmh`: Block Mean Hash
|
|
42
|
+
* `color_hash`: Color Moment Hash
|
|
43
|
+
* `radial_hash`: Radial Variance Hash
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Prerequisites
|
|
48
|
+
* A C compiler (GCC/Clang or MSVC)
|
|
49
|
+
* Python 3.8 or higher
|
|
50
|
+
|
|
51
|
+
### Install from source
|
|
52
|
+
```bash
|
|
53
|
+
git clone --recursive https://github.com/yourusername/python-libphash.git
|
|
54
|
+
cd python-libphash
|
|
55
|
+
pip install .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
### Basic Usage
|
|
61
|
+
```python
|
|
62
|
+
from libphash import ImageContext, HashMethod, hamming_distance
|
|
63
|
+
|
|
64
|
+
# Use the context manager for automatic memory management
|
|
65
|
+
with ImageContext("photo.jpg") as ctx:
|
|
66
|
+
# Get standard 64-bit hashes
|
|
67
|
+
phash_val = ctx.phash
|
|
68
|
+
dhash_val = ctx.dhash
|
|
69
|
+
|
|
70
|
+
print(f"pHash: {phash_val:016x}")
|
|
71
|
+
print(f"dHash: {dhash_val:016x}")
|
|
72
|
+
|
|
73
|
+
# Compare two images
|
|
74
|
+
from libphash import compare_images
|
|
75
|
+
distance = compare_images("image1.jpg", "image2.jpg", method=HashMethod.PHASH)
|
|
76
|
+
print(f"Hamming Distance: {distance}")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Working with Digests (Advanced Hashes)
|
|
80
|
+
Algorithms like Radial Hash or Color Hash return a `Digest` object instead of a single integer.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
with ImageContext("photo.jpg") as ctx:
|
|
84
|
+
digest = ctx.radial_hash
|
|
85
|
+
print(f"Digest size: {digest.size} bytes")
|
|
86
|
+
print(f"Raw data: {digest.data.hex()}")
|
|
87
|
+
|
|
88
|
+
# Comparing digests
|
|
89
|
+
with ImageContext("photo_v2.jpg") as ctx2:
|
|
90
|
+
digest2 = ctx2.radial_hash
|
|
91
|
+
|
|
92
|
+
# Hamming distance for bit-wise comparison
|
|
93
|
+
h_dist = digest.distance_hamming(digest2)
|
|
94
|
+
|
|
95
|
+
# L2 (Euclidean) distance for similarity
|
|
96
|
+
l2_dist = digest.distance_l2(digest2)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### `ImageContext`
|
|
102
|
+
The main class for loading images and computing hashes.
|
|
103
|
+
* `__init__(path=None, bytes_data=None)`: Load an image from a file path or memory.
|
|
104
|
+
* `set_gamma(gamma: float)`: Set gamma correction (useful for Radial Hash).
|
|
105
|
+
* **Properties**: `ahash`, `dhash`, `phash`, `whash`, `mhash` (returns `int`).
|
|
106
|
+
* **Properties**: `bmh`, `color_hash`, `radial_hash` (returns `Digest`).
|
|
107
|
+
|
|
108
|
+
### `Digest`
|
|
109
|
+
* `data`: The raw `bytes` of the hash.
|
|
110
|
+
* `size`: Length of the hash in bytes.
|
|
111
|
+
* `distance_hamming(other)`: Calculates bit-wise distance.
|
|
112
|
+
* `distance_l2(other)`: Calculates Euclidean distance.
|
|
113
|
+
|
|
114
|
+
### Utilities
|
|
115
|
+
* `hamming_distance(h1: int, h2: int)`: Returns the number of differing bits between two 64-bit integers.
|
|
116
|
+
* `get_hash(path, method)`: Quick way to get a hash without manual context management.
|
|
117
|
+
* `compare_images(path1, path2, method)`: Returns the Hamming distance between two image files.
|
|
118
|
+
|
|
119
|
+
## Performance
|
|
120
|
+
Since the core logic is implemented in C and uses `stb_image` for decoding, `libphash` is significantly faster than pure-Python alternatives. It also uses CFFI's "out-of-line" mode for minimal overhead.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
124
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# python-libphash
|
|
2
|
+
|
|
3
|
+
High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash), a C library for perceptual image hashing.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`libphash` provides multiple algorithms to generate "perceptual hashes" of images. Unlike cryptographic hashes (like MD5 or SHA256), perceptual hashes change only slightly if the image is resized, compressed, or has minor color adjustments. This makes them ideal for finding duplicate or similar images.
|
|
11
|
+
|
|
12
|
+
### Supported Algorithms
|
|
13
|
+
|
|
14
|
+
* **64-bit Hashes (uint64):**
|
|
15
|
+
* `ahash`: Average Hash
|
|
16
|
+
* `dhash`: Difference Hash
|
|
17
|
+
* `phash`: Perceptual Hash (DCT based)
|
|
18
|
+
* `whash`: Wavelet Hash
|
|
19
|
+
* `mhash`: Median Hash
|
|
20
|
+
* **Digest Hashes (Multi-byte):**
|
|
21
|
+
* `bmh`: Block Mean Hash
|
|
22
|
+
* `color_hash`: Color Moment Hash
|
|
23
|
+
* `radial_hash`: Radial Variance Hash
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
### Prerequisites
|
|
28
|
+
* A C compiler (GCC/Clang or MSVC)
|
|
29
|
+
* Python 3.8 or higher
|
|
30
|
+
|
|
31
|
+
### Install from source
|
|
32
|
+
```bash
|
|
33
|
+
git clone --recursive https://github.com/yourusername/python-libphash.git
|
|
34
|
+
cd python-libphash
|
|
35
|
+
pip install .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Basic Usage
|
|
41
|
+
```python
|
|
42
|
+
from libphash import ImageContext, HashMethod, hamming_distance
|
|
43
|
+
|
|
44
|
+
# Use the context manager for automatic memory management
|
|
45
|
+
with ImageContext("photo.jpg") as ctx:
|
|
46
|
+
# Get standard 64-bit hashes
|
|
47
|
+
phash_val = ctx.phash
|
|
48
|
+
dhash_val = ctx.dhash
|
|
49
|
+
|
|
50
|
+
print(f"pHash: {phash_val:016x}")
|
|
51
|
+
print(f"dHash: {dhash_val:016x}")
|
|
52
|
+
|
|
53
|
+
# Compare two images
|
|
54
|
+
from libphash import compare_images
|
|
55
|
+
distance = compare_images("image1.jpg", "image2.jpg", method=HashMethod.PHASH)
|
|
56
|
+
print(f"Hamming Distance: {distance}")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Working with Digests (Advanced Hashes)
|
|
60
|
+
Algorithms like Radial Hash or Color Hash return a `Digest` object instead of a single integer.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
with ImageContext("photo.jpg") as ctx:
|
|
64
|
+
digest = ctx.radial_hash
|
|
65
|
+
print(f"Digest size: {digest.size} bytes")
|
|
66
|
+
print(f"Raw data: {digest.data.hex()}")
|
|
67
|
+
|
|
68
|
+
# Comparing digests
|
|
69
|
+
with ImageContext("photo_v2.jpg") as ctx2:
|
|
70
|
+
digest2 = ctx2.radial_hash
|
|
71
|
+
|
|
72
|
+
# Hamming distance for bit-wise comparison
|
|
73
|
+
h_dist = digest.distance_hamming(digest2)
|
|
74
|
+
|
|
75
|
+
# L2 (Euclidean) distance for similarity
|
|
76
|
+
l2_dist = digest.distance_l2(digest2)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API Reference
|
|
80
|
+
|
|
81
|
+
### `ImageContext`
|
|
82
|
+
The main class for loading images and computing hashes.
|
|
83
|
+
* `__init__(path=None, bytes_data=None)`: Load an image from a file path or memory.
|
|
84
|
+
* `set_gamma(gamma: float)`: Set gamma correction (useful for Radial Hash).
|
|
85
|
+
* **Properties**: `ahash`, `dhash`, `phash`, `whash`, `mhash` (returns `int`).
|
|
86
|
+
* **Properties**: `bmh`, `color_hash`, `radial_hash` (returns `Digest`).
|
|
87
|
+
|
|
88
|
+
### `Digest`
|
|
89
|
+
* `data`: The raw `bytes` of the hash.
|
|
90
|
+
* `size`: Length of the hash in bytes.
|
|
91
|
+
* `distance_hamming(other)`: Calculates bit-wise distance.
|
|
92
|
+
* `distance_l2(other)`: Calculates Euclidean distance.
|
|
93
|
+
|
|
94
|
+
### Utilities
|
|
95
|
+
* `hamming_distance(h1: int, h2: int)`: Returns the number of differing bits between two 64-bit integers.
|
|
96
|
+
* `get_hash(path, method)`: Quick way to get a hash without manual context management.
|
|
97
|
+
* `compare_images(path1, path2, method)`: Returns the Hamming distance between two image files.
|
|
98
|
+
|
|
99
|
+
## Performance
|
|
100
|
+
Since the core logic is implemented in C and uses `stb_image` for decoding, `libphash` is significantly faster than pure-Python alternatives. It also uses CFFI's "out-of-line" mode for minimal overhead.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
104
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
BasedOnStyle: LLVM
|
|
3
|
+
IndentWidth: 4
|
|
4
|
+
ColumnLimit: 100
|
|
5
|
+
UseTab: Never
|
|
6
|
+
BreakBeforeBraces: Attach
|
|
7
|
+
AllowShortIfStatementsOnASingleLine: false
|
|
8
|
+
IndentCaseLabels: true
|
|
9
|
+
AlignAfterOpenBracket: Align
|
|
10
|
+
AlwaysBreakAfterReturnType: None
|
|
11
|
+
PointerAlignment: Right
|
|
12
|
+
SpaceAfterCStyleCast: false
|
|
13
|
+
SpaceBeforeParens: ControlStatements
|
|
14
|
+
Cpp11BracedListStyle: true
|
|
15
|
+
SortIncludes: true
|
|
16
|
+
MaxEmptyLinesToKeep: 1
|
|
17
|
+
---
|
|
18
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: ../../.git/modules/native/libphash
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Object files and libraries
|
|
2
|
+
*.o
|
|
3
|
+
*.a
|
|
4
|
+
*.so
|
|
5
|
+
*.dylib
|
|
6
|
+
*.dll
|
|
7
|
+
obj/
|
|
8
|
+
|
|
9
|
+
# Executables (Tests)
|
|
10
|
+
test_*
|
|
11
|
+
!tests/test_*.c
|
|
12
|
+
|
|
13
|
+
# IDEs and Editors
|
|
14
|
+
.vscode/
|
|
15
|
+
.idea/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
.DS_Store
|
|
19
|
+
|
|
20
|
+
# Build artifacts
|
|
21
|
+
dist/
|
|
22
|
+
build/
|
|
23
|
+
|
|
24
|
+
# Test images (if generated)
|
|
25
|
+
tests/output_*.jpeg
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.10)
|
|
2
|
+
project(libphash VERSION 1.2.0 LANGUAGES C)
|
|
3
|
+
|
|
4
|
+
# --- Options ---
|
|
5
|
+
option(PHASH_BUILD_TESTS "Build tests" ON)
|
|
6
|
+
option(PHASH_BUILD_SHARED "Build shared library" OFF)
|
|
7
|
+
|
|
8
|
+
# --- Compiler Flags ---
|
|
9
|
+
if(MSVC)
|
|
10
|
+
add_compile_options(/W3 /O2)
|
|
11
|
+
else()
|
|
12
|
+
add_compile_options(-Wall -Wextra -O3 -fPIC)
|
|
13
|
+
# Architecture specific optimizations
|
|
14
|
+
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64")
|
|
15
|
+
add_compile_options(-msse4.2)
|
|
16
|
+
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
|
17
|
+
add_compile_options(-march=armv8-a+simd)
|
|
18
|
+
endif()
|
|
19
|
+
endif()
|
|
20
|
+
|
|
21
|
+
# --- Library ---
|
|
22
|
+
file(GLOB_RECURSE SOURCES "src/*.c")
|
|
23
|
+
|
|
24
|
+
if(PHASH_BUILD_SHARED)
|
|
25
|
+
add_library(phash SHARED ${SOURCES})
|
|
26
|
+
target_compile_definitions(phash PRIVATE LIBPHASH_EXPORTS)
|
|
27
|
+
else()
|
|
28
|
+
add_library(phash STATIC ${SOURCES})
|
|
29
|
+
endif()
|
|
30
|
+
|
|
31
|
+
target_include_directories(phash PUBLIC
|
|
32
|
+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
33
|
+
$<INSTALL_INTERFACE:include>
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
target_link_libraries(phash PRIVATE m) # Link math library
|
|
37
|
+
|
|
38
|
+
# --- Tests ---
|
|
39
|
+
if(PHASH_BUILD_TESTS)
|
|
40
|
+
enable_testing()
|
|
41
|
+
file(GLOB TEST_SOURCES "tests/test_*.c")
|
|
42
|
+
foreach(test_src ${TEST_SOURCES})
|
|
43
|
+
get_filename_component(test_name ${test_src} NAME_WE)
|
|
44
|
+
add_executable(${test_name} ${test_src})
|
|
45
|
+
target_link_libraries(${test_name} PRIVATE phash)
|
|
46
|
+
add_test(NAME ${test_name} COMMAND ${test_name})
|
|
47
|
+
endforeach()
|
|
48
|
+
endif()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gudoshnikovn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
CC = gcc
|
|
2
|
+
CFLAGS = -I./include -O3 -Wall -Wextra -fPIC
|
|
3
|
+
LDFLAGS = -lm
|
|
4
|
+
|
|
5
|
+
UNAME_M := $(shell uname -m)
|
|
6
|
+
ifeq ($(UNAME_M),x86_64)
|
|
7
|
+
CFLAGS += -msse4.2
|
|
8
|
+
endif
|
|
9
|
+
ifeq ($(UNAME_M),arm64)
|
|
10
|
+
CFLAGS += -march=armv8-a+simd
|
|
11
|
+
endif
|
|
12
|
+
|
|
13
|
+
LIB_NAME = libphash.a
|
|
14
|
+
OBJ_DIR = obj
|
|
15
|
+
SRC_DIR = src
|
|
16
|
+
HASH_DIR = $(SRC_DIR)/hashes
|
|
17
|
+
TEST_DIR = tests
|
|
18
|
+
INC_DIR = include
|
|
19
|
+
|
|
20
|
+
SRCS = $(wildcard $(SRC_DIR)/*.c) $(wildcard $(HASH_DIR)/*.c)
|
|
21
|
+
OBJS = $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
|
|
22
|
+
|
|
23
|
+
TEST_SRCS = $(wildcard $(TEST_DIR)/test_*.c)
|
|
24
|
+
TEST_BINS = $(TEST_SRCS:$(TEST_DIR)/%.c=%)
|
|
25
|
+
|
|
26
|
+
all: $(LIB_NAME) $(TEST_BINS)
|
|
27
|
+
|
|
28
|
+
format:
|
|
29
|
+
find $(SRC_DIR) $(TEST_DIR) $(INC_DIR) -name "*.c" -o -name "*.h" | xargs clang-format -i
|
|
30
|
+
|
|
31
|
+
debug: CFLAGS = -I./include -g -O0 -fsanitize=address,undefined -Wall -Wextra -fPIC
|
|
32
|
+
debug: clean all
|
|
33
|
+
|
|
34
|
+
$(LIB_NAME): $(OBJS)
|
|
35
|
+
ar rcs $@ $^
|
|
36
|
+
|
|
37
|
+
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
|
|
38
|
+
@mkdir -p $(dir $@)
|
|
39
|
+
$(CC) $(CFLAGS) -c $< -o $@
|
|
40
|
+
|
|
41
|
+
test_%: $(TEST_DIR)/test_%.c $(LIB_NAME)
|
|
42
|
+
$(CC) $(CFLAGS) $< $(LIB_NAME) -o $@ $(LDFLAGS)
|
|
43
|
+
|
|
44
|
+
test: $(TEST_BINS)
|
|
45
|
+
@for test in $(TEST_BINS); do ./$$test || exit 1; done
|
|
46
|
+
@echo "ALL TESTS PASSED"
|
|
47
|
+
|
|
48
|
+
clean:
|
|
49
|
+
rm -rf $(OBJ_DIR) *.a test_*
|
|
50
|
+
|
|
51
|
+
.PHONY: all debug test clean format
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# libphash
|
|
2
|
+
|
|
3
|
+
A high-performance, portable C library for Perceptual Hashing. Designed for image similarity detection with zero external dependencies (except for the included `stb_image`).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Algorithms**:
|
|
8
|
+
- `aHash` (Average Hash): Fast, based on average intensity.
|
|
9
|
+
- `dHash` (Difference Hash): Fast, resistant to aspect ratio changes.
|
|
10
|
+
- `pHash` (Perceptual Hash): Robust, uses Discrete Cosine Transform (DCT).
|
|
11
|
+
- **FFI-Friendly**: Clean C API with opaque pointers, making it easy to wrap in Python (ctypes/cffi), Rust, or Node.js.
|
|
12
|
+
- **Thread-Safe**: No global state.
|
|
13
|
+
- **Cross-Platform**: Compatible with GCC, Clang, and MSVC.
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
The library follows a strict separation between public API and internal implementation:
|
|
18
|
+
- `include/libphash.h`: Public interface and error codes.
|
|
19
|
+
- `src/internal.h`: Internal structures and image processing helpers.
|
|
20
|
+
- `src/hashes/`: Core hash algorithm implementations.
|
|
21
|
+
|
|
22
|
+
## Building
|
|
23
|
+
|
|
24
|
+
To build the static library and run tests, you only need `make` and a C compiler:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Build the library (libphash.a)
|
|
28
|
+
make
|
|
29
|
+
|
|
30
|
+
# Run all tests
|
|
31
|
+
make test
|
|
32
|
+
|
|
33
|
+
# Clean build artifacts
|
|
34
|
+
make clean
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage Example (C)
|
|
38
|
+
|
|
39
|
+
```c
|
|
40
|
+
#include <libphash.h>
|
|
41
|
+
#include <stdio.h>
|
|
42
|
+
|
|
43
|
+
int main() {
|
|
44
|
+
ph_context_t *ctx = NULL;
|
|
45
|
+
uint64_t hash1, hash2;
|
|
46
|
+
|
|
47
|
+
ph_create(&ctx);
|
|
48
|
+
|
|
49
|
+
ph_load_from_file(ctx, "image1.jpg");
|
|
50
|
+
ph_compute_phash(ctx, &hash1);
|
|
51
|
+
|
|
52
|
+
ph_load_from_file(ctx, "image2.jpg");
|
|
53
|
+
ph_compute_phash(ctx, &hash2);
|
|
54
|
+
|
|
55
|
+
int distance = ph_hamming_distance(hash1, hash2);
|
|
56
|
+
printf("Hamming Distance: %d\n", distance);
|
|
57
|
+
|
|
58
|
+
if (distance < 5) {
|
|
59
|
+
printf("Images are very similar!\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ph_free(ctx);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## FFI Integration Notes
|
|
68
|
+
|
|
69
|
+
- **Opaque Pointer**: `ph_context_t` is an opaque struct. In high-level languages, treat it as a `void*` or `uintptr_t`.
|
|
70
|
+
- **Memory Management**: Always call `ph_free()` to release image data and context memory allocated on the C heap.
|
|
71
|
+
- **Error Handling**: Functions return `ph_error_t` (int). `0` always indicates `PH_SUCCESS`.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
76
|
+
Includes `stb_image` by Sean Barrett (Public Domain/MIT).
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#ifndef LIBPHASH_H
|
|
2
|
+
#define LIBPHASH_H
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @file libphash.h
|
|
9
|
+
* @brief High-performance, thread-safe perceptual hashing library.
|
|
10
|
+
*
|
|
11
|
+
* Designed for easy FFI integration (Python, Rust, Node.js).
|
|
12
|
+
* All functions are thread-safe provided they operate on different contexts.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// --- Platform & Export Macros ---
|
|
16
|
+
#ifndef PH_API
|
|
17
|
+
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
18
|
+
#ifdef LIBPHASH_EXPORTS
|
|
19
|
+
#define PH_API __declspec(dllexport)
|
|
20
|
+
#else
|
|
21
|
+
#define PH_API __declspec(dllimport)
|
|
22
|
+
#endif
|
|
23
|
+
#else
|
|
24
|
+
#if __GNUC__ >= 4
|
|
25
|
+
#define PH_API __attribute__((visibility("default")))
|
|
26
|
+
#else
|
|
27
|
+
#define PH_API
|
|
28
|
+
#endif
|
|
29
|
+
#endif
|
|
30
|
+
#endif
|
|
31
|
+
|
|
32
|
+
#ifndef PH_NODISCARD
|
|
33
|
+
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
|
|
34
|
+
#define PH_NODISCARD [[nodiscard]]
|
|
35
|
+
#elif defined(__GNUC__) || defined(__clang__)
|
|
36
|
+
#define PH_NODISCARD __attribute__((warn_unused_result))
|
|
37
|
+
#elif defined(_MSC_VER) && _MSC_VER >= 1700
|
|
38
|
+
#define PH_NODISCARD _Check_return_
|
|
39
|
+
#else
|
|
40
|
+
#define PH_NODISCARD
|
|
41
|
+
#endif
|
|
42
|
+
#endif
|
|
43
|
+
|
|
44
|
+
#ifdef __cplusplus
|
|
45
|
+
extern "C" {
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
// --- Constants ---
|
|
49
|
+
|
|
50
|
+
/** Maximum size in bytes for any digest supported by the library (64 bytes =
|
|
51
|
+
* 512 bits). */
|
|
52
|
+
#define PH_DIGEST_MAX_BYTES 64
|
|
53
|
+
|
|
54
|
+
// --- Error Codes ---
|
|
55
|
+
|
|
56
|
+
typedef enum {
|
|
57
|
+
PH_SUCCESS = 0,
|
|
58
|
+
PH_ERR_ALLOCATION_FAILED = -1,
|
|
59
|
+
PH_ERR_DECODE_FAILED = -2,
|
|
60
|
+
PH_ERR_INVALID_ARGUMENT = -3,
|
|
61
|
+
PH_ERR_NOT_IMPLEMENTED = -4,
|
|
62
|
+
PH_ERR_EMPTY_IMAGE = -5,
|
|
63
|
+
} ph_error_t;
|
|
64
|
+
|
|
65
|
+
// --- Types ---
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @brief Opaque context structure holding image data and configuration.
|
|
69
|
+
* Treat this as a void* in FFI.
|
|
70
|
+
*/
|
|
71
|
+
typedef struct ph_context ph_context_t;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @brief A flat structure representing a hash digest.
|
|
75
|
+
*
|
|
76
|
+
* @note This structure is FFI-safe and can be allocated on the stack.
|
|
77
|
+
* It does not own any heap memory.
|
|
78
|
+
*/
|
|
79
|
+
typedef struct {
|
|
80
|
+
uint8_t data[PH_DIGEST_MAX_BYTES]; ///< The raw hash bytes.
|
|
81
|
+
uint8_t size; ///< Number of valid bytes in 'data'.
|
|
82
|
+
uint8_t reserved[7]; ///< Padding for 64-bit alignment.
|
|
83
|
+
} ph_digest_t;
|
|
84
|
+
|
|
85
|
+
// --- Lifecycle & Configuration ---
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @brief Returns the library version string (e.g., "1.2.0").
|
|
89
|
+
*/
|
|
90
|
+
PH_API const char *ph_version(void);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @brief Allocates a new context with default settings (Gamma 2.2).
|
|
94
|
+
* @param[out] out_ctx Pointer to the created context.
|
|
95
|
+
*/
|
|
96
|
+
PH_API PH_NODISCARD ph_error_t ph_create(ph_context_t **out_ctx);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @brief Frees the context and all associated image memory.
|
|
100
|
+
* @param ctx The context to free. Safe to pass NULL.
|
|
101
|
+
*/
|
|
102
|
+
PH_API void ph_free(ph_context_t *ctx);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @brief Sets the gamma correction value for the context.
|
|
106
|
+
*
|
|
107
|
+
* Recomputes the internal lookup table (LUT).
|
|
108
|
+
* Default value is 2.2.
|
|
109
|
+
*
|
|
110
|
+
* @param ctx The context.
|
|
111
|
+
* @param gamma The gamma value (e.g., 2.2). Must be > 0.
|
|
112
|
+
*/
|
|
113
|
+
PH_API void ph_context_set_gamma(ph_context_t *ctx, float gamma);
|
|
114
|
+
|
|
115
|
+
// --- Loading ---
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @brief Loads an image from a file path.
|
|
119
|
+
* @param ctx The context.
|
|
120
|
+
* @param filepath Path to the image file.
|
|
121
|
+
*/
|
|
122
|
+
PH_API PH_NODISCARD ph_error_t ph_load_from_file(ph_context_t *ctx, const char *filepath);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @brief Loads an image from a memory buffer.
|
|
126
|
+
* @param ctx The context.
|
|
127
|
+
* @param buffer Pointer to the raw file data (e.g., JPEG bytes).
|
|
128
|
+
* @param length Size of the buffer.
|
|
129
|
+
*/
|
|
130
|
+
PH_API PH_NODISCARD ph_error_t ph_load_from_memory(ph_context_t *ctx, const uint8_t *buffer,
|
|
131
|
+
size_t length);
|
|
132
|
+
|
|
133
|
+
// --- uint64_t Hash Algorithms ---
|
|
134
|
+
|
|
135
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_ahash(ph_context_t *ctx, uint64_t *out_hash);
|
|
136
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_dhash(ph_context_t *ctx, uint64_t *out_hash);
|
|
137
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_phash(ph_context_t *ctx, uint64_t *out_hash);
|
|
138
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_whash(ph_context_t *ctx, uint64_t *out_hash);
|
|
139
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_mhash(ph_context_t *ctx, uint64_t *out_hash);
|
|
140
|
+
|
|
141
|
+
// --- Digest Hash Algorithms ---
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @brief Computes Block Mean Hash (BMH). Returns a 256-bit (32-byte) digest.
|
|
145
|
+
*/
|
|
146
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_bmh(ph_context_t *ctx, ph_digest_t *out_digest);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @brief Computes Color Hash. Returns a digest representing color distribution.
|
|
150
|
+
*/
|
|
151
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_color_hash(ph_context_t *ctx, ph_digest_t *out_digest);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @brief Computes Radial Hash. Robust against rotation. Uses context Gamma.
|
|
155
|
+
*/
|
|
156
|
+
PH_API PH_NODISCARD ph_error_t ph_compute_radial_hash(ph_context_t *ctx, ph_digest_t *out_digest);
|
|
157
|
+
|
|
158
|
+
// --- Comparison Functions ---
|
|
159
|
+
|
|
160
|
+
PH_API int ph_hamming_distance(uint64_t hash1, uint64_t hash2);
|
|
161
|
+
PH_API int ph_hamming_distance_digest(const ph_digest_t *a, const ph_digest_t *b);
|
|
162
|
+
PH_API double ph_l2_distance(const ph_digest_t *a, const ph_digest_t *b);
|
|
163
|
+
|
|
164
|
+
#ifdef __cplusplus
|
|
165
|
+
}
|
|
166
|
+
#endif
|
|
167
|
+
|
|
168
|
+
#endif // LIBPHASH_H
|