Skip to main content

Fast ThumbHash encode/decode – Rust-powered Python bindings

Project description

thumbhash

Fast ThumbHash encode/decode for Python — Rust-powered, zero mandatory dependencies.

A modern, OSS drop-in replacement for fast-thumbhash, built with maturin and PyO3.

  • ✅ Python 3.10 – 3.14 (including free-threaded 3.13t / 3.14t)
  • ✅ Pillow > 11 integration (optional)
  • ✅ Typed (py.typed + .pyi stubs)
  • ✅ Pure-Rust core — no C extensions, no NumPy required

Installation

pip install thumbleweed
# with Pillow helpers:
pip install "thumbleweed[pillow]"

Quick-start

Raw RGBA bytes

import thumbleweed as tw

# Encode -------------------------------------------------------------------
# rgba_bytes must be a bytes/bytearray of length w*h*4 (R G B A, non-premult.)
hash_bytes: bytes = tw.encode(w, h, rgba_bytes)

# Decode -------------------------------------------------------------------
w_out, h_out, rgba_out = tw.decode(hash_bytes)

# Helpers ------------------------------------------------------------------
r, g, b, a = tw.average_rgba(hash_bytes)   # dominant colour [0, 1]
ratio = tw.approximate_aspect_ratio(hash_bytes)  # width / height

Pillow images

from PIL import Image
import thumbleweed as tw

img = Image.open("photo.jpg")

hash_bytes = tw.encode_image(img)         # any mode, any size
placeholder: Image.Image = tw.decode_image(hash_bytes)   # RGBA, ≈32 px
placeholder.save("placeholder.png")

