A Pure Ruby JPEG XL decoder and lossless Modular encoder
Features · Installation · Quick Start · Decoding · Encoding · Development
jxl implements JPEG XL decoding and lossless Modular encoding entirely in Ruby. It supports Level 5 raw
codestreams and containers without calling libjxl, FFI, or native extensions.
- Raw JPEG XL codestreams and
jxlc/jxlpcontainers - Modular and VarDCT decoding, including progressive frames
- Animation, blending, reference frames, and all EXIF orientations
- Gaborish, EPF, upsampling, patches, splines, and noise
- Alpha, Black/CMYK, SpotColor, Depth, and other extra channels
- XYB, YCbCr, standard transfer functions, and embedded ICC profiles
- PNG, PPM/PGM, PAM, PFM, PGX, and multi-frame NPY output
- Packed RGBA8, RGBA16, and binary32 pixel output
- Lossless RGB Modular encoding compatible with libjxl
- Input, pixel-count, and decoded-memory limits
Add the gem to your application:
bundle add jxlOr install it directly:
gem install jxl- Ruby 3.2 or newer
- YJIT is recommended for large images
Decode a JPEG XL image and write it as PNG:
require "jxl"
image = JXL.decode(File.binread("input.jxl"))
File.binwrite("output.png", JXL::IO::PNG.dump(image))Encode an image as a lossless Modular codestream:
encoded = JXL.encode(image, bits_per_sample: 8)
File.binwrite("output.jxl", encoded)The command-line tools provide the same basic workflow:
djxl input.jxl output.png
cjxl input.png output.jxl
jxlinfo input.jxlimage = JXL.decode(
File.binread("input.jxl"),
color_space: :srgb,
max_pixels: 100_000_000,
max_memory: 2 << 30
)| Option | Default | Description |
|---|---|---|
pixel_format |
:native |
Validates :native, :rgba8, :rgba16, or :float32 compatibility |
color_space |
:original |
Returns :original, :srgb, or :linear_srgb colour values |
desired_scale |
1 |
Downsamples the returned image by 1, 2, 4, or 8 |
max_pixels |
100_000_000 |
Rejects images exceeding the pixel limit |
max_memory |
2 GiB |
Rejects input whose decoded-memory estimate exceeds the limit |
strict |
true |
Rejects non-finite decoded samples when enabled |
apply_orientation |
true |
Applies the image orientation to decoded pixels |
render_spot_colors |
true |
Composites spot-colour channels into the colour image |
Read metadata without decoding pixels:
info = JXL.info(File.binread("input.jxl"))
puts "#{info.width}x#{info.height}"Access native planes, extra channels, animation frames, or packed pixels from the decoded image:
image.channels
image.extra_channels
image.frames
image.to_rgba8
image.to_rgba16
image.to_floatJXL::Decoder accepts compressed input in chunks and emits subscribed events:
decoder = JXL::Decoder.new.subscribe(:basic_info, :frame, :full_image)
File.open("input.jxl", "rb") do |file|
decoder.feed(chunk) while (chunk = file.read(65_536))
end
decoder.finish
while (event = decoder.next_event)
puts event.type
endThe event decoder can expose metadata before the complete input arrives, but currently buffers the full compressed image before emitting frame pixels.
| Format | Writer | Notes |
|---|---|---|
| PNG | JXL::IO::PNG |
8-bit output with ICC profile |
| PPM/PGM | JXL::IO::PPM |
Binary RGB or grayscale |
| PAM | JXL::IO::PAM |
Supports alpha |
| PFM | JXL::IO::PFM |
Floating-point RGB or grayscale |
| PGX | JXL::IO::PGX |
Big-endian grayscale |
| NPY | JXL::IO::NPY |
Floating-point, multi-frame capable |
The encoder writes lossless RGB Modular codestreams using RCT6, a Gradient predictor, and prefix entropy coding.
bytes = JXL.encode(image, bits_per_sample: 8)The cjxl command accepts binary PPM/PGM and non-interlaced 8-bit PNG input:
cjxl input.png output.jxlEncoded output round-trips through both this gem and libjxl. Lossy VarDCT encoding and JPEG recompression are not part of the version 1.0 encoder.
Version 1.0 passes all 23 cases in the current upstream Level 5 conformance manifest in lax pixel mode. The 20 mandatory cases that do not require optional JPEG bitstream reconstruction also pass the official strict checks, including metadata and original ICC validation.
The following optional features are not currently implemented:
- JPEG bitstream reconstruction from
jbrd - Brotli-compressed
brobmetadata - Preview rendering
strict: false only replaces non-finite decoded samples. Structural corruption continues to raise JXL::Error.
Install dependencies and run the default test and lint tasks:
bundle install
bundle exec rakeAdditional checks:
bundle exec rake fuzz:truncate
bundle exec rake fuzz:bitflip
bundle exec rake fuzz:box
bundle exec rake benchSet JXL_CONFORMANCE_CASES to the conformance suite's main_level5.txt to enable RSpec reference comparisons.
After running the official conformance runner with --results, print its measured results with:
JXL_CONFORMANCE_RESULTS=results.json bundle exec rake conformance:reportBug reports and pull requests are welcome at https://github.com/ydah/jxl-ruby.
Released under the MIT License.