arthash 0.1.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 (55) hide show
  1. arthash-0.1.0/PKG-INFO +66 -0
  2. arthash-0.1.0/README.md +45 -0
  3. arthash-0.1.0/arthash-py/Cargo.lock +237 -0
  4. arthash-0.1.0/arthash-py/Cargo.toml +21 -0
  5. arthash-0.1.0/arthash-py/README.md +45 -0
  6. arthash-0.1.0/arthash-py/bench_binding.py +126 -0
  7. arthash-0.1.0/arthash-py/src/lib.rs +235 -0
  8. arthash-0.1.0/arthash-py/tests/__init__.py +0 -0
  9. arthash-0.1.0/arthash-py/tests/conftest.py +36 -0
  10. arthash-0.1.0/arthash-py/tests/generate_vectors.py +205 -0
  11. arthash-0.1.0/arthash-py/tests/test_codec.py +143 -0
  12. arthash-0.1.0/arthash-py/tests/test_search_options.py +105 -0
  13. arthash-0.1.0/arthash-py/tests/test_shape_roundtrip.py +113 -0
  14. arthash-0.1.0/arthash-py/tests/test_svg.py +164 -0
  15. arthash-0.1.0/arthash-py/tests/test_v4_roundtrip.py +73 -0
  16. arthash-0.1.0/arthash-py/tests/test_vectors.py +93 -0
  17. arthash-0.1.0/arthash-py/uv.lock +8 -0
  18. arthash-0.1.0/arthash-rs/Cargo.lock +271 -0
  19. arthash-0.1.0/arthash-rs/Cargo.toml +38 -0
  20. arthash-0.1.0/arthash-rs/README.md +81 -0
  21. arthash-0.1.0/arthash-rs/examples/bench.rs +156 -0
  22. arthash-0.1.0/arthash-rs/examples/bench_hillclimb.rs +280 -0
  23. arthash-0.1.0/arthash-rs/src/api.rs +240 -0
  24. arthash-0.1.0/arthash-rs/src/bitio.rs +86 -0
  25. arthash-0.1.0/arthash-rs/src/codec.rs +158 -0
  26. arthash-0.1.0/arthash-rs/src/colorspace.rs +27 -0
  27. arthash-0.1.0/arthash-rs/src/dct/colorspace.rs +115 -0
  28. arthash-0.1.0/arthash-rs/src/dct/decode.rs +276 -0
  29. arthash-0.1.0/arthash-rs/src/dct/encode.rs +367 -0
  30. arthash-0.1.0/arthash-rs/src/dct/mod.rs +8 -0
  31. arthash-0.1.0/arthash-rs/src/lib.rs +44 -0
  32. arthash-0.1.0/arthash-rs/src/shape/circle.rs +423 -0
  33. arthash-0.1.0/arthash-rs/src/shape/integral.rs +611 -0
  34. arthash-0.1.0/arthash-rs/src/shape/integral2d.rs +280 -0
  35. arthash-0.1.0/arthash-rs/src/shape/mod.rs +25 -0
  36. arthash-0.1.0/arthash-rs/src/shape/options.rs +67 -0
  37. arthash-0.1.0/arthash-rs/src/shape/pixel.rs +223 -0
  38. arthash-0.1.0/arthash-rs/src/shape/quant.rs +191 -0
  39. arthash-0.1.0/arthash-rs/src/shape/raster.rs +536 -0
  40. arthash-0.1.0/arthash-rs/src/shape/rect.rs +288 -0
  41. arthash-0.1.0/arthash-rs/src/shape/residual.rs +140 -0
  42. arthash-0.1.0/arthash-rs/src/shape/rng.rs +111 -0
  43. arthash-0.1.0/arthash-rs/src/shape/rotrect.rs +313 -0
  44. arthash-0.1.0/arthash-rs/src/shape/square.rs +271 -0
  45. arthash-0.1.0/arthash-rs/src/shape/svg.rs +505 -0
  46. arthash-0.1.0/arthash-rs/src/shape/triangle.rs +507 -0
  47. arthash-0.1.0/arthash-rs/tests/vectors.rs +337 -0
  48. arthash-0.1.0/pyproject.toml +35 -0
  49. arthash-0.1.0/python/arthash/__about__.py +1 -0
  50. arthash-0.1.0/python/arthash/__init__.py +43 -0
  51. arthash-0.1.0/python/arthash/_api.py +153 -0
  52. arthash-0.1.0/python/arthash/_codec.py +168 -0
  53. arthash-0.1.0/python/arthash/_colorspace.py +18 -0
  54. arthash-0.1.0/python/arthash/_search.py +38 -0
  55. arthash-0.1.0/python/arthash/palettes.py +131 -0
