Dithering algorithms for e-paper/e-ink displays. Rust core with Python and JavaScript/TypeScript bindings.
Dithering algorithms (Spectra 6-color, Marienplatz U-Bahn station):
Color schemes (Burkes, river scene):
Published to PyPI as epaper-dithering.
pip install epaper-ditheringfromPILimportImagefromepaper_ditheringimportdither_image, ColorScheme, DitherMode, SPECTRA_7_3_6COLOR_V2img=Image.open("photo.jpg")
# Idealized palettedithered=dither_image(img, ColorScheme.BWR, mode=DitherMode.FLOYD_STEINBERG)
# Measured palette — calibrated color matchingdithered=dither_image(img, SPECTRA_7_3_6COLOR_V2)
# Opt in to automatic tone + gamut compression for photosdithered=dither_image(img, SPECTRA_7_3_6COLOR_V2, tone="auto", gamut="auto")See packages/python/README.md for full documentation.
Published to npm as @opendisplay/epaper-dithering.
npm install @opendisplay/epaper-ditheringimport{ditherImage,ColorScheme,DitherMode,SPECTRA_7_3_6COLOR_V2}from'@opendisplay/epaper-dithering';// ImageBuffer from Canvas API or Node.js (sharp, etc.)constdithered=ditherImage(imageBuffer,ColorScheme.BWR,{mode: DitherMode.BURKES});// Measured palette — calibrated color matchingconstdithered=ditherImage(imageBuffer,SPECTRA_7_3_6COLOR_V2);// Opt in to automatic tone + gamut compression for photosconstcompressed=ditherImage(imageBuffer,SPECTRA_7_3_6COLOR_V2,{tone: 'auto',gamut: 'auto'});See packages/javascript/README.md for full documentation.
- Rust Core: All dithering logic in
packages/rust/core/— shared by both packages - 10 Dithering Algorithms: NONE, ORDERED, BURKES, FLOYD_STEINBERG, ATKINSON, STUCKI, SIERRA, SIERRA_LITE, JARVIS_JUDICE_NINKE, DIZZY
- 10 Color Schemes: MONO, BWR, BWY, BWRY, BWGBRY (Spectra 6), GRAYSCALE_4, GRAYSCALE_16, SEVEN_COLOR (Spectra/ACeP 7), BWGBRY_SPLIT, GRAYSCALE_8 (library-local, e.g. Inkplate 10 — not a firmware wire value)
- Measured Palettes: Calibrated RGB values for real displays, linked to their canonical firmware palette
- OKLab Color Matching: Weighted Cartesian OKLab — preserves hue without the achromatic-attractor bug of LCH-weighted approaches
- Pre-dither Knobs: Per-image exposure, saturation, shadows, highlights, and gamut compression — all orthogonal
epaper-dithering/
├── packages/
│ ├── rust/
│ │ ├── core/ # Shared Rust algorithms (OKLab, dithering, tone/gamut)
│ │ └── wasm/ # wasm-bindgen bindings for JavaScript
│ ├── python/ # PyO3/maturin extension + Python wrapper
│ └── javascript/ # TypeScript wrapper + bundled WASM
├── docs/ # Calibration and color science documentation
└── .github/workflows/ # CI for all packages
# Python (requires Rust toolchain: https://rustup.rs)cd packages/python
uv sync --all-extras
uv run maturin develop --release
uv run pytest tests/ -v
# JavaScriptcd packages/javascript
bun install
bun run test# Rustcd packages/rust
cargo test --workspace