encode_image automatically converts the image to RGBA and shrinks it so the longest side is ≤ 100 px (the algorithm's hard limit).


API reference

Function Description
encode(w, h, rgba) → bytes Encode raw RGBA bytes → ThumbHash
decode(hash) → (w, h, rgba) Decode ThumbHash → raw RGBA bytes
average_rgba(hash) → (r,g,b,a) Dominant colour in [0, 1]
approximate_aspect_ratio(hash) → float Width / height of the original image
encode_image(img) → bytes Encode a Pillow Image (requires Pillow)
decode_image(hash) → Image Decode a ThumbHash to a Pillow Image (requires Pillow)

Building from source

Prerequisites

Tool Version Install
Rust (stable) ≥ 1.75 (tested with 1.95.0) curl https://sh.rustup.rs -sSf | sh
Python 3.9 – 3.14 system / pyenv
maturin ≥ 1.7, < 2 pip install "maturin>=1.7,<2"

Ubuntu 26.04 / Python 3.14 note — Ubuntu 26.04 ships Python 3.14. The steps below work unchanged; maturin detects the interpreter automatically.

1 — Clone and enter the project

git clone https://github.com/New-Elysium/thumbleweed.git
cd thumbleweed

2 — Set up a virtual environment

python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install "maturin>=1.10,<2" "Pillow>12" pytest

3 — Development build (editable)

Compiles the Rust extension and installs the package in editable mode so that changes to the Python files in python/thumbhash/ take effect immediately without a rebuild.

maturin develop --release

4 — Run the tests

pytest -v

5 — Build release wheels

Local wheel (current platform only)

maturin build --release
# wheel lands in  target/wheels/thumbhash-*.whl
pip install target/wheels/thumbhash-*.whl

Portable manylinux wheels (for PyPI)

Use the official maturin Docker image to build wheels that are compatible with virtually every Linux distribution:

docker run --rm \
  -v "$(pwd)":/io \
  -w /io \
  ghcr.io/pyo3/maturin:latest \
  build --release --out dist

Wheels for each CPython version land in dist/.

macOS (universal2)

rustup target add aarch64-apple-darwin x86_64-apple-darwin
maturin build --release --target universal2-apple-darwin

Windows

maturin build --release

Free-threaded Python 3.13t / 3.14t

maturin ≥ 1.7 detects the free-threaded interpreter automatically. Just activate the python3.14t (or python3.13t) virtual environment and run maturin develop or maturin build as normal.


Publishing to PyPI

One-time setup

pip install twine
# Create a PyPI account and generate an API token at https://pypi.org/manage/account/token/

Store the token in ~/.pypirc:

[pypi]
  username = __token__
  password = pypi-<your-token-here>

Or export it as an environment variable:

export MATURIN_PYPI_TOKEN=pypi-<your-token-here>

Build all wheels with maturin publish (recommended)

maturin publish builds and uploads in one step. It uses cibuildwheel conventions internally when run in CI.

maturin publish --token pypi-<your-token-here>

Manual upload with twine

If you built wheels separately (e.g. via the Docker approach above):

twine upload dist/*.whl dist/*.tar.gz

CI / GitHub Actions (recommended for cross-platform wheels)

Create .github/workflows/release.yml:

name: Release

on:
  push:
    tags: ["v*"]

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.x"

      - uses: PyO3/maturin-action@v1
        with:
          command: build
          args: --release --out dist
          manylinux: auto   # builds manylinux wheels on Linux

      - uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.os }}
          path: dist

  publish:
    needs: build
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write   # OIDC trusted publishing
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: wheels-*
          merge-multiple: true
          path: dist

      - uses: pypa/gh-action-pypi-publish@release/v1
        # Trusted publishing — no API token needed when configured on PyPI

Tip: Enable Trusted Publishing on PyPI to avoid managing API tokens in GitHub secrets.


Project layout

thumbhash-py/
├── Cargo.toml          # Rust crate manifest
├── pyproject.toml      # maturin + PEP 621 metadata
├── README.md
├── src/
│   └── lib.rs          # Rust implementation + PyO3 bindings
├── python/
│   └── thumbhash/
│       ├── __init__.py # Public Python API + Pillow helpers
│       ├── _core.pyi   # Type stubs for the compiled extension
│       └── py.typed    # PEP 561 marker
└── tests/
    └── test_thumbhash.py

Algorithm

ThumbHash is a compact image placeholder algorithm by Evan Wallace. It encodes images using a discrete cosine transform (DCT) into a small byte string (typically 5–32 bytes) that decodes to a smooth, colourful placeholder.

Compared to BlurHash it supports a more accurate aspect ratio, alpha transparency, and better colour fidelity.


Licence

MIT — see LICENCE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thumbleweed-0.1.1.tar.gz (274.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

thumbleweed-0.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (203.2 kB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ x86-64

thumbleweed-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (204.1 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

thumbleweed-0.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (203.5 kB view details)

Uploaded CPython 3.13tmanylinux: glibc 2.17+ x86-64

thumbleweed-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (204.2 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

thumbleweed-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (203.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

thumbleweed-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (204.5 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

thumbleweed-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (204.6 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

File details

Details for the file thumbleweed-0.1.1.tar.gz.

File metadata

  • Download URL: thumbleweed-0.1.1.tar.gz
  • Upload date:
  • Size: 274.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for thumbleweed-0.1.1.tar.gz
Algorithm Hash digest
SHA256 acfaf6055f491cfceadfc218e4b2939e2d5377b71dfcb983f04444a24088dd3d
MD5 60add0ce43273b8dedd76c378e2415a1
BLAKE2b-256 5fe6c868d9f90f2762e921ac774e49dc34971ae85da5399926c9a687f2895cd9

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 da31f9335856809073c699ce73238ab35034604fa7f70db18bd0508bddeafb8b
MD5 d095a3257e4f56c8a8fb71da5de2d225
BLAKE2b-256 10583ae813aaa69dfa8586950effe6726a3fbe9fb031fb22ec11675cf436bfe4

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b932e8b6e65165f178347ae1b23ca7725626c823b25a1d8c3a3ca9435d3226bc
MD5 547e3d418e8eb9816364ccca9b32bc38
BLAKE2b-256 33693cfb913bce0748ba26a5829c0862266943f9f15c54fc4bd4b8833ba08196

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 63b2c35a3d8520ef7e3ec26bb94d45bf8a9d69cac28a8b5d0be355d017f3477f
MD5 744221902cc400744b347c84ef4e1f2e
BLAKE2b-256 b6a3b8c1fc8b812e414d3b7004edd8adc4f11717e885eab49d19642e4599bd74

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 aed2be1a2c1df03e34100b7555736ba0e57739a9d815b62a07f0df4211e8a60e
MD5 afdb3d9a8d0a57e50e4fe379bb9db6b0
BLAKE2b-256 61beb49016a8c0cf933feee613b315f4e9fef9edf6b5a6cb1b61b35da89d7eaf

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 78704769626864de0b0c8839e9a9c7985a4e629637916765f94cd1752c58cc35
MD5 ebce4792d75cb70d0225d985e9f1fcae
BLAKE2b-256 3ffe4f79d3f84f63cd71bf737f44051317c8664fc2eed7fdeaf18ae2e805b8f3

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 491528f037d8b35ebfd647df8a2ab72391b2746a0ef9bdb4845336efa0246d73
MD5 2f5e528b09693338de6a0e8fd582dfbc
BLAKE2b-256 14d801a935ff5b0767d83c7bb7e2940cff668604873a0915437f50d4d059fdb9

See more details on using hashes here.

File details

Details for the file thumbleweed-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for thumbleweed-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 239666d8b5cd66833151636a2afce287ddbe34e04c01375453d4f79ad9c2c44d
MD5 42276e1d4df32f61fe772f0dcd276013
BLAKE2b-256 ddcc2676baf8754276fd246d544759b960fd1f1436d1c60348f95f6cc84bc74c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page