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.
- thumbleweed-0.1.1/.gitignore +32 -0
- thumbleweed-0.1.1/.nextest.toml +8 -0
- thumbleweed-0.1.1/Cargo.lock +154 -0
- thumbleweed-0.1.1/Cargo.toml +25 -0
- thumbleweed-0.1.1/LICENCE +21 -0
- thumbleweed-0.1.1/PKG-INFO +329 -0
- thumbleweed-0.1.1/README.md +298 -0
- thumbleweed-0.1.1/pyproject.toml +62 -0
- thumbleweed-0.1.1/python/thumbhash/__init__.py +120 -0
- thumbleweed-0.1.1/python/thumbhash/_core.pyi +54 -0
- thumbleweed-0.1.1/python/thumbhash/py.typed +0 -0
- thumbleweed-0.1.1/src/lib.rs +506 -0
- thumbleweed-0.1.1/tests/OPS.jpg +0 -0
- thumbleweed-0.1.1/tests/four.jpg +0 -0
- thumbleweed-0.1.1/tests/one.jpg +0 -0
- thumbleweed-0.1.1/tests/test_thumbhash.py +422 -0
- thumbleweed-0.1.1/tests/two.jpg +0 -0
|
@@ -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,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
|
+
|