jpegli-rs
A pure Rust JPEG encoder and decoder with perceptual optimizations.
Heritage and Divergence
This project started as a port of jpegli, Google's improved JPEG encoder from the JPEG XL project. It's been rewritten a few times and has diverged to the point it should probably be renamed.
Ideas we adopted from jpegli:
- Adaptive quantization (content-aware bit allocation)
- XYB color space with ICC profiles
- Perceptually-tuned quantization tables
- Zero-bias strategies for coefficient rounding
Where we went our own way:
- Pure Rust,
#![forbid(unsafe_code)]by default (unsafe SIMD is opt-in) - Streaming encoder API for memory efficiency
- Portable SIMD via
widecrate instead of platform intrinsics - Parallel encoding support
- Independent optimizations and bug fixes
Features
- Pure Rust - No C/C++ dependencies, builds anywhere Rust does
- Perceptual optimization - Adaptive quantization for better visual quality at smaller sizes
- Backward compatible - Produces standard JPEG files readable by any decoder
- SIMD accelerated - Portable SIMD via
widecrate - Streaming API - Memory-efficient row-by-row encoding
- Parallel encoding - Multi-threaded for large images (1024x1024+)
- Color management - Optional ICC profile support
API Reference
Encoder API
All encoder types are in jpegli::encoder:
use ;
Quick Start
use ;
// Create reusable config
let config = new
.quality
.progressive;
// Encode from raw bytes
let mut enc = config.encode_from_bytes?;
enc.push_packed?;
let jpeg = enc.finish?;
Three Encoder Entry Points
| Method | Input Type | Use Case |
|---|---|---|
encode_from_bytes(w, h, layout) |
&[u8] |
Raw byte buffers |
encode_from_rgb::<P>(w, h) |
rgb crate types |
RGB<u8>, RGBA<f32>, etc. |
encode_from_ycbcr_planar(w, h) |
YCbCrPlanes |
Video decoder output |
Examples
use ;
let config = new.quality;
// From raw RGB bytes
let mut enc = config.encode_from_bytes?;
enc.push_packed?;
let jpeg = enc.finish?;
// From rgb crate types
use RGB;
let mut enc = config.?;
enc.push_packed?;
let jpeg = enc.finish?;
// From planar YCbCr (video pipelines)
let mut enc = config.encode_from_ycbcr_planar?;
enc.push?;
let jpeg = enc.finish?;
EncoderConfig Builder Methods
| Method | Description | Default |
|---|---|---|
.quality(q) |
Quality 0-100 or Quality enum |
90 |
.progressive(bool) |
Progressive JPEG (~3% smaller) | false |
.optimize_huffman(bool) |
Optimal Huffman tables | true |
.ycbcr(sub) |
YCbCr with subsampling | Quarter (4:2:0) |
.xyb() |
XYB perceptual color space | - |
.grayscale() |
Single-channel output | - |
.sharp_yuv(bool) |
SharpYUV downsampling | false |
.icc_profile(bytes) |
Attach ICC profile | None |
.restart_interval(n) |
MCUs between restart markers | 0 |
Quality Options
use ;
// Simple quality scale (0-100)
let config = new.quality;
// Quality enum variants
let config = new
.quality // Default scale
.quality // Match mozjpeg output
.quality // Target SSIMULACRA2 score
.quality; // Target butteraugli distance
Pixel Layouts
| Layout | Bytes/px | Notes |
|---|---|---|
Rgb8Srgb |
3 | Default, sRGB gamma |
Bgr8Srgb / Bgrx8Srgb |
3/4 | Windows/GDI order |
Rgbx8Srgb |
4 | 4th byte ignored |
Gray8Srgb |
1 | Grayscale sRGB |
Rgb16Linear |
6 | 16-bit linear |
RgbF32Linear |
12 | HDR float (0.0-1.0) |
YCbCr8 / YCbCrF32 |
3/12 | Pre-converted YCbCr |
Chroma Subsampling
use ;
let config = new
.ycbcr // 4:2:0 (default, best compression)
.ycbcr // 4:4:4 (best quality)
.ycbcr // 4:2:2
.ycbcr; // 4:4:0
Resource Estimation
use EncoderConfig;
let config = new.quality;
// Typical memory estimate
let estimate = config.estimate_memory;
// Guaranteed upper bound (for resource reservation)
let ceiling = config.estimate_memory_ceiling;
Decoder API
Prerelease: The decoder API is behind the
decoderfeature flag and will have breaking changes. Enable withjpegli-rs = { version = "...", features = ["decoder"] }.
All decoder types are in jpegli::decoder:
use ;
Basic Decoding
// Decode to RGB (default)
let image = new.decode?;
let pixels: & = image.pixels;
let = image.dimensions;
High-Precision Decoding (f32)
Preserves jpegli's 12-bit internal precision:
let image: DecodedImageF32 = new.decode_f32?;
let pixels: & = image.pixels; // Values in 0.0-1.0
// Convert to 8-bit or 16-bit when needed
let u8_pixels: = image.to_u8;
let u16_pixels: = image.to_u16;
YCbCr Output (Zero Color Conversion)
For video pipelines or re-encoding:
use ;
let ycbcr: DecodedYCbCr = new.decode_to_ycbcr_f32?;
// Access Y, Cb, Cr planes directly (f32, range [-128, 127])
Reading JPEG Info Without Decoding
let info = new.read_info?;
println!;
Decoder Options
| Method | Description | Default |
|---|---|---|
.output_format(fmt) |
Output pixel format | Rgb |
.fancy_upsampling(bool) |
Smooth chroma upsampling | true |
.block_smoothing(bool) |
DCT block edge smoothing | false |
.apply_icc(bool) |
Apply embedded ICC profile | true |
.max_pixels(n) |
Pixel count limit (DoS protection) | 100M |
.max_memory(n) |
Memory limit in bytes | 512 MB |
Decoded Image Methods
let image = new.decode?;
image.width // Image width
image.height // Image height
image.dimensions // (width, height) tuple
image.pixels // &[u8] pixel data
image.bytes_per_pixel // Bytes per pixel for format
image.stride // Bytes per row
DecoderConfig (Advanced)
use ;
// Most users should use the builder methods instead:
let image = new
.fancy_upsampling
.block_smoothing
.apply_icc
.max_pixels
.max_memory
.decode?;
// Or construct DecoderConfig directly:
let config = default;
let decoder = from_config;
Performance
Encoding Speed
| Image Size | Sequential | Progressive | Notes |
|---|---|---|---|
| 512x512 | 118 MP/s | 58 MP/s | Small images |
| 1024x1024 | 92 MP/s | 36 MP/s | Medium images |
| 2048x2048 | 87 MP/s | 46 MP/s | Large images |
Sequential vs Progressive
| Quality | Seq Size | Prog Size | Prog Δ | Prog Slowdown |
|---|---|---|---|---|
| Q50 | 322 KB | 313 KB | -2.8% | 2.5x |
| Q70 | 429 KB | 416 KB | -3.0% | 2.0x |
| Q85 | 586 KB | 568 KB | -3.1% | 2.1x |
| Q95 | 915 KB | 887 KB | -3.1% | 2.2x |
Progressive produces ~3% smaller files at the same quality, but takes ~2x longer.
Recommendation:
- Use Sequential for: real-time encoding, high throughput
- Use Progressive for: web delivery, storage optimization
Decoding Speed
| Decoder | Speed | Notes |
|---|---|---|
| zune-jpeg | 392 MP/s | Integer IDCT, AVX2 |
| jpeg-decoder | 120 MP/s | Integer IDCT |
| jpegli-rs | 47 MP/s | f32 IDCT, 12-bit precision |
The decoder prioritizes precision over speed, matching C++ jpegli's 12-bit pipeline.
C++ Parity Status
Tested against C++ jpegli on frymire.png (1118x1105):
| Metric | Rust | C++ | Difference |
|---|---|---|---|
| File size (Q85 seq) | 586.3 KB | 586.7 KB | -0.1% |
| File size (Q85 prog) | 568.2 KB | 565.1 KB | +0.5% |
| SSIM2 (Q85) | 69.0 | 69.0 | identical |
Quality is identical; file sizes within 0.5%.
Feature Flags
| Feature | Default | Description |
|---|---|---|
decoder |
No | Enable decoder API (prerelease, API will change) |
cms-lcms2 |
Yes | Color management via lcms2 |
cms-moxcms |
No | Pure Rust color management |
unsafe_simd |
No | Raw AVX2/SSE intrinsics (~10-20% faster) |
test-utils |
Yes | Testing utilities |
By default, the crate uses #![forbid(unsafe_code)]. SIMD is provided via the safe, portable wide crate. Enable unsafe_simd for raw intrinsics on x86_64.
[]
= "0.5"
# Minimal (no CMS):
= { = "0.5", = false }
# With unsafe SIMD (x86_64 only):
= { = "0.5", = ["unsafe_simd"] }
Encoder Status
| Feature | Status |
|---|---|
| Baseline JPEG | Working |
| Progressive JPEG | Working |
| Adaptive quantization | Working |
| Huffman optimization | Working |
| 4:4:4 / 4:2:0 / 4:2:2 / 4:4:0 | Working |
| XYB color space | Working |
| Grayscale | Working |
| Custom quant tables | Working |
| ICC profile embedding | Working |
| YCbCr planar input | Working |
Decoder Status
Prerelease: Enable with
features = ["decoder"]. API will have breaking changes.
| Feature | Status |
|---|---|
| Baseline JPEG | Working |
| Progressive JPEG | Working |
| All subsampling modes | Working |
| Restart markers | Working |
| ICC profile extraction | Working |
| XYB decoding | Working (with CMS) |
| f32 output | Working |
Development
Verify C++ Parity
# Quick parity test (no C++ build needed)
# Full comparison (requires C++ jpegli built)
Building C++ Reference (Optional)
&& &&
License
AGPL-3.0-or-later
A commercial license is available from https://imageresizing.net/pricing
Acknowledgments
Originally a port of jpegli from the JPEG XL project by Google (BSD-3-Clause). After six rewrites, this is now an independent project that shares ideas but little code with the original.
AI Disclosure
Developed with assistance from Claude (Anthropic). Extensively tested against C++ reference with 340+ tests. Report issues at https://github.com/imazen/jpegli-rs/issues