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.
Files changed (73) hide show
  1. {python_libphash-1.1.0/src/python_libphash.egg-info → python_libphash-1.2.0}/PKG-INFO +42 -3
  2. {python_libphash-1.1.0 → python_libphash-1.2.0}/README.md +41 -2
  3. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/Makefile +6 -2
  4. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/README.md +11 -0
  5. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/include/libphash.h +28 -1
  6. python_libphash-1.2.0/native/libphash/src/core.c +154 -0
  7. python_libphash-1.2.0/native/libphash/src/hashes/ahash.c +35 -0
  8. python_libphash-1.2.0/native/libphash/src/hashes/bmh.c +51 -0
  9. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/src/hashes/color_hash.c +13 -14
  10. python_libphash-1.2.0/native/libphash/src/hashes/dhash.c +32 -0
  11. python_libphash-1.2.0/native/libphash/src/hashes/mhash.c +47 -0
  12. python_libphash-1.2.0/native/libphash/src/hashes/phash.c +99 -0
  13. python_libphash-1.2.0/native/libphash/src/hashes/radial.c +113 -0
  14. python_libphash-1.2.0/native/libphash/src/hashes/whash.c +64 -0
  15. python_libphash-1.2.0/native/libphash/src/image.c +193 -0
  16. python_libphash-1.2.0/native/libphash/src/internal.h +105 -0
  17. python_libphash-1.2.0/native/libphash/tests/benchmark.c +79 -0
  18. python_libphash-1.2.0/native/libphash/tests/test_config.c +96 -0
  19. python_libphash-1.2.0/native/libphash/tests/test_context_reuse.c +56 -0
  20. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_internal.c +1 -1
  21. {python_libphash-1.1.0 → python_libphash-1.2.0}/pyproject.toml +1 -1
  22. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/__init__.py +1 -1
  23. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/_build.py +4 -0
  24. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/context.py +13 -1
  25. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/utils.py +1 -1
  26. {python_libphash-1.1.0 → python_libphash-1.2.0/src/python_libphash.egg-info}/PKG-INFO +42 -3
  27. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/SOURCES.txt +4 -1
  28. python_libphash-1.1.0/native/libphash/src/core.c +0 -82
  29. python_libphash-1.1.0/native/libphash/src/hashes/ahash.c +0 -32
  30. python_libphash-1.1.0/native/libphash/src/hashes/bmh.c +0 -36
  31. python_libphash-1.1.0/native/libphash/src/hashes/dhash.c +0 -28
  32. python_libphash-1.1.0/native/libphash/src/hashes/mhash.c +0 -33
  33. python_libphash-1.1.0/native/libphash/src/hashes/phash.c +0 -78
  34. python_libphash-1.1.0/native/libphash/src/hashes/radial.c +0 -103
  35. python_libphash-1.1.0/native/libphash/src/hashes/whash.c +0 -53
  36. python_libphash-1.1.0/native/libphash/src/image.c +0 -105
  37. python_libphash-1.1.0/native/libphash/src/internal.h +0 -39
  38. {python_libphash-1.1.0 → python_libphash-1.2.0}/LICENSE +0 -0
  39. {python_libphash-1.1.0 → python_libphash-1.2.0}/MANIFEST.in +0 -0
  40. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/.clang-format +0 -0
  41. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/.git +0 -0
  42. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/.gitignore +0 -0
  43. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/CMakeLists.txt +0 -0
  44. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/LICENSE +0 -0
  45. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/src/hashes/common.c +0 -0
  46. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo.jpeg +0 -0
  47. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo_color_changed.jpeg +0 -0
  48. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo_copy.jpeg +0 -0
  49. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/photo_rotated_90.jpeg +0 -0
  50. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_bmh.c +0 -0
  51. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_color.c +0 -0
  52. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_core.c +0 -0
  53. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_hashes.c +0 -0
  54. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_image_hashes.c +0 -0
  55. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_macros.h +0 -0
  56. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_mhash.c +0 -0
  57. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_radial.c +0 -0
  58. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/tests/test_whash.c +0 -0
  59. {python_libphash-1.1.0 → python_libphash-1.2.0}/native/libphash/vendor/stb_image.h +0 -0
  60. {python_libphash-1.1.0 → python_libphash-1.2.0}/setup.cfg +0 -0
  61. {python_libphash-1.1.0 → python_libphash-1.2.0}/setup.py +0 -0
  62. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/_native.pyi +0 -0
  63. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/exceptions.py +0 -0
  64. /python_libphash-1.1.0/src/libphash/types.py → /python_libphash-1.2.0/src/libphash/ph_types.py +0 -0
  65. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/libphash/py.typed +0 -0
  66. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/dependency_links.txt +0 -0
  67. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/not-zip-safe +0 -0
  68. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/requires.txt +0 -0
  69. {python_libphash-1.1.0 → python_libphash-1.2.0}/src/python_libphash.egg-info/top_level.txt +0 -0
  70. {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_digests.py +0 -0
  71. {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_distances.py +0 -0
  72. {python_libphash-1.1.0 → python_libphash-1.2.0}/tests/test_hashes.py +0 -0
  73. {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.1.0
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.3.0, a C library for perceptual image hashing.
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
26
26
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](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 (useful for Radial Hash).
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.0, a C library for perceptual image hashing.
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](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 (useful for Radial Hash).
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
- .PHONY: all debug test clean format
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 3 color moments for 3 channels (R, G, B) = 9 values total.
12
- * Each value is stored as 1 byte (9 bytes required).
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 = 9; // Set the actual size in bytes.
15
+ out_digest->size = PH_COLOR_CHANNELS * PH_COLOR_MOMENTS;
16
16
 
17
- double mean[3] = {0}, std_dev[3] = {0}, skew[3] = {0};
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 < 3; 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 < 3; 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 < 3; 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 < 3; 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
- /* * Step 3: Write to digest.
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 * 3 + 0] = (uint8_t)mean[c];
51
- out_digest->data[c * 3 + 1] = (uint8_t)fmin(255.0, std_dev[c]);
52
- out_digest->data[c * 3 + 2] = (uint8_t)fmin(255.0, fabs(skew[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
+ }