Python bindings for libjxl using pybind11
Project description
pylibjxl
Fast Python bindings for libjxl and libjpeg-turbo. Built with pybind11, with GIL-free encoding/decoding and native async support.
Features
- 🚀 High performance — C++ core with GIL release during encode/decode
- 📦 Metadata support — Read/write EXIF, XMP, and JUMBF metadata
- ⚡ Async-first — Native
asynciosupport for concurrent I/O - 🎯 Simple API — Free functions for quick use, context managers for control
- 🖼️ NumPy native — Direct
ndarrayinput/output (RGB/RGBA, uint8) - 🔄 JPEG support — Encode/decode JPEG via libjpeg-turbo + lossless JPEG↔JXL transcoding
Installation
Prerequisites
- Python ≥ 3.11
- CMake ≥ 3.15
- C++17 compiler (GCC, Clang, MSVC)
Note: libjxl and libjpeg-turbo are bundled as Git submodules in
third_party/and statically linked — no system-level installation required.
Install
git clone --recurse-submodules https://github.com/user/pylibjxl.git
cd pylibjxl
pip install .
Quick Start
import numpy as np
import pylibjxl
# Create a test image (H, W, C)
image = np.random.randint(0, 256, (512, 512, 3), dtype=np.uint8)
# Encode → Decode
data = pylibjxl.encode(image, effort=7, distance=1.0)
decoded = pylibjxl.decode(data)
Usage
Encode / Decode (In-Memory)
import pylibjxl
# Lossy encoding (default)
data = pylibjxl.encode(image, effort=7, distance=1.0)
# Lossless encoding
data = pylibjxl.encode(image, lossless=True)
# Decode
image = pylibjxl.decode(data)
File I/O
# Write to file (creates parent directories automatically)
pylibjxl.write("output.jxl", image, effort=7, distance=1.0)
# Read from file
image = pylibjxl.read("output.jxl")
Metadata (EXIF / XMP / JUMBF)
# Encode with metadata
data = pylibjxl.encode(image, exif=exif_bytes, xmp=xmp_bytes)
# Decode with metadata extraction
image, meta = pylibjxl.decode(data, metadata=True)
print(meta.keys()) # dict_keys(['exif', 'xmp'])
# File I/O with metadata
pylibjxl.write("photo.jxl", image, exif=exif_bytes, xmp=xmp_bytes, jumbf=jumbf_bytes)
image, meta = pylibjxl.read("photo.jxl", metadata=True)
Context Manager
with pylibjxl.JXL(effort=7, distance=1.0) as jxl:
# Encode/decode with shared defaults
data = jxl.encode(image)
result = jxl.decode(data)
# Per-call overrides
data_hq = jxl.encode(image, distance=0.5)
# File I/O
jxl.write("output.jxl", image, exif=exif_bytes)
result, meta = jxl.read("output.jxl", metadata=True)
Async
import asyncio
import pylibjxl
async def main():
# In-memory async
data = await pylibjxl.encode_async(image, exif=exif_bytes)
image, meta = await pylibjxl.decode_async(data, metadata=True)
# File async
await pylibjxl.write_async("output.jxl", image, xmp=xmp_bytes)
image = await pylibjxl.read_async("output.jxl")
# Async context manager
async with pylibjxl.AsyncJXL(effort=5) as jxl:
data = await jxl.encode_async(image)
result = await jxl.decode_async(data)
asyncio.run(main())
JPEG Encode / Decode
import pylibjxl
# Encode to JPEG (via libjpeg-turbo)
jpeg_data = pylibjxl.encode_jpeg(image, quality=95)
# Decode JPEG to numpy array
image = pylibjxl.decode_jpeg(jpeg_data)
JPEG ↔ JXL Transcoding
# Losslessly recompress JPEG → JXL (preserves JPEG reconstruction data)
jxl_data = pylibjxl.jpeg_to_jxl(jpeg_data, effort=7)
# Reconstruct original JPEG from JXL (lossless roundtrip)
jpeg_restored = pylibjxl.jxl_to_jpeg(jxl_data)
# Async variants
jxl_data = await pylibjxl.jpeg_to_jxl_async(jpeg_data)
jpeg_data = await pylibjxl.jxl_to_jpeg_async(jxl_data)
JPEG File I/O
# Write JPEG file
pylibjxl.write_jpeg("photo.jpg", image, quality=95)
# Read JPEG file
image = pylibjxl.read_jpeg("photo.jpg")
# Async
await pylibjxl.write_jpeg_async("photo.jpg", image)
image = await pylibjxl.read_jpeg_async("photo.jpg")
Cross-Format File Conversion
# JPEG → JXL (lossless transcoding, preserves JPEG reconstruction data)
pylibjxl.convert_jpeg_to_jxl("photo.jpg", "photo.jxl")
# JXL → JPEG (lossless reconstruction from transcoded JXL)
pylibjxl.convert_jxl_to_jpeg("photo.jxl", "restored.jpg")
# Async
await pylibjxl.convert_jpeg_to_jxl_async("photo.jpg", "photo.jxl")
await pylibjxl.convert_jxl_to_jpeg_async("photo.jxl", "restored.jpg")
API Reference
Free Functions
encode(input, effort=7, distance=1.0, lossless=False, *, exif=None, xmp=None, jumbf=None) → bytes
Encode a NumPy array to JXL bytes.
| Parameter | Type | Default | Description |
|---|---|---|---|
input |
ndarray |
required | uint8 array of shape (H, W, 3) or (H, W, 4) |
effort |
int |
7 |
Encoding effort [1-10], higher = slower + smaller |
distance |
float |
1.0 |
Perceptual distance [0.0-25.0], 0 = lossless |
lossless |
bool |
False |
If True, encode losslessly (overrides distance) |
exif |
bytes | None |
None |
Raw EXIF metadata to embed |
xmp |
bytes | None |
None |
Raw XMP (XML) metadata to embed |
jumbf |
bytes | None |
None |
Raw JUMBF metadata to embed |
decode(data, *, metadata=False) → ndarray | tuple[ndarray, dict]
Decode JXL bytes to a NumPy array.
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
bytes |
required | JXL-encoded data |
metadata |
bool |
False |
If True, also return metadata dict |
Returns:
metadata=False→ndarrayof shape(H, W, C), dtypeuint8metadata=True→tuple(ndarray, dict)where dict may contain keys:"exif","xmp","jumbf"(asbytes)
read(path, *, metadata=False)
Read a .jxl file from disk. Returns same types as decode().
write(path, image, effort=7, distance=1.0, lossless=False, *, exif=None, xmp=None, jumbf=None)
Encode and write to a .jxl file. Creates parent directories automatically.
encode_async(...) / decode_async(...) / read_async(...) / write_async(...)
Async versions of the above functions — same parameters, returns Awaitable.
Context Managers
JXL(effort=7, distance=1.0, lossless=False)
Synchronous codec context manager with shared defaults.
| Method | Description |
|---|---|
encode(input, effort=None, distance=None, lossless=None, *, exif=None, xmp=None, jumbf=None) |
Encode in-memory |
decode(data, *, metadata=False) |
Decode in-memory |
read(path, *, metadata=False) |
Read from file |
write(path, image, effort=None, distance=None, lossless=None, *, exif=None, xmp=None, jumbf=None) |
Write to file |
AsyncJXL(effort=7, distance=1.0, lossless=False)
Async codec context manager. Methods: encode_async, decode_async, read_async, write_async.
JPEG & Transcoding Functions
encode_jpeg(input, quality=95) → bytes
Encode a NumPy array to JPEG bytes using libjpeg-turbo.
| Parameter | Type | Default | Description |
|---|---|---|---|
input |
ndarray |
required | uint8 array of shape (H, W, 3) or (H, W, 4) |
quality |
int |
95 |
JPEG quality [1-100] |
decode_jpeg(data) → ndarray
Decode JPEG bytes to a NumPy array (H, W, 3) using libjpeg-turbo.
read_jpeg(path) → ndarray
Read a .jpg/.jpeg file from disk. Returns ndarray of shape (H, W, 3).
write_jpeg(path, image, quality=95)
Encode and write to a JPEG file. Creates parent directories automatically.
jpeg_to_jxl(data, effort=7) → bytes
Losslessly recompress JPEG bytes to JXL. Embeds JPEG reconstruction data so the original JPEG can be restored.
jxl_to_jpeg(data) → bytes
Reconstruct the original JPEG bytes from a JXL file (only works if the JXL was created via jpeg_to_jxl).
convert_jpeg_to_jxl(jpeg_path, jxl_path, effort=7)
Convert a JPEG file to JXL file via lossless transcoding. Creates parent directories automatically.
convert_jxl_to_jpeg(jxl_path, jpeg_path)
Reconstruct the original JPEG file from a JXL file. Only works for JPEG-transcoded JXL files.
Async variants
encode_jpeg_async, decode_jpeg_async, read_jpeg_async, write_jpeg_async, jpeg_to_jxl_async, jxl_to_jpeg_async, convert_jpeg_to_jxl_async, convert_jxl_to_jpeg_async — same parameters, returns Awaitable.
Utility Functions
| Function | Returns | Description |
|---|---|---|
version() |
dict |
libjxl version {"major", "minor", "patch"} |
decoder_version() |
int |
Decoder version number |
encoder_version() |
int |
Encoder version number |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pylibjxl-0.1.0-cp312-cp312-macosx_26_0_arm64.whl.
File metadata
- Download URL: pylibjxl-0.1.0-cp312-cp312-macosx_26_0_arm64.whl
- Upload date:
- Size: 10.9 MB
- Tags: CPython 3.12, macOS 26.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d642016fd430949c78b70e265ea265d3edfef917fa967a8d11f39ff4fb0e1dc9
|
|
| MD5 |
6d407c9f0768258f356aff0432599f7d
|
|
| BLAKE2b-256 |
4c740faf4145a7c14442866d5997639b4eb7f229d4a21e83b2d5eef13f17d8af
|