arthash-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: arthash
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3.11
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Classifier: Programming Language :: Rust
10
+ Classifier: Topic :: Multimedia :: Graphics
11
+ Requires-Dist: numpy>=2.0
12
+ Requires-Dist: pillow>=11.0
13
+ Summary: Placeholder-image hash family — DCT / CIRCLE / TRIANGLE / PIXEL modes share a unified Codec API. PyO3 binding to arthash-rs.
14
+ Keywords: thumbhash,placeholder,image,hash,perceptual,sqip,primitive
15
+ Author-email: Jianqi Pan <jannchie@gmail.com>
16
+ License: MIT
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
19
+ Project-URL: Source, https://github.com/Jannchie/arthash
20
+
21
+ # arthash
22
+
23
+ Placeholder-image hash family. Four modes share a unified Codec API:
24
+
25
+ | Shape | Bytes (typical) | Notes |
26
+ |----------|-----------------|-------|
27
+ | DCT | ~21 B | ThumbHash V4 derivative. Default. |
28
+ | CIRCLE | varies | SQIP-style overlapping circles. |
29
+ | TRIANGLE | varies | fogleman/primitive-style triangle mosaic. |
30
+ | PIXEL | varies | Retro-palette pixel mosaic. |
31
+
32
+ The implementation is a thin Python wrapper around the `arthash-rs` Rust crate
33
+ exposed via PyO3 — encode/decode/SVG all run in native code.
34
+
35
+ ## Install (from source)
36
+
37
+ ```bash
38
+ maturin develop --uv -m packages/arthash-py/Cargo.toml
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ from arthash import encode, decode, to_svg, Codec, ShapeType
45
+
46
+ # DCT (default)
47
+ h = encode("photo.jpg")
48
+ w, hh, rgba = decode(h, base_size=256)
49
+
50
+ # Shape modes
51
+ codec = Codec(shape=ShapeType.CIRCLE, n_shapes=12)
52
+ h = encode("photo.jpg", codec, seed=0)
53
+ w, hh, rgb = decode(h, codec, base_size=256) # (h, w, 3) RGB ndarray
54
+ svg = to_svg(h, codec, base_size=256, blur=8.0) # circle/triangle only
55
+ ```
56
+
57
+ ## Layout
58
+
59
+ - `python/arthash/` — public Python API (`Codec`, `ShapeType`, `SearchOptions`,
60
+ `palettes`, `encode`, `decode`, `to_svg`).
61
+ - `src/lib.rs` — PyO3 binding to the `arthash` Rust crate. Compiled into
62
+ `arthash._native`.
63
+ - `tests/` — pytest suite covering codec validation, V4 round-trip, shape
64
+ round-trip, SVG generation, search-options, and the cross-language test
65
+ vectors at `docs/test-vectors/vectors.json`.
66
+
@@ -0,0 +1,45 @@
1
+ # arthash
2
+
3
+ Placeholder-image hash family. Four modes share a unified Codec API:
4
+
5
+ | Shape | Bytes (typical) | Notes |
6
+ |----------|-----------------|-------|
7
+ | DCT | ~21 B | ThumbHash V4 derivative. Default. |
8
+ | CIRCLE | varies | SQIP-style overlapping circles. |
9
+ | TRIANGLE | varies | fogleman/primitive-style triangle mosaic. |
10
+ | PIXEL | varies | Retro-palette pixel mosaic. |
11
+
12
+ The implementation is a thin Python wrapper around the `arthash-rs` Rust crate
13
+ exposed via PyO3 — encode/decode/SVG all run in native code.
14
+
15
+ ## Install (from source)
16
+
17
+ ```bash
18
+ maturin develop --uv -m packages/arthash-py/Cargo.toml
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ```python
24
+ from arthash import encode, decode, to_svg, Codec, ShapeType
25
+
26
+ # DCT (default)
27
+ h = encode("photo.jpg")
28
+ w, hh, rgba = decode(h, base_size=256)
29
+
30
+ # Shape modes
31
+ codec = Codec(shape=ShapeType.CIRCLE, n_shapes=12)
32
+ h = encode("photo.jpg", codec, seed=0)
33
+ w, hh, rgb = decode(h, codec, base_size=256) # (h, w, 3) RGB ndarray
34
+ svg = to_svg(h, codec, base_size=256, blur=8.0) # circle/triangle only
35
+ ```
36
+
37
+ ## Layout
38
+
39
+ - `python/arthash/` — public Python API (`Codec`, `ShapeType`, `SearchOptions`,
40
+ `palettes`, `encode`, `decode`, `to_svg`).
41
+ - `src/lib.rs` — PyO3 binding to the `arthash` Rust crate. Compiled into
42
+ `arthash._native`.
43
+ - `tests/` — pytest suite covering codec validation, V4 round-trip, shape
44
+ round-trip, SVG generation, search-options, and the cross-language test
45
+ vectors at `docs/test-vectors/vectors.json`.
@@ -0,0 +1,237 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "arthash"
7
+ version = "0.1.0"
8
+ dependencies = [
9
+ "matrixmultiply",
10
+ ]
11
+
12
+ [[package]]
13
+ name = "arthash-py"
14
+ version = "0.1.0"
15
+ dependencies = [
16
+ "arthash",
17
+ "numpy",
18
+ "pyo3",
19
+ ]
20
+
21
+ [[package]]
22
+ name = "autocfg"
23
+ version = "1.5.0"
24
+ source = "registry+https://github.com/rust-lang/crates.io-index"
25
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
26
+
27
+ [[package]]
28
+ name = "heck"
29
+ version = "0.5.0"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
32
+
33
+ [[package]]
34
+ name = "libc"
35
+ version = "0.2.186"
36
+ source = "registry+https://github.com/rust-lang/crates.io-index"
37
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
38
+
39
+ [[package]]
40
+ name = "matrixmultiply"
41
+ version = "0.3.10"
42
+ source = "registry+https://github.com/rust-lang/crates.io-index"
43
+ checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
44
+ dependencies = [
45
+ "autocfg",
46
+ "rawpointer",
47
+ ]
48
+
49
+ [[package]]
50
+ name = "ndarray"
51
+ version = "0.17.2"
52
+ source = "registry+https://github.com/rust-lang/crates.io-index"
53
+ checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
54
+ dependencies = [
55
+ "matrixmultiply",
56
+ "num-complex",
57
+ "num-integer",
58
+ "num-traits",
59
+ "portable-atomic",
60
+ "portable-atomic-util",
61
+ "rawpointer",
62
+ ]
63
+
64
+ [[package]]
65
+ name = "num-complex"
66
+ version = "0.4.6"
67
+ source = "registry+https://github.com/rust-lang/crates.io-index"
68
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
69
+ dependencies = [
70
+ "num-traits",
71
+ ]
72
+
73
+ [[package]]
74
+ name = "num-integer"
75
+ version = "0.1.46"
76
+ source = "registry+https://github.com/rust-lang/crates.io-index"
77
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
78
+ dependencies = [
79
+ "num-traits",
80
+ ]
81
+
82
+ [[package]]
83
+ name = "num-traits"
84
+ version = "0.2.19"
85
+ source = "registry+https://github.com/rust-lang/crates.io-index"
86
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
87
+ dependencies = [
88
+ "autocfg",
89
+ ]
90
+
91
+ [[package]]
92
+ name = "numpy"
93
+ version = "0.28.0"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "778da78c64ddc928ebf5ad9df5edf0789410ff3bdbf3619aed51cd789a6af1e2"
96
+ dependencies = [
97
+ "libc",
98
+ "ndarray",
99
+ "num-complex",
100
+ "num-integer",
101
+ "num-traits",
102
+ "pyo3",
103
+ "pyo3-build-config",
104
+ "rustc-hash",
105
+ ]
106
+
107
+ [[package]]
108
+ name = "once_cell"
109
+ version = "1.21.4"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
112
+
113
+ [[package]]
114
+ name = "portable-atomic"
115
+ version = "1.13.1"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
118
+
119
+ [[package]]
120
+ name = "portable-atomic-util"
121
+ version = "0.2.7"
122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
123
+ checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
124
+ dependencies = [
125
+ "portable-atomic",
126
+ ]
127
+
128
+ [[package]]
129
+ name = "proc-macro2"
130
+ version = "1.0.106"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
133
+ dependencies = [
134
+ "unicode-ident",
135
+ ]
136
+
137
+ [[package]]
138
+ name = "pyo3"
139
+ version = "0.28.3"
140
+ source = "registry+https://github.com/rust-lang/crates.io-index"
141
+ checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
142
+ dependencies = [
143
+ "libc",
144
+ "once_cell",
145
+ "portable-atomic",
146
+ "pyo3-build-config",
147
+ "pyo3-ffi",
148
+ "pyo3-macros",
149
+ ]
150
+
151
+ [[package]]
152
+ name = "pyo3-build-config"
153
+ version = "0.28.3"
154
+ source = "registry+https://github.com/rust-lang/crates.io-index"
155
+ checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
156
+ dependencies = [
157
+ "target-lexicon",
158
+ ]
159
+
160
+ [[package]]
161
+ name = "pyo3-ffi"
162
+ version = "0.28.3"
163
+ source = "registry+https://github.com/rust-lang/crates.io-index"
164
+ checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
165
+ dependencies = [
166
+ "libc",
167
+ "pyo3-build-config",
168
+ ]
169
+
170
+ [[package]]
171
+ name = "pyo3-macros"
172
+ version = "0.28.3"
173
+ source = "registry+https://github.com/rust-lang/crates.io-index"
174
+ checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
175
+ dependencies = [
176
+ "proc-macro2",
177
+ "pyo3-macros-backend",
178
+ "quote",
179
+ "syn",
180
+ ]
181
+
182
+ [[package]]
183
+ name = "pyo3-macros-backend"
184
+ version = "0.28.3"
185
+ source = "registry+https://github.com/rust-lang/crates.io-index"
186
+ checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
187
+ dependencies = [
188
+ "heck",
189
+ "proc-macro2",
190
+ "pyo3-build-config",
191
+ "quote",
192
+ "syn",
193
+ ]
194
+
195
+ [[package]]
196
+ name = "quote"
197
+ version = "1.0.45"
198
+ source = "registry+https://github.com/rust-lang/crates.io-index"
199
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
200
+ dependencies = [
201
+ "proc-macro2",
202
+ ]
203
+
204
+ [[package]]
205
+ name = "rawpointer"
206
+ version = "0.2.1"
207
+ source = "registry+https://github.com/rust-lang/crates.io-index"
208
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
209
+
210
+ [[package]]
211
+ name = "rustc-hash"
212
+ version = "2.1.2"
213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
214
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
215
+
216
+ [[package]]
217
+ name = "syn"
218
+ version = "2.0.117"
219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
220
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
221
+ dependencies = [
222
+ "proc-macro2",
223
+ "quote",
224
+ "unicode-ident",
225
+ ]
226
+
227
+ [[package]]
228
+ name = "target-lexicon"
229
+ version = "0.13.5"
230
+ source = "registry+https://github.com/rust-lang/crates.io-index"
231
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
232
+
233
+ [[package]]
234
+ name = "unicode-ident"
235
+ version = "1.0.24"
236
+ source = "registry+https://github.com/rust-lang/crates.io-index"
237
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "arthash-py"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ license = "MIT"
6
+ description = "Python bindings (PyO3) for arthash-rs."
7
+ publish = false
8
+ readme = "README.md"
9
+
10
+ [lib]
11
+ name = "_native"
12
+ crate-type = ["cdylib"]
13
+
14
+ [dependencies]
15
+ arthash = { path = "../arthash-rs" }
16
+ pyo3 = { version = "0.28", features = ["extension-module", "abi3-py39"] }
17
+ numpy = "0.28"
18
+
19
+ [profile.release]
20
+ lto = "thin"
21
+ codegen-units = 1
@@ -0,0 +1,45 @@
1
+ # arthash
2
+
3
+ Placeholder-image hash family. Four modes share a unified Codec API:
4
+
5
+ | Shape | Bytes (typical) | Notes |
6
+ |----------|-----------------|-------|
7
+ | DCT | ~21 B | ThumbHash V4 derivative. Default. |
8
+ | CIRCLE | varies | SQIP-style overlapping circles. |
9
+ | TRIANGLE | varies | fogleman/primitive-style triangle mosaic. |
10
+ | PIXEL | varies | Retro-palette pixel mosaic. |
11
+
12
+ The implementation is a thin Python wrapper around the `arthash-rs` Rust crate
13
+ exposed via PyO3 — encode/decode/SVG all run in native code.
14
+
15
+ ## Install (from source)
16
+
17
+ ```bash
18
+ maturin develop --uv -m packages/arthash-py/Cargo.toml
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ```python
24
+ from arthash import encode, decode, to_svg, Codec, ShapeType
25
+
26
+ # DCT (default)
27
+ h = encode("photo.jpg")
28
+ w, hh, rgba = decode(h, base_size=256)
29
+
30
+ # Shape modes
31
+ codec = Codec(shape=ShapeType.CIRCLE, n_shapes=12)
32
+ h = encode("photo.jpg", codec, seed=0)
33
+ w, hh, rgb = decode(h, codec, base_size=256) # (h, w, 3) RGB ndarray
34
+ svg = to_svg(h, codec, base_size=256, blur=8.0) # circle/triangle only
35
+ ```
36
+
37
+ ## Layout
38
+
39
+ - `python/arthash/` — public Python API (`Codec`, `ShapeType`, `SearchOptions`,
40
+ `palettes`, `encode`, `decode`, `to_svg`).
41
+ - `src/lib.rs` — PyO3 binding to the `arthash` Rust crate. Compiled into
42
+ `arthash._native`.
43
+ - `tests/` — pytest suite covering codec validation, V4 round-trip, shape
44
+ round-trip, SVG generation, search-options, and the cross-language test
45
+ vectors at `docs/test-vectors/vectors.json`.
@@ -0,0 +1,126 @@
1
+ """Benchmark the raw `arthash._native` extension surface — same workload as
2
+ scripts/bench_py.py but calling the FFI directly (no PIL, no Codec dataclass)
3
+ so we can isolate FFI overhead from the Python wrapper's preprocessing.
4
+
5
+ Run (from repo root):
6
+
7
+ uv run python packages/arthash-py/bench_binding.py > docs/benchmarks/binding.ndjson
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import statistics
13
+ import time
14
+ from typing import Callable
15
+
16
+ import numpy as np
17
+ from arthash import _native as m
18
+
19
+
20
+ def gradient_rgb(w: int, h: int) -> np.ndarray:
21
+ xs = np.arange(w, dtype=np.float32)
22
+ ys = np.arange(h, dtype=np.float32)
23
+ rx = np.round(xs * 255.0 / max(w - 1, 1)).astype(np.uint8)
24
+ gy = np.round(ys * 255.0 / max(h - 1, 1)).astype(np.uint8)
25
+ img = np.zeros((h, w, 3), dtype=np.uint8)
26
+ img[..., 0] = np.broadcast_to(rx, (h, w))
27
+ img[..., 1] = np.broadcast_to(gy[:, None], (h, w))
28
+ xy = (xs[None, :] + ys[:, None]) * 0.3
29
+ img[..., 2] = np.clip(xy, 0, 255).astype(np.uint8)
30
+ return img
31
+
32
+
33
+ def measure(fn: Callable[[], None], warmup: int, iters: int) -> dict:
34
+ for _ in range(warmup):
35
+ fn()
36
+ samples = []
37
+ for _ in range(iters):
38
+ t0 = time.perf_counter_ns()
39
+ fn()
40
+ samples.append((time.perf_counter_ns() - t0) / 1000.0)
41
+ samples.sort()
42
+ return {
43
+ "median_us": statistics.median(samples),
44
+ "p95_us": samples[int(len(samples) * 0.95)],
45
+ "min_us": samples[0],
46
+ "iters": iters,
47
+ }
48
+
49
+
50
+ def report(mode: str, op: str, w: int, h: int, s: dict, extra: dict | None = None) -> None:
51
+ mpix_s = (w * h) / s["median_us"]
52
+ out = {
53
+ "impl": "pyo3",
54
+ "mode": mode,
55
+ "op": op,
56
+ "w": w,
57
+ "h": h,
58
+ "median_us": round(s["median_us"], 2),
59
+ "p95_us": round(s["p95_us"], 2),
60
+ "min_us": round(s["min_us"], 2),
61
+ "iters": s["iters"],
62
+ "mpix_per_s": round(mpix_s, 3),
63
+ }
64
+ if extra:
65
+ out.update(extra)
66
+ print(json.dumps(out), flush=True)
67
+
68
+
69
+ def main() -> None:
70
+ # DCT
71
+ w, h = 100, 100
72
+ img = gradient_rgb(w, h)
73
+ rgb_bytes = img.tobytes()
74
+
75
+ hash_holder = {"b": b""}
76
+
77
+ def enc():
78
+ hash_holder["b"] = m.encode_rgb(rgb_bytes, w, h)
79
+
80
+ s = measure(enc, 30, 200)
81
+ report("dct", "encode", w, h, s, {"hash_bytes": len(hash_holder["b"])})
82
+
83
+ hb = hash_holder["b"]
84
+
85
+ def dec():
86
+ m.decode(hb, base_size=256)
87
+
88
+ s = measure(dec, 10, 50)
89
+ report("dct", "decode", w, h, s)
90
+
91
+ # Shape modes
92
+ shapes = [
93
+ ("circle", "circle", 12),
94
+ ("triangle", "triangle", 12),
95
+ ("pixel", "pixel", 12),
96
+ ]
97
+ for name, shape, n_shapes in shapes:
98
+ w, h = 48, 48
99
+ img = gradient_rgb(w, h)
100
+ rgb_bytes = img.tobytes()
101
+ codec = {"shape": shape, "n_shapes": n_shapes}
102
+
103
+ if shape == "pixel":
104
+ warmup, iters = 10, 100
105
+ else:
106
+ warmup, iters = 3, 15
107
+
108
+ hash_holder = {"b": b""}
109
+
110
+ def enc():
111
+ hash_holder["b"] = m.encode_rgb(rgb_bytes, w, h, codec)
112
+
113
+ s = measure(enc, warmup, iters)
114
+ report(name, "encode", w, h, s, {"hash_bytes": len(hash_holder["b"])})
115
+
116
+ hb = hash_holder["b"]
117
+
118
+ def dec():
119
+ m.decode(hb, codec, base_size=256)
120
+
121
+ s = measure(dec, 5, 30)
122
+ report(name, "decode", w, h, s)
123
+
124
+
125
+ if __name__ == "__main__":
126
+ main()