thumbleweed 0.1.1__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.
@@ -0,0 +1,32 @@
1
+ # Rust
2
+ /target/
3
+ **/*.rs.bk
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ *.so
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+ *.egg
14
+ .eggs/
15
+
16
+ # Virtual environments
17
+ .venv/
18
+ venv/
19
+
20
+ # IDE / OS
21
+ .idea/
22
+ .vscode/
23
+ *.swp
24
+ *.swo
25
+ .DS_Store
26
+ Thumbs.db
27
+ .mypy_cache/
28
+ .pytest_cache/
29
+ .zed/
30
+
31
+ # Maturin
32
+ *.whl
@@ -0,0 +1,8 @@
1
+ # cargo-nextest configuration
2
+ # Note: this crate is a PyO3 extension module; Python-level tests are run
3
+ # via pytest (see [tool.pytest.ini_options] in pyproject.toml).
4
+
5
+ [profile.default]
6
+ retries = 3
7
+ fail-fast = false
8
+ status-level = "all"
@@ -0,0 +1,154 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "heck"
7
+ version = "0.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
10
+
11
+ [[package]]
12
+ name = "libc"
13
+ version = "0.2.186"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
16
+
17
+ [[package]]
18
+ name = "once_cell"
19
+ version = "1.21.4"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
22
+
23
+ [[package]]
24
+ name = "portable-atomic"
25
+ version = "1.13.1"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
28
+
29
+ [[package]]
30
+ name = "proc-macro2"
31
+ version = "1.0.106"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
34
+ dependencies = [
35
+ "unicode-ident",
36
+ ]
37
+
38
+ [[package]]
39
+ name = "pyo3"
40
+ version = "0.28.3"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
43
+ dependencies = [
44
+ "libc",
45
+ "once_cell",
46
+ "portable-atomic",
47
+ "pyo3-build-config",
48
+ "pyo3-ffi",
49
+ "pyo3-macros",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "pyo3-build-config"
54
+ version = "0.28.3"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
57
+ dependencies = [
58
+ "target-lexicon",
59
+ ]
60
+
61
+ [[package]]
62
+ name = "pyo3-ffi"
63
+ version = "0.28.3"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
66
+ dependencies = [
67
+ "libc",
68
+ "pyo3-build-config",
69
+ ]
70
+
71
+ [[package]]
72
+ name = "pyo3-macros"
73
+ version = "0.28.3"
74
+ source = "registry+https://github.com/rust-lang/crates.io-index"
75
+ checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
76
+ dependencies = [
77
+ "proc-macro2",
78
+ "pyo3-macros-backend",
79
+ "quote",
80
+ "syn",
81
+ ]
82
+
83
+ [[package]]
84
+ name = "pyo3-macros-backend"
85
+ version = "0.28.3"
86
+ source = "registry+https://github.com/rust-lang/crates.io-index"
87
+ checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
88
+ dependencies = [
89
+ "heck",
90
+ "proc-macro2",
91
+ "pyo3-build-config",
92
+ "quote",
93
+ "syn",
94
+ ]
95
+
96
+ [[package]]
97
+ name = "quote"
98
+ version = "1.0.45"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
101
+ dependencies = [
102
+ "proc-macro2",
103
+ ]
104
+
105
+ [[package]]
106
+ name = "syn"
107
+ version = "2.0.117"
108
+ source = "registry+https://github.com/rust-lang/crates.io-index"
109
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
110
+ dependencies = [
111
+ "proc-macro2",
112
+ "quote",
113
+ "unicode-ident",
114
+ ]
115
+
116
+ [[package]]
117
+ name = "target-lexicon"
118
+ version = "0.13.5"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
121
+
122
+ [[package]]
123
+ name = "thiserror"
124
+ version = "2.0.18"
125
+ source = "registry+https://github.com/rust-lang/crates.io-index"
126
+ checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
127
+ dependencies = [
128
+ "thiserror-impl",
129
+ ]
130
+
131
+ [[package]]
132
+ name = "thiserror-impl"
133
+ version = "2.0.18"
134
+ source = "registry+https://github.com/rust-lang/crates.io-index"
135
+ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
136
+ dependencies = [
137
+ "proc-macro2",
138
+ "quote",
139
+ "syn",
140
+ ]
141
+
142
+ [[package]]
143
+ name = "thumbleweed"
144
+ version = "0.1.1"
145
+ dependencies = [
146
+ "pyo3",
147
+ "thiserror",
148
+ ]
149
+
150
+ [[package]]
151
+ name = "unicode-ident"
152
+ version = "1.0.24"
153
+ source = "registry+https://github.com/rust-lang/crates.io-index"
154
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,25 @@
1
+ [package]
2
+ name = "thumbleweed"
3
+ version = "0.1.1"
4
+ edition = "2024"
5
+ description = "Fast ThumbHash encode/decode – Python bindings via PyO3"
6
+ license = "MIT"
7
+ readme = "README.md"
8
+
9
+ [lib]
10
+ name = "_core"
11
+ crate-type = ["cdylib"]
12
+
13
+ [dependencies]
14
+ pyo3 = { version = "^0.28.3", features = ["extension-module"] }
15
+ thiserror = "2"
16
+
17
+ [profile.release]
18
+ lto = true
19
+ codegen-units = 1
20
+ opt-level = 3
21
+ strip = true
22
+
23
+ # ── Free-threaded Python 3.13+ (no GIL) ────────────────────────────────────
24
+ # pyo3 0.23+ enables this automatically when building against a free-threaded
25
+ # interpreter; no extra feature flag needed.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 thumbhash-py contributors
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,329 @@
1
+ Metadata-Version: 2.4
2
+ Name: thumbleweed
3
+ Version: 0.1.1
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Programming Language :: Rust
14
+ Classifier: Topic :: Multimedia :: Graphics
15
+ Classifier: Typing :: Typed
16
+ Requires-Dist: pillow>12 ; extra == 'dev'
17
+ Requires-Dist: pytest>=9 ; extra == 'dev'
18
+ Requires-Dist: maturin>=1.10 ; extra == 'dev'
19
+ Requires-Dist: pillow>11 ; extra == 'pillow'
20
+ Provides-Extra: dev
21
+ Provides-Extra: pillow
22
+ License-File: LICENCE
23
+ Summary: Fast ThumbHash encode/decode – Rust-powered Python bindings
24
+ Keywords: thumbhash,image,placeholder,blurhash
25
+ License: MIT
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
28
+ Project-URL: Bug Tracker, https://github.com/New-Elysium/thumbleweed/issues
29
+ Project-URL: Repository, https://github.com/New-Elysium/thumbleweed
30
+
31
+ # thumbhash
32
+
33
+ **Fast ThumbHash encode/decode for Python — Rust-powered, zero mandatory dependencies.**
34
+
35
+ A modern, OSS drop-in replacement for [`fast-thumbhash`](https://pypi.org/project/fast-thumbhash/),
36
+ built with [maturin](https://www.maturin.rs/) and [PyO3](https://pyo3.rs/).
37
+
38
+ - ✅ Python 3.10 – 3.14 (including free-threaded `3.13t` / `3.14t`)
39
+ - ✅ Pillow > 11 integration (optional)
40
+ - ✅ Typed (`py.typed` + `.pyi` stubs)
41
+ - ✅ Pure-Rust core — no C extensions, no NumPy required
42
+
43
+ ---
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install thumbleweed
49
+ # with Pillow helpers:
50
+ pip install "thumbleweed[pillow]"
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Quick-start
56
+
57
+ ### Raw RGBA bytes
58
+
59
+ ```python
60
+ import thumbleweed as tw
61
+
62
+ # Encode -------------------------------------------------------------------
63
+ # rgba_bytes must be a bytes/bytearray of length w*h*4 (R G B A, non-premult.)
64
+ hash_bytes: bytes = tw.encode(w, h, rgba_bytes)
65
+
66
+ # Decode -------------------------------------------------------------------
67
+ w_out, h_out, rgba_out = tw.decode(hash_bytes)
68
+
69
+ # Helpers ------------------------------------------------------------------
70
+ r, g, b, a = tw.average_rgba(hash_bytes) # dominant colour [0, 1]
71
+ ratio = tw.approximate_aspect_ratio(hash_bytes) # width / height
72
+ ```
73
+
74
+ ### Pillow images
75
+
76
+ ```python
77
+ from PIL import Image
78
+ import thumbleweed as tw
79
+
80
+ img = Image.open("photo.jpg")
81
+
82
+ hash_bytes = tw.encode_image(img) # any mode, any size
83
+ placeholder: Image.Image = tw.decode_image(hash_bytes) # RGBA, ≈32 px
84
+ placeholder.save("placeholder.png")
85
+ ```
86
+
87
+ `encode_image` automatically converts the image to RGBA and shrinks it so
88
+ the longest side is ≤ 100 px (the algorithm's hard limit).
89
+
90
+ ---
91
+
92
+ ## API reference
93
+
94
+ | Function | Description |
95
+ |---|---|
96
+ | `encode(w, h, rgba) → bytes` | Encode raw RGBA bytes → ThumbHash |
97
+ | `decode(hash) → (w, h, rgba)` | Decode ThumbHash → raw RGBA bytes |
98
+ | `average_rgba(hash) → (r,g,b,a)` | Dominant colour in [0, 1] |
99
+ | `approximate_aspect_ratio(hash) → float` | Width / height of the original image |
100
+ | `encode_image(img) → bytes` | Encode a Pillow `Image` *(requires Pillow)* |
101
+ | `decode_image(hash) → Image` | Decode a ThumbHash to a Pillow `Image` *(requires Pillow)* |
102
+
103
+ ---
104
+
105
+ ## Building from source
106
+
107
+ ### Prerequisites
108
+
109
+ | Tool | Version | Install |
110
+ |---|---|---|
111
+ | Rust (stable) | ≥ 1.75 (tested with **1.95.0**) | `curl https://sh.rustup.rs -sSf \| sh` |
112
+ | Python | 3.9 – **3.14** | system / pyenv |
113
+ | maturin | ≥ 1.7, < 2 | `pip install "maturin>=1.7,<2"` |
114
+
115
+ > **Ubuntu 26.04 / Python 3.14 note** — Ubuntu 26.04 ships Python 3.14.
116
+ > The steps below work unchanged; maturin detects the interpreter automatically.
117
+
118
+ ### 1 — Clone and enter the project
119
+
120
+ ```bash
121
+ git clone https://github.com/New-Elysium/thumbleweed.git
122
+ cd thumbleweed
123
+ ```
124
+
125
+ ### 2 — Set up a virtual environment
126
+
127
+ ```bash
128
+ python3 -m venv .venv
129
+ source .venv/bin/activate
130
+ pip install --upgrade pip
131
+ pip install "maturin>=1.10,<2" "Pillow>12" pytest
132
+ ```
133
+
134
+ ### 3 — Development build (editable)
135
+
136
+ Compiles the Rust extension and installs the package in editable mode so
137
+ that changes to the Python files in `python/thumbhash/` take effect
138
+ immediately without a rebuild.
139
+
140
+ ```bash
141
+ maturin develop --release
142
+ ```
143
+
144
+ ### 4 — Run the tests
145
+
146
+ ```bash
147
+ pytest -v
148
+ ```
149
+
150
+ ### 5 — Build release wheels
151
+
152
+ #### Local wheel (current platform only)
153
+
154
+ ```bash
155
+ maturin build --release
156
+ # wheel lands in target/wheels/thumbhash-*.whl
157
+ pip install target/wheels/thumbhash-*.whl
158
+ ```
159
+
160
+ #### Portable `manylinux` wheels (for PyPI)
161
+
162
+ Use the official maturin Docker image to build wheels that are compatible
163
+ with virtually every Linux distribution:
164
+
165
+ ```bash
166
+ docker run --rm \
167
+ -v "$(pwd)":/io \
168
+ -w /io \
169
+ ghcr.io/pyo3/maturin:latest \
170
+ build --release --out dist
171
+ ```
172
+
173
+ Wheels for each CPython version land in `dist/`.
174
+
175
+ #### macOS (universal2)
176
+
177
+ ```bash
178
+ rustup target add aarch64-apple-darwin x86_64-apple-darwin
179
+ maturin build --release --target universal2-apple-darwin
180
+ ```
181
+
182
+ #### Windows
183
+
184
+ ```powershell
185
+ maturin build --release
186
+ ```
187
+
188
+ #### Free-threaded Python 3.13t / 3.14t
189
+
190
+ maturin ≥ 1.7 detects the free-threaded interpreter automatically.
191
+ Just activate the `python3.14t` (or `python3.13t`) virtual environment and
192
+ run `maturin develop` or `maturin build` as normal.
193
+
194
+ ---
195
+
196
+ ## Publishing to PyPI
197
+
198
+ ### One-time setup
199
+
200
+ ```bash
201
+ pip install twine
202
+ # Create a PyPI account and generate an API token at https://pypi.org/manage/account/token/
203
+ ```
204
+
205
+ Store the token in `~/.pypirc`:
206
+
207
+ ```ini
208
+ [pypi]
209
+ username = __token__
210
+ password = pypi-<your-token-here>
211
+ ```
212
+
213
+ Or export it as an environment variable:
214
+
215
+ ```bash
216
+ export MATURIN_PYPI_TOKEN=pypi-<your-token-here>
217
+ ```
218
+
219
+ ### Build all wheels with `maturin publish` (recommended)
220
+
221
+ `maturin publish` builds *and* uploads in one step. It uses
222
+ [`cibuildwheel`](https://cibuildwheel.pypa.io/) conventions internally when
223
+ run in CI.
224
+
225
+ ```bash
226
+ maturin publish --token pypi-<your-token-here>
227
+ ```
228
+
229
+ ### Manual upload with twine
230
+
231
+ If you built wheels separately (e.g. via the Docker approach above):
232
+
233
+ ```bash
234
+ twine upload dist/*.whl dist/*.tar.gz
235
+ ```
236
+
237
+ ### CI / GitHub Actions (recommended for cross-platform wheels)
238
+
239
+ Create `.github/workflows/release.yml`:
240
+
241
+ ```yaml
242
+ name: Release
243
+
244
+ on:
245
+ push:
246
+ tags: ["v*"]
247
+
248
+ jobs:
249
+ build:
250
+ strategy:
251
+ matrix:
252
+ os: [ubuntu-latest, macos-latest, windows-latest]
253
+ runs-on: ${{ matrix.os }}
254
+ steps:
255
+ - uses: actions/checkout@v4
256
+
257
+ - uses: actions/setup-python@v5
258
+ with:
259
+ python-version: "3.x"
260
+
261
+ - uses: PyO3/maturin-action@v1
262
+ with:
263
+ command: build
264
+ args: --release --out dist
265
+ manylinux: auto # builds manylinux wheels on Linux
266
+
267
+ - uses: actions/upload-artifact@v4
268
+ with:
269
+ name: wheels-${{ matrix.os }}
270
+ path: dist
271
+
272
+ publish:
273
+ needs: build
274
+ runs-on: ubuntu-latest
275
+ environment: pypi
276
+ permissions:
277
+ id-token: write # OIDC trusted publishing
278
+ steps:
279
+ - uses: actions/download-artifact@v4
280
+ with:
281
+ pattern: wheels-*
282
+ merge-multiple: true
283
+ path: dist
284
+
285
+ - uses: pypa/gh-action-pypi-publish@release/v1
286
+ # Trusted publishing — no API token needed when configured on PyPI
287
+ ```
288
+
289
+ > **Tip**: Enable [Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
290
+ > on PyPI to avoid managing API tokens in GitHub secrets.
291
+
292
+ ---
293
+
294
+ ## Project layout
295
+
296
+ ```
297
+ thumbhash-py/
298
+ ├── Cargo.toml # Rust crate manifest
299
+ ├── pyproject.toml # maturin + PEP 621 metadata
300
+ ├── README.md
301
+ ├── src/
302
+ │ └── lib.rs # Rust implementation + PyO3 bindings
303
+ ├── python/
304
+ │ └── thumbhash/
305
+ │ ├── __init__.py # Public Python API + Pillow helpers
306
+ │ ├── _core.pyi # Type stubs for the compiled extension
307
+ │ └── py.typed # PEP 561 marker
308
+ └── tests/
309
+ └── test_thumbhash.py
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Algorithm
315
+
316
+ ThumbHash is a compact image placeholder algorithm by
317
+ [Evan Wallace](https://evanw.github.io/thumbhash/). It encodes images using
318
+ a discrete cosine transform (DCT) into a small byte string (typically 5–32
319
+ bytes) that decodes to a smooth, colourful placeholder.
320
+
321
+ Compared to BlurHash it supports a more accurate aspect ratio, alpha
322
+ transparency, and better colour fidelity.
323
+
324
+ ---
325
+
326
+ ## Licence
327
+
328
+ MIT — see [LICENCE](LICENCE).
329
+