python-libphash 1.1.0__tar.gz → 1.2.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.1.0/src/python_libphash.egg-info → python_libphash-1.2.0}/PKG-INFO +42 -3
- {python_libphash-1.1.0 → python_libphash-1.2.0}/README.md +41 -2
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/Makefile +6 -2
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/README.md +11 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/include/libphash.h +28 -1
- python_libphash-1.2.0/native/libphash/src/core.c +154 -0
- python_libphash-1.2.0/native/libphash/src/hashes/ahash.c +35 -0
- python_libphash-1.2.0/native/libphash/src/hashes/bmh.c +51 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/src/hashes/color_hash.c +13 -14
- python_libphash-1.2.0/native/libphash/src/hashes/dhash.c +32 -0
- python_libphash-1.2.0/native/libphash/src/hashes/mhash.c +47 -0
- python_libphash-1.2.0/native/libphash/src/hashes/phash.c +99 -0
- python_libphash-1.2.0/native/libphash/src/hashes/radial.c +113 -0
- python_libphash-1.2.0/native/libphash/src/hashes/whash.c +64 -0
- python_libphash-1.2.0/native/libphash/src/image.c +193 -0
- python_libphash-1.2.0/native/libphash/src/internal.h +105 -0
- python_libphash-1.2.0/native/libphash/tests/benchmark.c +79 -0
- python_libphash-1.2.0/native/libphash/tests/test_config.c +96 -0
- python_libphash-1.2.0/native/libphash/tests/test_context_reuse.c +56 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_internal.c +1 -1
- {python_libphash-1.1.0 → python_libphash-1.2.0}/pyproject.toml +1 -1
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/__init__.py +1 -1
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/_build.py +4 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/context.py +13 -1
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/utils.py +1 -1
- {python_libphash-1.1.0 → python_libphash-1.2.0/src/python_libphash.egg-info}/PKG-INFO +42 -3
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/SOURCES.txt +4 -1
- python_libphash-1.1.0/native/libphash/src/core.c +0 -82
- python_libphash-1.1.0/native/libphash/src/hashes/ahash.c +0 -32
- python_libphash-1.1.0/native/libphash/src/hashes/bmh.c +0 -36
- python_libphash-1.1.0/native/libphash/src/hashes/dhash.c +0 -28
- python_libphash-1.1.0/native/libphash/src/hashes/mhash.c +0 -33
- python_libphash-1.1.0/native/libphash/src/hashes/phash.c +0 -78
- python_libphash-1.1.0/native/libphash/src/hashes/radial.c +0 -103
- python_libphash-1.1.0/native/libphash/src/hashes/whash.c +0 -53
- python_libphash-1.1.0/native/libphash/src/image.c +0 -105
- python_libphash-1.1.0/native/libphash/src/internal.h +0 -39
- {python_libphash-1.1.0 → python_libphash-1.2.0}/LICENSE +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/MANIFEST.in +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/.clang-format +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/.git +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/.gitignore +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/CMakeLists.txt +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/LICENSE +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/src/hashes/common.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo.jpeg +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo_color_changed.jpeg +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo_copy.jpeg +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo_rotated_90.jpeg +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_bmh.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_color.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_core.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_hashes.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_image_hashes.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_macros.h +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_mhash.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_radial.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_whash.c +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/vendor/stb_image.h +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/setup.cfg +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/setup.py +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/_native.pyi +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/exceptions.py +0 -0
- /python_libphash-1.1.0/src/libphash/types.py → /python_libphash-1.2.0/src/libphash/ph_types.py +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/py.typed +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/dependency_links.txt +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/not-zip-safe +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/requires.txt +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/top_level.txt +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_digests.py +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_distances.py +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_hashes.py +0 -0
- {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_loading.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-libphash
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: High-performance perceptual hashing library (CFFI bindings)
|
|
5
5
|
Author-email: gudoshnikovn <gudoshnikov-na@yandex.ru>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,7 +20,7 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
# python-libphash
|
|
22
22
|
|
|
23
|
-
High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash) v1.
|
|
23
|
+
High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash) v1.6.1, a C library for perceptual image hashing.
|
|
24
24
|
|
|
25
25
|
[](https://opensource.org/licenses/MIT)
|
|
26
26
|
[](https://www.python.org/downloads/)
|
|
@@ -48,15 +48,30 @@ High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/
|
|
|
48
48
|
* A C compiler (GCC/Clang or MSVC)
|
|
49
49
|
* Python 3.8 or higher
|
|
50
50
|
|
|
51
|
+
### Install from PyPI
|
|
52
|
+
```bash
|
|
53
|
+
pip install libphash
|
|
54
|
+
# or using uv
|
|
55
|
+
uv add libphash
|
|
56
|
+
```
|
|
57
|
+
|
|
51
58
|
### Install from source
|
|
52
59
|
```bash
|
|
53
60
|
git clone --recursive https://github.com/yourusername/python-libphash.git
|
|
54
61
|
cd python-libphash
|
|
55
62
|
pip install .
|
|
63
|
+
# or using uv
|
|
64
|
+
uv pip install .
|
|
56
65
|
```
|
|
57
66
|
|
|
58
67
|
## Quick Start
|
|
59
68
|
|
|
69
|
+
### Quick Start (CLI)
|
|
70
|
+
You can quickly compute a hash from the command line after installation:
|
|
71
|
+
```bash
|
|
72
|
+
python -m libphash.utils --path photo.jpg --method phash
|
|
73
|
+
```
|
|
74
|
+
|
|
60
75
|
### Basic Usage
|
|
61
76
|
```python
|
|
62
77
|
from libphash import ImageContext, HashMethod, hamming_distance
|
|
@@ -76,6 +91,26 @@ distance = compare_images("image1.jpg", "image2.jpg", method=HashMethod.PHASH)
|
|
|
76
91
|
print(f"Hamming Distance: {distance}")
|
|
77
92
|
```
|
|
78
93
|
|
|
94
|
+
### Advanced Configuration (New in v1.6.1)
|
|
95
|
+
Fine-tune hashing algorithms for specific use cases. Note that hashes generated with different parameters are **not comparable**.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
with ImageContext("photo.jpg") as ctx:
|
|
99
|
+
# pHash (DCT) resolution
|
|
100
|
+
ctx.set_phash_params(dct_size=32, reduction_size=8)
|
|
101
|
+
|
|
102
|
+
# Radial Hash precision
|
|
103
|
+
ctx.set_radial_params(projections=40, samples=128)
|
|
104
|
+
|
|
105
|
+
# Block-based hashes (BMH) grid resolution
|
|
106
|
+
ctx.set_block_params(block_size=16)
|
|
107
|
+
|
|
108
|
+
# Custom Grayscale weights (R, G, B)
|
|
109
|
+
ctx.set_gray_weights(38, 75, 15)
|
|
110
|
+
|
|
111
|
+
print(f"Custom pHash: {ctx.phash:016x}")
|
|
112
|
+
```
|
|
113
|
+
|
|
79
114
|
### Working with Digests (Advanced Hashes)
|
|
80
115
|
Algorithms like Radial Hash or Color Hash return a `Digest` object instead of a single integer.
|
|
81
116
|
|
|
@@ -101,7 +136,11 @@ with ImageContext("photo_v2.jpg") as ctx2:
|
|
|
101
136
|
### `ImageContext`
|
|
102
137
|
The main class for loading images and computing hashes.
|
|
103
138
|
* `__init__(path=None, bytes_data=None)`: Load an image from a file path or memory.
|
|
104
|
-
* `set_gamma(gamma: float)`: Set gamma correction
|
|
139
|
+
* `set_gamma(gamma: float)`: Set gamma correction.
|
|
140
|
+
* `set_gray_weights(r, g, b)`: Set custom RGB weights for grayscale conversion.
|
|
141
|
+
* `set_phash_params(dct_size, reduction_size)`: Configure pHash DCT resolution.
|
|
142
|
+
* `set_radial_params(projections, samples)`: Configure Radial Hash precision.
|
|
143
|
+
* `set_block_params(block_size)`: Configure BMH/mHash grid resolution.
|
|
105
144
|
* **Properties**: `ahash`, `dhash`, `phash`, `whash`, `mhash` (returns `int`).
|
|
106
145
|
* **Properties**: `bmh`, `color_hash`, `radial_hash` (returns `Digest`).
|
|
107
146
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# python-libphash
|
|
2
2
|
|
|
3
|
-
High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash) v1.
|
|
3
|
+
High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/libphash) v1.6.1, a C library for perceptual image hashing.
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://www.python.org/downloads/)
|
|
@@ -28,15 +28,30 @@ High-performance Python bindings for [libphash](https://github.com/gudoshnikovn/
|
|
|
28
28
|
* A C compiler (GCC/Clang or MSVC)
|
|
29
29
|
* Python 3.8 or higher
|
|
30
30
|
|
|
31
|
+
### Install from PyPI
|
|
32
|
+
```bash
|
|
33
|
+
pip install libphash
|
|
34
|
+
# or using uv
|
|
35
|
+
uv add libphash
|
|
36
|
+
```
|
|
37
|
+
|
|
31
38
|
### Install from source
|
|
32
39
|
```bash
|
|
33
40
|
git clone --recursive https://github.com/yourusername/python-libphash.git
|
|
34
41
|
cd python-libphash
|
|
35
42
|
pip install .
|
|
43
|
+
# or using uv
|
|
44
|
+
uv pip install .
|
|
36
45
|
```
|
|
37
46
|
|
|
38
47
|
## Quick Start
|
|
39
48
|
|
|
49
|
+
### Quick Start (CLI)
|
|
50
|
+
You can quickly compute a hash from the command line after installation:
|
|
51
|
+
```bash
|
|
52
|
+
python -m libphash.utils --path photo.jpg --method phash
|
|
53
|
+
```
|
|
54
|
+
|
|
40
55
|
### Basic Usage
|
|
41
56
|
```python
|
|
42
57
|
from libphash import ImageContext, HashMethod, hamming_distance
|
|
@@ -56,6 +71,26 @@ distance = compare_images("image1.jpg", "image2.jpg", method=HashMethod.PHASH)
|
|
|
56
71
|
print(f"Hamming Distance: {distance}")
|
|
57
72
|
```
|
|
58
73
|
|
|
74
|
+
### Advanced Configuration (New in v1.6.1)
|
|
75
|
+
Fine-tune hashing algorithms for specific use cases. Note that hashes generated with different parameters are **not comparable**.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
with ImageContext("photo.jpg") as ctx:
|
|
79
|
+
# pHash (DCT) resolution
|
|
80
|
+
ctx.set_phash_params(dct_size=32, reduction_size=8)
|
|
81
|
+
|
|
82
|
+
# Radial Hash precision
|
|
83
|
+
ctx.set_radial_params(projections=40, samples=128)
|
|
84
|
+
|
|
85
|
+
# Block-based hashes (BMH) grid resolution
|
|
86
|
+
ctx.set_block_params(block_size=16)
|
|
87
|
+
|
|
88
|
+
# Custom Grayscale weights (R, G, B)
|
|
89
|
+
ctx.set_gray_weights(38, 75, 15)
|
|
90
|
+
|
|
91
|
+
print(f"Custom pHash: {ctx.phash:016x}")
|
|
92
|
+
```
|
|
93
|
+
|
|
59
94
|
### Working with Digests (Advanced Hashes)
|
|
60
95
|
Algorithms like Radial Hash or Color Hash return a `Digest` object instead of a single integer.
|
|
61
96
|
|
|
@@ -81,7 +116,11 @@ with ImageContext("photo_v2.jpg") as ctx2:
|
|
|
81
116
|
### `ImageContext`
|
|
82
117
|
The main class for loading images and computing hashes.
|
|
83
118
|
* `__init__(path=None, bytes_data=None)`: Load an image from a file path or memory.
|
|
84
|
-
* `set_gamma(gamma: float)`: Set gamma correction
|
|
119
|
+
* `set_gamma(gamma: float)`: Set gamma correction.
|
|
120
|
+
* `set_gray_weights(r, g, b)`: Set custom RGB weights for grayscale conversion.
|
|
121
|
+
* `set_phash_params(dct_size, reduction_size)`: Configure pHash DCT resolution.
|
|
122
|
+
* `set_radial_params(projections, samples)`: Configure Radial Hash precision.
|
|
123
|
+
* `set_block_params(block_size)`: Configure BMH/mHash grid resolution.
|
|
85
124
|
* **Properties**: `ahash`, `dhash`, `phash`, `whash`, `mhash` (returns `int`).
|
|
86
125
|
* **Properties**: `bmh`, `color_hash`, `radial_hash` (returns `Digest`).
|
|
87
126
|
|
|
@@ -46,6 +46,10 @@ test: $(TEST_BINS)
|
|
|
46
46
|
@echo "ALL TESTS PASSED"
|
|
47
47
|
|
|
48
48
|
clean:
|
|
49
|
-
rm -rf $(OBJ_DIR) *.a test_*
|
|
49
|
+
rm -rf $(OBJ_DIR) *.a test_* benchmark
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
benchmark: tests/benchmark.c $(LIB_NAME)
|
|
52
|
+
$(CC) $(CFLAGS) $< $(LIB_NAME) -o benchmark $(LDFLAGS)
|
|
53
|
+
./benchmark
|
|
54
|
+
|
|
55
|
+
.PHONY: all debug test clean format benchmark
|
|
@@ -29,6 +29,17 @@ Official and community-supported wrappers:
|
|
|
29
29
|
- **Cross-Platform**: Compatible with GCC, Clang, and MSVC.
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
## Configuration & Tuning
|
|
33
|
+
|
|
34
|
+
libphash allows fine-tuning algorithms for specific use cases via the context API. Note that changing these values will result in hashes that are **not comparable** to those generated with default settings.
|
|
35
|
+
|
|
36
|
+
| Function | Default | Description |
|
|
37
|
+
| :--- | :--- | :--- |
|
|
38
|
+
| `ph_context_set_gamma` | 2.2 | Adjusts brightness normalization. |
|
|
39
|
+
| `ph_context_set_gray_weights` | 38,75,15 | Custom RGB-to-Grayscale proportions. |
|
|
40
|
+
| `ph_context_set_phash_params` | 32, 8 | DCT size and reduction size. |
|
|
41
|
+
| `ph_context_set_radial_params` | 40, 128 | Projection count and radial samples. |
|
|
42
|
+
| `ph_context_set_block_params` | 16 | Grid resolution for BMH and mHash. |
|
|
32
43
|
|
|
33
44
|
## Architecture
|
|
34
45
|
|
|
@@ -112,6 +112,34 @@ PH_API void ph_free(ph_context_t *ctx);
|
|
|
112
112
|
*/
|
|
113
113
|
PH_API void ph_context_set_gamma(ph_context_t *ctx, float gamma);
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* @brief Sets custom RGB-to-Grayscale weights.
|
|
117
|
+
*
|
|
118
|
+
* Input values are automatically normalized to sum to 128 for optimized internal processing.
|
|
119
|
+
* Default is PH_GRAY_R=38, PH_GRAY_G=75, PH_GRAY_B=15.
|
|
120
|
+
*/
|
|
121
|
+
PH_API void ph_context_set_gray_weights(ph_context_t *ctx, int r, int g, int b);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @brief Sets pHash parameters.
|
|
125
|
+
* @param dct_size Size of the DCT matrix (default 32).
|
|
126
|
+
* @param reduction_size Size of the low-frequency coefficients to keep (default 8).
|
|
127
|
+
*/
|
|
128
|
+
PH_API void ph_context_set_phash_params(ph_context_t *ctx, int dct_size, int reduction_size);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @brief Sets Radial Hash parameters.
|
|
132
|
+
* @param projections Number of angular projections (default 40).
|
|
133
|
+
* @param samples Number of radial samples (default 128).
|
|
134
|
+
*/
|
|
135
|
+
PH_API void ph_context_set_radial_params(ph_context_t *ctx, int projections, int samples);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @brief Sets Block-based hash parameters (BMH/mHash).
|
|
139
|
+
* @param block_size Resolution of the grid (default 16).
|
|
140
|
+
*/
|
|
141
|
+
PH_API void ph_context_set_block_params(ph_context_t *ctx, int block_size);
|
|
142
|
+
|
|
115
143
|
// --- Loading ---
|
|
116
144
|
|
|
117
145
|
/**
|
|
@@ -161,7 +189,6 @@ PH_API int ph_hamming_distance(uint64_t hash1, uint64_t hash2);
|
|
|
161
189
|
PH_API int ph_hamming_distance_digest(const ph_digest_t *a, const ph_digest_t *b);
|
|
162
190
|
PH_API double ph_l2_distance(const ph_digest_t *a, const ph_digest_t *b);
|
|
163
191
|
|
|
164
|
-
void init_dct_matrix(void);
|
|
165
192
|
#ifdef __cplusplus
|
|
166
193
|
}
|
|
167
194
|
#endif
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#include "internal.h"
|
|
2
|
+
#include <math.h>
|
|
3
|
+
#include <stdlib.h>
|
|
4
|
+
|
|
5
|
+
#define STB_IMAGE_IMPLEMENTATION
|
|
6
|
+
#include "../vendor/stb_image.h"
|
|
7
|
+
|
|
8
|
+
PH_API const char *ph_version(void) { return "1.5.0"; }
|
|
9
|
+
|
|
10
|
+
PH_API void ph_context_set_gamma(ph_context_t *ctx, float gamma) {
|
|
11
|
+
if (!ctx || gamma <= PH_GAMMA_EPSILON)
|
|
12
|
+
return;
|
|
13
|
+
|
|
14
|
+
ctx->gamma = gamma;
|
|
15
|
+
// Precompute LUT for O(1) access during processing
|
|
16
|
+
for (int i = 0; i < 256; i++) {
|
|
17
|
+
double val = i / 255.0;
|
|
18
|
+
// Standard gamma correction: value^(1/gamma)
|
|
19
|
+
double res = pow(val, 1.0 / (double)gamma) * 255.0;
|
|
20
|
+
ctx->gamma_lut[i] = (uint8_t)(res > 255.0 ? 255.0 : res);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
PH_API void ph_context_set_gray_weights(ph_context_t *ctx, int r, int g, int b) {
|
|
25
|
+
if (!ctx)
|
|
26
|
+
return;
|
|
27
|
+
|
|
28
|
+
int sum = r + g + b;
|
|
29
|
+
if (sum <= 0) {
|
|
30
|
+
// Fallback to defaults if invalid
|
|
31
|
+
ctx->gray_r = PH_GRAY_R;
|
|
32
|
+
ctx->gray_g = PH_GRAY_G;
|
|
33
|
+
ctx->gray_b = PH_GRAY_B;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Normalize to sum 128 for the >> 7 shift
|
|
38
|
+
ctx->gray_r = (r * 128) / sum;
|
|
39
|
+
ctx->gray_g = (g * 128) / sum;
|
|
40
|
+
ctx->gray_b = 128 - ctx->gray_r - ctx->gray_g;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
PH_API void ph_context_set_phash_params(ph_context_t *ctx, int dct_size, int reduction_size) {
|
|
44
|
+
if (!ctx || dct_size <= 0 || reduction_size <= 0 || reduction_size > dct_size)
|
|
45
|
+
return;
|
|
46
|
+
ctx->phash_dct_size = dct_size;
|
|
47
|
+
ctx->phash_reduction_size = reduction_size;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
PH_API void ph_context_set_radial_params(ph_context_t *ctx, int projections, int samples) {
|
|
51
|
+
if (!ctx || projections <= 0 || samples <= 0)
|
|
52
|
+
return;
|
|
53
|
+
ctx->radial_projections = projections;
|
|
54
|
+
ctx->radial_samples = samples;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
PH_API void ph_context_set_block_params(ph_context_t *ctx, int block_size) {
|
|
58
|
+
if (!ctx || block_size <= 0)
|
|
59
|
+
return;
|
|
60
|
+
ctx->block_size = block_size;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
PH_API ph_error_t ph_create(ph_context_t **out_ctx) {
|
|
64
|
+
if (!out_ctx)
|
|
65
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
66
|
+
|
|
67
|
+
ph_context_t *ctx = (ph_context_t *)calloc(1, sizeof(ph_context_t));
|
|
68
|
+
if (!ctx)
|
|
69
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
70
|
+
|
|
71
|
+
ctx->data = NULL;
|
|
72
|
+
ctx->gray_data = NULL;
|
|
73
|
+
ctx->width = 0;
|
|
74
|
+
ctx->height = 0;
|
|
75
|
+
ctx->channels = 0;
|
|
76
|
+
ctx->is_loaded = 0;
|
|
77
|
+
|
|
78
|
+
/* Defaults */
|
|
79
|
+
ctx->gray_r = PH_GRAY_R;
|
|
80
|
+
ctx->gray_g = PH_GRAY_G;
|
|
81
|
+
ctx->gray_b = PH_GRAY_B;
|
|
82
|
+
ctx->phash_dct_size = PH_DCT_SIZE;
|
|
83
|
+
ctx->phash_reduction_size = PH_DCT_REDUCTION_SIZE;
|
|
84
|
+
ctx->radial_projections = PH_RADIAL_PROJECTIONS;
|
|
85
|
+
ctx->radial_samples = PH_RADIAL_SAMPLES;
|
|
86
|
+
ctx->block_size = PH_BLOCK_SIZE;
|
|
87
|
+
|
|
88
|
+
ph_context_set_gamma(ctx, PH_DEFAULT_GAMMA);
|
|
89
|
+
|
|
90
|
+
*out_ctx = ctx;
|
|
91
|
+
return PH_SUCCESS;
|
|
92
|
+
}
|
|
93
|
+
PH_API void ph_free(ph_context_t *ctx) {
|
|
94
|
+
if (ctx) {
|
|
95
|
+
if (ctx->data)
|
|
96
|
+
stbi_image_free(ctx->data);
|
|
97
|
+
if (ctx->gray_data)
|
|
98
|
+
free(ctx->gray_data);
|
|
99
|
+
if (ctx->scratchpad)
|
|
100
|
+
free(ctx->scratchpad);
|
|
101
|
+
free(ctx);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
uint8_t *ph_get_scratchpad(ph_context_t *ctx, size_t size) {
|
|
106
|
+
if (!ctx || size == 0)
|
|
107
|
+
return NULL;
|
|
108
|
+
|
|
109
|
+
if (ctx->scratchpad_size < size) {
|
|
110
|
+
uint8_t *new_ptr = (uint8_t *)realloc(ctx->scratchpad, size);
|
|
111
|
+
if (!new_ptr)
|
|
112
|
+
return NULL;
|
|
113
|
+
ctx->scratchpad = new_ptr;
|
|
114
|
+
ctx->scratchpad_size = size;
|
|
115
|
+
}
|
|
116
|
+
return ctx->scratchpad;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
PH_API ph_error_t ph_load_from_file(ph_context_t *ctx, const char *filepath) {
|
|
120
|
+
if (!ctx || !filepath)
|
|
121
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
122
|
+
if (ctx->data)
|
|
123
|
+
stbi_image_free(ctx->data);
|
|
124
|
+
if (ctx->gray_data) {
|
|
125
|
+
free(ctx->gray_data);
|
|
126
|
+
ctx->gray_data = NULL;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ctx->data = stbi_load(filepath, &ctx->width, &ctx->height, &ctx->channels, 0);
|
|
130
|
+
if (!ctx->data)
|
|
131
|
+
return PH_ERR_DECODE_FAILED;
|
|
132
|
+
|
|
133
|
+
ctx->is_loaded = 1;
|
|
134
|
+
return PH_SUCCESS;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
PH_API ph_error_t ph_load_from_memory(ph_context_t *ctx, const uint8_t *buffer, size_t length) {
|
|
138
|
+
if (!ctx || !buffer || length == 0)
|
|
139
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
140
|
+
if (ctx->data)
|
|
141
|
+
stbi_image_free(ctx->data);
|
|
142
|
+
if (ctx->gray_data) {
|
|
143
|
+
free(ctx->gray_data);
|
|
144
|
+
ctx->gray_data = NULL;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ctx->data =
|
|
148
|
+
stbi_load_from_memory(buffer, (int)length, &ctx->width, &ctx->height, &ctx->channels, 0);
|
|
149
|
+
if (!ctx->data)
|
|
150
|
+
return PH_ERR_DECODE_FAILED;
|
|
151
|
+
|
|
152
|
+
ctx->is_loaded = 1;
|
|
153
|
+
return PH_SUCCESS;
|
|
154
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#include "../internal.h"
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
|
|
4
|
+
PH_API ph_error_t ph_compute_ahash(ph_context_t *ctx, uint64_t *out_hash) {
|
|
5
|
+
if (!ctx || !ctx->is_loaded || !out_hash) {
|
|
6
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
size_t gray_size = (size_t)ctx->width * ctx->height;
|
|
10
|
+
uint8_t *gray_full = ph_get_scratchpad(ctx, gray_size);
|
|
11
|
+
if (!gray_full) {
|
|
12
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
13
|
+
}
|
|
14
|
+
ph_to_grayscale(ctx, ctx->data, ctx->width, ctx->height, ctx->channels, gray_full);
|
|
15
|
+
|
|
16
|
+
uint8_t hash_input[PH_CORE_HASH_SIZE * PH_CORE_HASH_SIZE];
|
|
17
|
+
ph_resize_bilinear(gray_full, ctx->width, ctx->height, hash_input, PH_CORE_HASH_SIZE, PH_CORE_HASH_SIZE);
|
|
18
|
+
|
|
19
|
+
uint64_t total_sum = 0;
|
|
20
|
+
int num_pixels = PH_CORE_HASH_SIZE * PH_CORE_HASH_SIZE;
|
|
21
|
+
for (int i = 0; i < num_pixels; i++) {
|
|
22
|
+
total_sum += hash_input[i];
|
|
23
|
+
}
|
|
24
|
+
uint8_t avg = (uint8_t)(total_sum / num_pixels);
|
|
25
|
+
|
|
26
|
+
uint64_t hash = 0;
|
|
27
|
+
for (int i = 0; i < num_pixels; i++) {
|
|
28
|
+
if (hash_input[i] >= avg) {
|
|
29
|
+
hash |= (1ULL << i);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
*out_hash = hash;
|
|
34
|
+
return PH_SUCCESS;
|
|
35
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#include "../internal.h"
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
PH_API ph_error_t ph_compute_bmh(ph_context_t *ctx, ph_digest_t *out_digest) {
|
|
6
|
+
if (!ctx || !ctx->is_loaded || !out_digest) {
|
|
7
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
int block_size = ctx->block_size;
|
|
11
|
+
int total_pixels = block_size * block_size;
|
|
12
|
+
|
|
13
|
+
// Clear digest and set size
|
|
14
|
+
memset(out_digest, 0, sizeof(ph_digest_t));
|
|
15
|
+
out_digest->size = (uint8_t)((total_pixels + 7) / 8);
|
|
16
|
+
if (out_digest->size > PH_DIGEST_MAX_BYTES)
|
|
17
|
+
out_digest->size = PH_DIGEST_MAX_BYTES;
|
|
18
|
+
|
|
19
|
+
size_t gray_size = (size_t)ctx->width * ctx->height;
|
|
20
|
+
if (!PH_SAFE_ALLOC_SIZE(ctx->width, ctx->height))
|
|
21
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
22
|
+
|
|
23
|
+
/* Scratchpad:
|
|
24
|
+
* 1. gray_full: img_size
|
|
25
|
+
* 2. block_data: total_pixels
|
|
26
|
+
*/
|
|
27
|
+
uint8_t *scratch = ph_get_scratchpad(ctx, gray_size + total_pixels);
|
|
28
|
+
if (!scratch)
|
|
29
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
30
|
+
|
|
31
|
+
uint8_t *full_gray = scratch;
|
|
32
|
+
uint8_t *block_data = scratch + gray_size;
|
|
33
|
+
|
|
34
|
+
ph_to_grayscale(ctx, ctx->data, ctx->width, ctx->height, ctx->channels, full_gray);
|
|
35
|
+
ph_resize_box(full_gray, ctx->width, ctx->height, block_data, block_size, block_size);
|
|
36
|
+
|
|
37
|
+
uint64_t total_sum = 0;
|
|
38
|
+
for (int i = 0; i < total_pixels; i++) {
|
|
39
|
+
total_sum += block_data[i];
|
|
40
|
+
}
|
|
41
|
+
uint8_t avg = (uint8_t)(total_sum / total_pixels);
|
|
42
|
+
|
|
43
|
+
int max_bits = out_digest->size * 8;
|
|
44
|
+
for (int i = 0; i < total_pixels && i < max_bits; i++) {
|
|
45
|
+
if (block_data[i] >= avg) {
|
|
46
|
+
out_digest->data[i / 8] |= (1 << (i % 8));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return PH_SUCCESS;
|
|
51
|
+
}
|
|
@@ -8,48 +8,47 @@ PH_API ph_error_t ph_compute_color_hash(ph_context_t *ctx, ph_digest_t *out_dige
|
|
|
8
8
|
return PH_ERR_INVALID_ARGUMENT;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/* We calculate
|
|
12
|
-
*
|
|
11
|
+
/* We calculate color moments for RGB channels.
|
|
12
|
+
* Moments are stored as bytes: [MeanR, StdDevR, SkewR, MeanG, ...]
|
|
13
13
|
*/
|
|
14
14
|
memset(out_digest, 0, sizeof(ph_digest_t));
|
|
15
|
-
out_digest->size =
|
|
15
|
+
out_digest->size = PH_COLOR_CHANNELS * PH_COLOR_MOMENTS;
|
|
16
16
|
|
|
17
|
-
double mean[
|
|
17
|
+
double mean[PH_COLOR_CHANNELS] = {0}, std_dev[PH_COLOR_CHANNELS] = {0}, skew[PH_COLOR_CHANNELS] = {0};
|
|
18
18
|
int num_pixels = ctx->width * ctx->height;
|
|
19
19
|
|
|
20
20
|
/* Step 1: Calculate the Arithmetic Mean */
|
|
21
21
|
for (int i = 0; i < num_pixels; i++) {
|
|
22
|
-
for (int c = 0; c <
|
|
22
|
+
for (int c = 0; c < PH_COLOR_CHANNELS; c++) {
|
|
23
23
|
mean[c] += ctx->data[i * ctx->channels + c];
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
for (int c = 0; c <
|
|
26
|
+
for (int c = 0; c < PH_COLOR_CHANNELS; c++) {
|
|
27
27
|
mean[c] /= num_pixels;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/* Step 2: Calculate Standard Deviation (2nd moment) and Skewness (3rd moment)
|
|
31
|
-
*/
|
|
30
|
+
/* Step 2: Calculate Standard Deviation (2nd moment) and Skewness (3rd moment) */
|
|
32
31
|
for (int i = 0; i < num_pixels; i++) {
|
|
33
|
-
for (int c = 0; c <
|
|
32
|
+
for (int c = 0; c < PH_COLOR_CHANNELS; c++) {
|
|
34
33
|
double diff = ctx->data[i * ctx->channels + c] - mean[c];
|
|
35
34
|
std_dev[c] += diff * diff;
|
|
36
35
|
skew[c] += diff * diff * diff;
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
for (int c = 0; c <
|
|
39
|
+
for (int c = 0; c < PH_COLOR_CHANNELS; c++) {
|
|
41
40
|
/* Standard deviation normalization */
|
|
42
41
|
std_dev[c] = sqrt(std_dev[c] / num_pixels);
|
|
43
42
|
|
|
44
43
|
/* Cube root for skewness normalization */
|
|
45
44
|
skew[c] = cbrt(skew[c] / num_pixels);
|
|
46
45
|
|
|
47
|
-
/*
|
|
46
|
+
/* Step 3: Write to digest.
|
|
48
47
|
* Moments are mapped to 0-255 range and stored as bytes.
|
|
49
48
|
*/
|
|
50
|
-
out_digest->data[c *
|
|
51
|
-
out_digest->data[c *
|
|
52
|
-
out_digest->data[c *
|
|
49
|
+
out_digest->data[c * PH_COLOR_MOMENTS + 0] = (uint8_t)mean[c];
|
|
50
|
+
out_digest->data[c * PH_COLOR_MOMENTS + 1] = (uint8_t)fmin(255.0, std_dev[c]);
|
|
51
|
+
out_digest->data[c * PH_COLOR_MOMENTS + 2] = (uint8_t)fmin(255.0, fabs(skew[c]));
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
return PH_SUCCESS;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#include "../internal.h"
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
|
|
4
|
+
PH_API ph_error_t ph_compute_dhash(ph_context_t *ctx, uint64_t *out_hash) {
|
|
5
|
+
if (!ctx || !ctx->is_loaded || !out_hash) {
|
|
6
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
size_t gray_size = (size_t)ctx->width * ctx->height;
|
|
10
|
+
uint8_t *gray_full = ph_get_scratchpad(ctx, gray_size);
|
|
11
|
+
if (!gray_full) {
|
|
12
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
13
|
+
}
|
|
14
|
+
ph_to_grayscale(ctx, ctx->data, ctx->width, ctx->height, ctx->channels, gray_full);
|
|
15
|
+
|
|
16
|
+
uint8_t hash_input[(PH_CORE_HASH_SIZE + 1) * PH_CORE_HASH_SIZE];
|
|
17
|
+
ph_resize_bilinear(gray_full, ctx->width, ctx->height, hash_input, PH_CORE_HASH_SIZE + 1,
|
|
18
|
+
PH_CORE_HASH_SIZE);
|
|
19
|
+
|
|
20
|
+
uint64_t hash = 0;
|
|
21
|
+
for (int row = 0; row < PH_CORE_HASH_SIZE; row++) {
|
|
22
|
+
for (int col = 0; col < PH_CORE_HASH_SIZE; col++) {
|
|
23
|
+
if (hash_input[row * (PH_CORE_HASH_SIZE + 1) + col] <
|
|
24
|
+
hash_input[row * (PH_CORE_HASH_SIZE + 1) + col + 1]) {
|
|
25
|
+
hash |= (1ULL << (row * PH_CORE_HASH_SIZE + col));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
*out_hash = hash;
|
|
31
|
+
return PH_SUCCESS;
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#include "../internal.h"
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
|
|
4
|
+
PH_API ph_error_t ph_compute_mhash(ph_context_t *ctx, uint64_t *out_hash) {
|
|
5
|
+
if (!ctx || !ctx->is_loaded || !out_hash)
|
|
6
|
+
return PH_ERR_INVALID_ARGUMENT;
|
|
7
|
+
|
|
8
|
+
int block_size = ctx->block_size;
|
|
9
|
+
size_t gray_size = (size_t)ctx->width * ctx->height;
|
|
10
|
+
if (!PH_SAFE_ALLOC_SIZE(ctx->width, ctx->height))
|
|
11
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
12
|
+
|
|
13
|
+
/* Use scratchpad for:
|
|
14
|
+
* 1. full_gray: img_size
|
|
15
|
+
* 2. block_data: block_size * block_size
|
|
16
|
+
*/
|
|
17
|
+
uint8_t *scratch = ph_get_scratchpad(ctx, gray_size + (size_t)block_size * block_size);
|
|
18
|
+
if (!scratch)
|
|
19
|
+
return PH_ERR_ALLOCATION_FAILED;
|
|
20
|
+
|
|
21
|
+
uint8_t *full_gray = scratch;
|
|
22
|
+
uint8_t *block_data = scratch + gray_size;
|
|
23
|
+
|
|
24
|
+
ph_to_grayscale(ctx, ctx->data, ctx->width, ctx->height, ctx->channels, full_gray);
|
|
25
|
+
ph_resize_box(full_gray, ctx->width, ctx->height, block_data, block_size, block_size);
|
|
26
|
+
|
|
27
|
+
// 2. Simple 3x3 Laplacian Kernel for edge detection
|
|
28
|
+
uint64_t hash = 0;
|
|
29
|
+
int bit_idx = 0;
|
|
30
|
+
|
|
31
|
+
/* We sample the edges. For 16x16 block, we had a 7x7 grid (step=2).
|
|
32
|
+
* To fit into uint64_t, we need at most 64 bits (8x8).
|
|
33
|
+
*/
|
|
34
|
+
int step = (block_size > 8) ? 2 : 1;
|
|
35
|
+
for (int y = 1; y < block_size - 1 && bit_idx < 64; y += step) {
|
|
36
|
+
for (int x = 1; x < block_size - 1 && bit_idx < 64; x += step) {
|
|
37
|
+
int center = block_data[y * block_size + x] * 4;
|
|
38
|
+
int neighbors = block_data[(y - 1) * block_size + x] + block_data[(y + 1) * block_size + x] +
|
|
39
|
+
block_data[y * block_size + (x - 1)] + block_data[y * block_size + (x + 1)];
|
|
40
|
+
if (center - neighbors > 0)
|
|
41
|
+
hash |= (1ULL << bit_idx);
|
|
42
|
+
bit_idx++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
*out_hash = hash;
|
|
46
|
+
return PH_SUCCESS;
|
|
47
|
+
}
|