zenwebp
Pure Rust WebP encoding and decoding. No C dependencies, no unsafe code.
Getting Started
Add to your Cargo.toml:
[]
= "0.3"
📚 New to zenwebp? Check out the comprehensive API guide that demonstrates 100% of the public API with runnable examples.
Decode a WebP image
use WebPDecoder;
let webp_bytes: & = /* your WebP data */;
// Two-phase decoding: parse headers first
let mut decoder = build?;
let info = decoder.info;
println!;
// Then decode into a pre-allocated buffer
let mut output = vec!;
decoder.read_image?;
Encode to WebP (Lossy)
use ;
let rgb_pixels: & = /* your RGB data */;
let = ;
// Create reusable config
let config = new
.with_quality
.with_method; // 0=fast, 6=best
// Encode
let webp = lossy
.encode?;
Encode to WebP (Lossless)
use ;
let rgba_pixels: & = /* your RGBA data */;
let = ;
let config = new
.with_quality
.with_method;
let webp = lossless
.encode?;
Features
- Pure Rust - no C dependencies, builds anywhere Rust does
#![forbid(unsafe_code)]- memory safety guaranteed- no_std compatible - works with just
alloc, no standard library needed - SIMD accelerated - SSE2/SSE4.1/AVX2 on x86, SIMD128 on WASM
- Full format support - lossy, lossless, alpha, animation (encode + decode), ICC/EXIF/XMP metadata, mux/demux
- Metadata module -
zenwebp::metadatafor extracting/embedding ICC, EXIF, and XMP in encoded WebP bytes without decoding pixels
Safe SIMD
We achieve both safety and performance through safe abstractions over CPU intrinsics:
archmageandmagetypes- token-gated safe intrinsics with runtime CPU detectionsafe_unaligned_simd- safe unaligned load/store
These abstractions may not be perfect, but we trust them over hand-rolled unsafe code.
Decoder
Supports all WebP features: lossy and lossless compression, alpha channel, animation, and extended format with ICC/EXIF/XMP chunks.
Encoder
Supports lossy and lossless encoding with configurable quality (0-100) and speed/quality tradeoff (method 0-6).
use ;
// Lossless encoding
let config = new.with_quality;
let webp = lossless.encode?;
// Fast lossy encoding (larger files)
let config = new.with_quality.with_method;
let webp = lossy.encode?;
// High quality lossy (slower, smaller files)
let config = new.with_quality.with_method;
let webp = lossy.encode?;
Feature Comparison with libwebp
zenwebp aims to be a drop-in replacement for libwebp in most use cases. Here's what's implemented and what's not.
Decoder
| Feature | zenwebp | libwebp |
|---|---|---|
| Lossy (VP8) | Yes | Yes |
| Lossless (VP8L) | Yes | Yes |
| Alpha channel | Yes | Yes |
| Animation (ANIM/ANMF) | Yes (read + write) | Yes (read + write) |
| Extended format (VP8X) | Yes | Yes |
| ICC/EXIF/XMP metadata | Yes (raw bytes) | Yes (raw bytes) |
| Output: RGB, RGBA | Yes | Yes |
| Output: BGR, BGRA | Yes | Yes |
| Output: ARGB | No | Yes |
| Output: YUV 4:2:0 | Yes | Yes |
| Output: RGB565, RGBA4444 | No | Yes |
| Premultiplied alpha output | No | Yes |
| Fancy chroma upsampling | Yes | Yes |
| Simple (nearest) upsampling | Yes | Yes |
| Incremental/streaming decode | No | Yes |
| Crop during decode | No | Yes |
| Scale during decode | No | Yes |
| Threaded decoding | No | Yes |
| Dithering | No | Yes |
Encoder (Lossy VP8)
| Feature | zenwebp | libwebp |
|---|---|---|
| Quality (0-100) | Yes | Yes |
| Method (0-6) speed/quality | Yes | Yes |
| Presets (Photo, Drawing, etc.) | Yes (7 presets, including Auto) | Yes (6 presets) |
| Target file size | Yes (secant method) | Yes (multi-pass) |
| Target PSNR | Yes (secant method) | Yes |
| SNS (spatial noise shaping) | Yes | Yes |
| Filter strength/sharpness | Yes | Yes |
| Autofilter | Yes | Yes |
| Segments (1-4) | Yes | Yes |
| Token partitions | 1 partition | 1-8 partitions |
| Intra16 modes (DC/V/H/TM) | Yes | Yes |
| Intra4 modes (10 modes) | Yes | Yes |
| Trellis quantization | Yes (m5-6) | Yes (m5-6) |
| Alpha channel encoding | Yes (lossless + lossy quantization) | Yes (lossless + lossy quantization) |
| Sharp YUV conversion | Yes (via yuv crate, fast-yuv feature) |
Yes (libsharpyuv) |
| Multi-pass encoding | Yes | Yes |
| Near-lossless | Yes | Yes |
| Input: RGB, RGBA | Yes | Yes |
| Input: L8 (grayscale) | Yes | No (requires conversion) |
| Input: BGR, BGRA | Yes | Yes |
| Input: YUV 4:2:0 | Yes | Yes |
| Encoding statistics | Yes (EncodeStats) | Yes (WebPAuxStats) |
| Progress callback | Yes (EncodeProgress + Stop) | Yes |
| Threaded encoding | No | Yes (alpha parallel) |
Encoder (Lossless VP8L)
| Feature | zenwebp | libwebp |
|---|---|---|
| Predictor transform (14 modes) | Yes | Yes |
| Cross-color transform | Yes | Yes |
| Subtract green transform | Yes | Yes |
| Color indexing (palette) | Yes | Yes |
| Palette sorting strategies | Yes (2) | Yes |
| Pixel bundling (2/4/8 per pixel) | Yes | Yes |
| Color cache (auto-sized) | Yes | Yes |
| LZ77 Standard | Yes | Yes |
| LZ77 RLE | Yes | Yes |
| LZ77 Box | Yes (palette images) | Yes (palette images) |
| TraceBackwards DP | Yes (Zopfli-style) | Yes (Zopfli-style) |
| Meta-Huffman (spatial codes) | Yes | Yes |
| Multi-config testing | Yes (m5-6) | Yes (m5-6) |
| Near-lossless | Yes (pixel + residual) | Yes (pixel + residual) |
| AnalyzeEntropy selection | Yes (5 modes) | Yes (5 modes) |
Container / Metadata
| Feature | zenwebp | libwebp |
|---|---|---|
| RIFF container read | Yes | Yes |
| RIFF container write | Yes | Yes |
| VP8X extended format | Yes | Yes |
| ICC profile read | Yes | Yes |
| EXIF metadata read | Yes | Yes |
| XMP metadata read | Yes | Yes |
| Metadata write (ICC/EXIF/XMP) | Yes | Yes (via libwebpmux) |
| Animation write | Yes (AnimationEncoder) | Yes (WebPAnimEncoder) |
| Mux API (assemble chunks) | Yes (WebPMux) | Yes (libwebpmux) |
| Demux API (frame iteration) | Yes (WebPDemuxer) | Yes (libwebpdemux) |
Platform / Build
| Feature | zenwebp | libwebp |
|---|---|---|
| Language | Pure Rust | C |
| Unsafe code | #![forbid(unsafe_code)] |
N/A (C) |
| no_std + alloc | Yes | No |
| WASM | Yes (SIMD128) | Yes (via Emscripten/SIMDe) |
| SSE2 | Yes | Yes |
| SSE4.1 | Yes | Yes |
| AVX2 | Yes | No |
| NEON (ARM64) | Yes | Yes |
| MIPS DSP | No | Yes |
| Runtime CPU detection | Yes | Yes |
| Custom allocator | No (uses alloc) | No (compile-time only) |
What zenwebp has that libwebp doesn't
- no_std support - use in embedded, WASM, or kernel contexts with just
alloc - Memory safety -
#![forbid(unsafe_code)], no buffer overflows by construction - AVX2 SIMD - wider SIMD for loop filter and YUV conversion
- Auto preset - content-aware preset selection based on image analysis
- Grayscale input - direct L8/LA8 encoding without manual conversion
- Cooperative cancellation - separate
Stoptoken for external cancellation (viaenoughcrate)
Performance
Decoder benchmarks
Tested across three corpora with varying image sizes and content:
| Corpus | Images | Megapixels | zenwebp | image-webp | zenwebp speedup |
|---|---|---|---|---|---|
| CLIC2025 | 15 | 42.6 | 1.28x slower | 2.78x slower | 2.2x faster |
| Screenshots | 9 | 64.4 | 1.22x slower | 2.46x slower | 2.0x faster |
| CID22 | 15 | 3.9 | 1.67x slower | 2.98x slower | 1.8x faster |
All ratios vs libwebp (C). Lower is better. zenwebp consistently ~2x faster than image-webp.
Encoder benchmarks
| Corpus | zenwebp m5 | libwebp m5 | Size ratio |
|---|---|---|---|
| CID22 (248 images) | - | - | 1.0002x |
| Screenshots (13 images) | - | - | 1.0022x |
Encoding speed is ~1.5-1.7x slower than libwebp. File sizes within 0.2% of libwebp at method 5.
Quality
At the same quality setting, zenwebp produces files within 1-5% of libwebp's size with comparable visual quality. Quality is slightly better than libwebp below Q75 and slightly worse above Q75.
no_std Support
[]
= { = "0.3", = false }
Both encoder and decoder work without std. The decoder takes &[u8] slices and the encoder writes to Vec<u8>. Only encode_to_writer() requires the std feature.
Comparison with image-webp
This crate is forked from image-webp, the official pure-Rust
WebP crate from the image-rs project. Both are excellent choices depending on your needs.
When to use image-webp (upstream)
| Aspect | image-webp |
|---|---|
| Safety | Zero unsafe code in crate AND all dependencies |
| Codebase | ~10,600 lines - small, auditable |
| Dependencies | 2 (byteorder-lite, quick-error) |
| Decoder speed | ~2.5-3x slower than libwebp |
| Encoder | Lossless only, basic but fast |
| Best for | Security-critical contexts, minimal attack surface |
Choose image-webp if: You need maximum assurance of memory safety, minimal dependencies, or only need lossless encoding. The smaller codebase is easier to audit.
When to use zenwebp
| Aspect | zenwebp |
|---|---|
| Safety | #![forbid(unsafe_code)] but relies on archmage for SIMD |
| Codebase | ~41,000 lines (+30k for lossy encoder, same decoder base) |
| Dependencies | 14 (7 optional) |
| Decoder speed | ~1.4-1.7x slower than libwebp (~2x faster than image-webp) |
| Encoder | Lossy + lossless, matches libwebp compression |
| Best for | Full WebP support, lossy encoding, libwebp replacement |
Choose zenwebp if: You need lossy encoding, faster decoding, libwebp-compatible compression, or features like animation encoding, near-lossless, or target file size.
Honest tradeoffs
Code size: We added ~30,000 lines to implement lossy encoding matching libwebp. This is a significant increase in attack surface and audit burden compared to image-webp's compact codebase.
Safety model: We use #![forbid(unsafe_code)] which prevents any direct unsafe in our source.
However, our SIMD acceleration depends on the archmage crate, whose #[arcane] proc macro
generates unsafe blocks internally (to call CPU intrinsics). The generated unsafe bypasses
Rust's forbid lint due to proc-macro span handling. We consider archmage's token-based safety
model reasonable - tokens are only created after runtime CPU feature detection, and we don't
forge tokens - but this is not the same as image-webp's truly zero-unsafe guarantee where both
the crate and all dependencies contain no unsafe whatsoever.
Without the simd feature, zenwebp contains no unsafe code at all, but decoding will be slower.
Feature additions over image-webp
- Lossy VP8 encoder - full RD optimization, trellis quantization, all I4/I16 modes
- ~2x faster decoder - SIMD loop filter, YUV conversion, coefficient decoding
- Animation encoding - AnimationEncoder with frame timing
- Near-lossless - pixel and residual quantization
- Target file size - secant method convergence
- SIMD - SSE2/SSE4.1/AVX2 via archmage (but at cost of indirect unsafe)
- no_std - both encoder and decoder work with just
alloc
License
Sustainable, large-scale open source work requires a funding model, and I have been doing this full-time for 15 years. If you are using this for closed-source development AND make over $1 million per year, you'll need to buy a commercial license at https://www.imazen.io/pricing
Commercial licenses are similar to the Apache 2 license but company-specific, and on a sliding scale. You can also use this under the AGPL v3.
Previous Versions
Versions 0.1.x - 0.2.x were dual-licensed under MIT OR Apache-2.0. See the git history for those license files.
Contributing
Contributions welcome! Please feel free to open issues or pull requests.
Credits
This project builds on excellent work by others:
-
image-rs/image-webp - The foundation of this crate. The image-rs team built a complete, correct, truly-safe WebP decoder and lossless encoder. We forked their work and added lossy encoding on top. If you don't need lossy encoding, consider using their crate directly for a smaller, simpler dependency.
-
libwebp (Google) - Reference implementation. Our lossy encoder closely follows libwebp's algorithms for RD optimization, trellis quantization, and mode selection. The WebP format itself is Google's creation.
-
safe_unaligned_simd - Safe unaligned SIMD operations
-
Claude (Anthropic) - AI-assisted development
Code review recommended for production use.