pyvips
| Language | Python |
|---|---|
| License | MIT (libvips: LGPL-2.1) |
| Repository | github.com/libvips/pyvips |
| Documentation | pyvips docs, jxlsave options |
| Depends on | libvips built with libjxl |
| Read / write | yes / yes, lossy and lossless, 8/16-bit and float, animation |
pyvips is the Python binding of libvips, an image processing library that streams images in small pieces instead of loading them whole. It is much faster than Pillow for resizing and converting, and handles images larger than memory. JPEG XL support comes from libjxl.
You need a libvips built with libjxl. The prebuilt pyvips-binary wheels don’t include libjxl, so install libvips from your system package manager instead: Homebrew’s vips and the libvips packages of current Linux distributions include JPEG XL. Check with vips -l | grep jxl.
Installation#
sudo apt install libvips-dev && pip install pyvips # Debian / Ubuntu
sudo dnf install vips-devel && pip install pyvips # Fedora
brew install vips && pip install pyvips # macOS
On Windows, download a libvips build from libvips releases (“all” variant, which includes libjxl), add its bin folder to PATH, then pip install pyvips.
Basic usage: convert JXL to PNG and back#
import pyvips
image = pyvips.Image.new_from_file("in.jxl")
print(image.width, image.height, image.bands, image.format)
image.write_to_file("out.png")
photo = pyvips.Image.new_from_file("photo.png")
photo.write_to_file("photo.jxl", Q=90)
Save options can be given to write_to_file as keyword arguments, or after the file name: photo.write_to_file("photo.jxl[Q=90]").
How to save a lossless JPEG XL#
image = pyvips.Image.new_from_file("screenshot.png")
image.jxlsave("screenshot.jxl", lossless=True)
How to set distance and effort#
image.jxlsave(
"photo.jxl",
distance=1.0, # max error: 0 lossless, 1.0 visually lossless (default), up to 15
effort=7, # 1-9: higher is slower and smaller
)
Q (JPEG-like quality) is an alternative to distance; set one of them.
How to make a JXL thumbnail#
thumbnail decodes only what it needs and resizes in one step:
thumb = pyvips.Image.thumbnail("big.jxl", 400) # fit in 400 x 400
thumb.write_to_file("thumb.jpg", Q=85)
How to read metadata#
image = pyvips.Image.new_from_file("in.jxl")
for name in image.get_fields():
print(name)
if image.get_typeof("exif-data") != 0:
exif = image.get("exif-data") # raw Exif bytes
How to convert JXL to JPEG in memory#
data = open("in.jxl", "rb").read()
image = pyvips.Image.new_from_buffer(data, "")
jpeg_bytes = image.write_to_buffer(".jpg", Q=85)
Tips#
- Pass
access="sequential"tonew_from_filewhen you read the image once from top to bottom (e.g. a conversion): it lowers memory use. - For animated JXL, load all frames with
pyvips.Image.new_from_file("anim.jxl", n=-1); frames are stacked vertically andimage.get("page-height")gives the frame height. - libvips converts JPEG input to pixels; it doesn’t do lossless JPEG recompression. Use
cjxlfor that.