Blanket
Image processing in Python. Pixel work in Rust.
Blanket is a Rust-backed image library with a familiar, Pillow-shaped Python API. Open, transform, and save images using native pixel kernels, with no Pillow or other Python packages required at runtime.
from blanket import Image, ImageOps
with Image.open("photo.jpg") as image:
upright = ImageOps.exif_transpose(image)
thumbnail = ImageOps.fit(upright, (256, 256), method=Image.Resampling.LANCZOS)
thumbnail.save("thumbnail.webp", quality=85)
Installation · Examples · Formats · API notes · Contributing
Why Blanket?
- Familiar image APIs. Work with
Image,ImageOps,ImageChops,ImageEnhance,ImageFilter,ImagePalette, andImageStat. - Native processing. Rust kernels use SIMD and parallel execution for supported operations, with small-image fast paths.
- Modern formats. Read and write JPEG XL, WebP, AVIF, and HEIC/HEIF alongside PNG, JPEG, TIFF, BMP, GIF, and ICO. Export single-page PDFs.
- Explicit precision. Keep 10-, 12-, and 16-bit samples in supported workflows, and choose when to convert to 8 bits.
- Python interoperability. Import array data, work with binary streams, and convert explicitly to Pillow when you need it.
Blanket is currently alpha software. It implements a focused subset of
Pillow's API, centered on L, RGB, and RGBA images, with limited indexed
P support. It is not a drop-in replacement for PIL.
See ImageChops for arithmetic and blend modes, and ImageStat for per-band statistics.
Installation
Blanket requires Python 3.12+. Install from this repository after setting up the native build prerequisites below:
pip install pyblanket
Source builds require Rust 1.97+, CMake, Ninja, a C/C++ compiler, and NASM on x86. JPEG XL builds libjxl statically. Install native codec dependencies:
# macOS
brew install cmake ninja dav1d libheif
# Ubuntu
sudo apt-get install build-essential cmake ninja-build nasm libdav1d-dev \
libheif-dev libde265-dev libx265-dev libnuma-dev
AVIF requires dav1d 1.3+. The embedded libheif build requires libde265 and x265 development libraries to compile HEIF support; external libheif plugins are not loaded. Native shared codec libraries must also be available at runtime. See the development notes for more context.
Examples
Convert between formats
from blanket import Image
with Image.open("input.png") as image:
image.convert("RGB").save("output.jpg", quality=85)
image.save("output.jxl", lossless=True)
File extensions select the output format. For binary streams, pass it explicitly:
from io import BytesIO
from blanket import Image
with Image.open("input.png") as image:
buffer = BytesIO()
image.save(buffer, format="PNG")
png_bytes = buffer.getvalue()
Optimize a save without additional loss
from blanket import Image
from blanket.Compressor import LosslessImageCompressor
compressor = LosslessImageCompressor(effort=7)
with Image.open("input.png") as image:
image.save("optimized.png", compressor=compressor)
image.convert("RGB").save("optimized.jpg", quality=90, compressor=compressor)
image.save("optimized.jxl", lossless=True, compressor=compressor)
image.save("optimized.heic", lossless=True, compressor=compressor)
The compressor keeps the smallest encoding that preserves the normal save's decoded pixels. It supports PNG, JPEG, JPEG XL, and HEIF/HEIC; higher effort tries more settings and takes longer. JPEG optimization preserves DCT coefficients. This optimizes the requested save, not the original source file: lossy save settings still introduce their normal loss. See the Compressor module for the API, format-specific behavior, and compression benchmarks.
For smaller output with bounded additional pixel error, pass
LossyImageCompressor(max_rmse=2.0, effort=7) from blanket.Compressor.
It searches PNG color precision or lower JPEG, WebP, JPEG XL, AVIF, and
HEIF/HEIC quality settings, preserving alpha and checking decoded RGB error
against the normal save. The result never exceeds the normal save's size.
Resize, enhance, and filter
from blanket import Image, ImageEnhance, ImageFilter, ImageOps
with Image.open("photo.jpg") as image:
resized = ImageOps.contain(image, (1200, 800))
enhanced = ImageEnhance.Contrast(resized).enhance(1.2)
sharpened = enhanced.filter(ImageFilter.UnsharpMask(radius=2, percent=150))
sharpened.save("edited.png")
resize() supports all six Image.Resampling filters. ImageOps includes
cropping, padding, flips, color adjustments, and EXIF orientation correction.
ImageFilter provides blurs, convolution kernels, rank filters, and 3D color LUTs.
Build an image with transparency
from blanket import Image
background = Image.new("RGBA", (640, 480), "white")
overlay = Image.new("RGBA", (160, 160), (30, 100, 220, 128))
background.alpha_composite(overlay, dest=(40, 40))
background.save("composite.png")
Use paste() for masked placement, putalpha() to set transparency, and
split() / Image.merge() to work with individual channels.
Work with NumPy and high-bit-depth images
NumPy is optional. fromarray() accepts array-interface objects, including
strided arrays:
import numpy as np
from blanket import Image
samples = np.full((64, 64, 3), 713, dtype=np.uint16)
image = Image.fromarray(samples, bit_depth=10)
image.save("exact.png")
image.convert("RGB", bit_depth=8).save("preview.jpg")
image.bit_depth reports sample precision. Copying, conversion, pixel access,
splitting, cropping, transposition, and resizing preserve high-bit-depth
samples. Other processing operations require conversion to 8 bits.
See high-bit-depth images for storage
and format-specific behavior.
Supported formats
| Format | Read | Write | Notes |
|---|---|---|---|
| PNG | Yes | Yes | Lossless; high-bit-depth and indexed output |
| JPEG | Yes | Yes | 8-bit output; convert RGBA to RGB before saving |
| JPEG XL | Yes | Yes | Lossy or lossless; high-bit-depth support |
| TIFF | Yes | Yes | First image only; uncompressed output |
| WebP | Yes | Yes | Lossy or lossless; first frame only |
| AVIF | Yes | Yes | Primary image only; 8-bit, lossy output |
| HEIC / HEIF | Yes | Yes | HEVC; retains 8-, 10-, or 12-bit source depth |
| — | Yes | Single page; lossless 8-bit output with transparency | |
| BMP | Yes | Yes | 8-bit output; uncompressed |
| GIF | Yes | Yes | First frame only; single-frame output, 256 colors and binary transparency |
| ICO | Yes | Yes | Largest icon on read; one PNG icon on write, 1–256 pixels per dimension |
Encoder settings and their defaults are listed in the format guide. HEIF lossless compression can still change RGB values during RGB/YUV conversion.
Compatibility and scope
- Image modes: general processing uses
L,RGB, andRGBA. Quantization produces indexedPimages with a smaller supported operation set. - Metadata: PNG, JPEG, and uncompressed JPEG XL metadata boxes supply EXIF/XMP for orientation handling. Saving writes pixels only; metadata is not preserved.
- Multiple frames: animation and multipage editing are not supported.
- High bit depth: sample retention is supported for selected operations and formats; HDR tone mapping and HEIF HDR/ICC metadata retention are not.
- Pillow interop:
image.to_pillow()creates a Pillow image when Pillow is installed. High-bit-depth images must first be converted to 8 bits.
See the usage and API notes for operation-specific restrictions, palette support, and interoperability examples.
Performance
The benchmark suite compares Blanket and Pillow using equal in-memory inputs and eager decoding. After completing the development setup below, run it with a release build:
maturin develop -r --extras test --uv
uv run --no-sync scripts/benchmark.py --sizes web
Use --all for the full suite or --sections Resize ImageOps to select
operations. Results depend on image size, operation, codec settings, and
hardware. The benchmark guide
explains baseline comparisons and reproducible runs.
Contributing
With the build prerequisites installed, set up a development environment and run the checks:
uv sync --no-install-project --extra test
uv tool install maturin
maturin develop --extras test --uv
cargo fmt --check
cargo clippy -- -D warnings
cargo test
uv run --no-sync pytest -q
uv run --no-sync scripts/verify_behavior.py
The native core lives in src/, the Python API in
python/blanket/, and compatibility tests in tests/.
Include a focused regression test with bug fixes and new features.
open an issue to report a problem.
License
Blanket is licensed under the MIT License. See third-party licenses for native dependency notices.
Release files for pyblanket 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyblanket-0.0.1.tar.gz | 214.1 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyblanket-0.0.1-cp312-abi3-manylinux_2_39_x86_64.whl | CPython 3.12 | abi3 | Linux glibc 2.39+ x86-64 | Details |
| pyblanket-0.0.1-cp312-abi3-macosx_26_0_arm64.whl | CPython 3.12 | abi3 | macOS 26.0+ ARM64 | Details |
Total release size: 25.0 MB
Release files / pyblanket-0.0.1.tar.gz
| Download URL | pyblanket-0.0.1.tar.gz |
|---|---|
| Size | 214.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
48d7948548a9d7b533bbda0e295715e48e6c6841759fd6e495430f39e93010ed
|
|
BLAKE2b-256 checksum How to use checksums |
712569dc3dee6c01c9e466c8c00eb6b53de4c90d57b591c4bab00d9cf6af47f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / pyblanket-0.0.1-cp312-abi3-manylinux_2_39_x86_64.whl
| Download URL | pyblanket-0.0.1-cp312-abi3-manylinux_2_39_x86_64.whl |
|---|---|
| Size | 14.7 MB |
| Tags | CPython 3.12 Linux glibc 2.39+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
d0ab9042a4dd81759b9f2b6ea2a6dd96f6e66a2b9169f623922704a2c05fc943
|
|
BLAKE2b-256 checksum How to use checksums |
03d5e28cd9e48bbc28215a45c8e5f10018d888625759f209fc90e67a9f779c06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / pyblanket-0.0.1-cp312-abi3-macosx_26_0_arm64.whl
| Download URL | pyblanket-0.0.1-cp312-abi3-macosx_26_0_arm64.whl |
|---|---|
| Size | 10.1 MB |
| Tags | CPython 3.12 abi3 macOS 26.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
f931c97583c12b8f1eccfd478f1662efba14af5256bf4bfe8616a0d1c22a16c1
|
|
BLAKE2b-256 checksum How to use checksums |
8e3eb85e36c2f74696ecb75ea50d100b31d2074eaeba23de911145f6cb92fba3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log