webpx
Ergonomic FFI bindings to Google's libwebp, with support for static images, animations, ICC profiles, streaming, and no_std.
Use zenwebp instead
For any new project, reach for zenwebp. It is equally or more capable than webpx on every axis that matters:
- Full feature parity with libwebp: lossy and lossless encode and decode, animation, alpha, ICC / EXIF / XMP metadata, streaming, content presets, resource limits.
- Native
wasm32-unknown-unknownsupport — pure Rust, no C compiler, no emscripten.webpxrequires emscripten because libwebp is C. #![forbid(unsafe_code)]— pure Rust top to bottom. Zero FFI surface, zerounsafeblocks.
Performance and compression are essentially a wash:
- libwebp can be up to 35 % faster on specific photos, but it can also be up to 2.5× slower on others. Net wash unless you're tuned to specific content types.
- Encoded-size difference is at most ~0.02 % — noise, not meaningful.
The security argument is concrete, not theoretical:
- libwebp has a documented history of high-severity vulnerabilities. CVE-2023-4863 was a heap buffer overflow in
BuildHuffmanTableactively exploited in the wild against Chrome, Safari, Firefox, and Electron apps via a 0-click attack chain — patched out of band on every major platform. That is the failure mode an FFI wrapper inherits, not a hypothetical. - Every libwebp wrapper that has been audited has shipped soundness bugs,
webpxincluded. Versions 0.1.0–0.1.4 are yanked, and 0.2.0 + 0.2.1 fixed multiple stride-overflow / use-after-free / aliasing issues found across two parallel audit passes. If you adopt a libwebp wrapper, you are taking on that exposure.
zenwebp's #![forbid(unsafe_code)] makes that whole class of bug structurally impossible. Use it.
webpx is maintained for users whose application already links libwebp through another path (existing C / C++ code, system package) and would prefer to share that codebase, or who specifically need libwebp's MIPS DSP code paths. If that's not you, switch.
Why use webpx anyway?
- Ergonomic Rust API — Builder patterns, strong types, comprehensive error handling,
Limitspolicy for untrusted-input decoding - Shares an existing libwebp link — If your application already links libwebp via another path (C / C++ code, system package),
webpxreuses that codebase rather than pulling in a second WebP implementation - MIPS DSP — libwebp ships hand-written MIPS DSP-R2 / DSP-ASE assembly paths. If you target that hardware,
webpxinherits them;zenwebpdoes not. - Up to 35 % faster on specific photos — libwebp's hand-tuned VP8 path beats pure-Rust on some content. Note that it can also be up to 2.5× slower on other content; benchmark your actual workload.
Quick Start
[]
= "0.2"
use ;
// Encode RGBA pixels to WebP
let webp = new_rgba
.quality
.encode?;
// Decode WebP back to RGBA
let = decode_rgba?;
Decoding untrusted input
If you decode WebP files supplied by users (HTTP request bodies, uploaded
files, etc.), apply a Limits policy to bound resource usage before
libwebp allocates pixel buffers. Without one, an attacker can declare a
16383×16383 canvas (libwebp's intrinsic max ≈ 1 GiB at 4 bpp) and force
your process into OOM territory. With Limits set, oversized inputs are
rejected at parse time with Error::LimitExceeded.
use ;
let limits = none
.with_max_pixels // 64 MP per frame (≈256 MB at 4 bpp)
.with_max_total_pixels // 256 MP cumulative across animation frames
.with_max_frames // sane animation cap
.with_max_metadata_bytes; // 4 MB ICCP/EXIF/XMP
let img = new?
.config
.decode_rgba?;
The same Limits value also wires into AnimationDecoder::with_options_limits
and mux::get_icc_profile_with_limits (and the _with_limits variants
for EXIF / XMP). Field naming matches zencodec::ResourceLimits so a
single shared policy carries cleanly between Imazen codecs.
Features at a Glance
| Feature | Description |
|---|---|
| Lossy Encoding | VP8-based compression with quality 0-100 |
| Lossless Encoding | Exact pixel preservation |
| Alpha Channel | Full transparency support with separate quality control |
| Animation | Multi-frame WebP with timing control |
| ICC Profiles | Embed/extract color profiles |
| EXIF/XMP | Preserve camera metadata |
| Streaming | Decode as data arrives |
| Cropping/Scaling | Decode to any size |
| YUV Support | Direct YUV420 input/output |
| Content Presets | Optimized settings for photos, drawings, icons, text |
| Resource Limits | Limits policy: per-frame & cumulative pixel caps, frame count, metadata size, ... |
| Cancellation | Cooperative cancellation via enough crate |
Examples
Basic Encoding
use ;
// Lossy encoding (quality 0-100)
let webp = new_rgba
.quality
.encode?;
// Lossless encoding (exact pixels)
let webp = new_rgba
.lossless
.encode?;
// RGB without alpha
let webp = new_rgb
.quality
.encode?;
Builder API with Options
use ;
let webp = new_rgba
.preset // Content-aware optimization
.quality // Higher quality
.method // Better compression (slower)
.alpha_quality // High-quality alpha
.sharp_yuv // Better color accuracy
.encode?;
Advanced Configuration
use EncoderConfig;
// Maximum compression (slow but smallest files)
let config = max_compression;
let webp = config.encode_rgba?;
// Maximum quality lossless
let config = max_compression_lossless;
let webp = config.encode_rgba?;
// Fine-grained control
let config = new
.quality
.method
.filter_strength
.sns_strength
.segments
.pass
.preprocessing;
let = config.encode_rgba_with_stats?;
println!;
Decoding with Processing
use Decoder;
let decoder = new?;
// Get image info without decoding
let info = decoder.info;
println!;
// Decode with cropping and scaling
let = decoder
.crop // Extract region
.scale // Resize
.decode_rgba_raw?;
Animation
use ;
// Create animated WebP
let mut encoder = new?;
encoder.set_quality;
encoder.set_lossless;
encoder.add_frame_rgba?; // Start at 0ms
encoder.add_frame_rgba?; // Show at 100ms
encoder.add_frame_rgba?; // Show at 200ms
let webp = encoder.finish?; // End timestamp
// Decode animation. Use `with_options_limits` if the input is
// untrusted — `max_total_pixels` covers the W × H × frame_count
// case (a 1000×1000 × 200-frame animation has 200 MP cumulative
// even when each frame fits a per-frame `max_pixels` cap).
let limits = none
.with_max_pixels
.with_max_total_pixels
.with_max_frames
.with_max_animation_ms // 60 s of animation
.with_max_input_bytes; // 64 MB bitstream
let mut decoder = with_options_limits?;
let info = decoder.info;
println!;
// Iterate frames
while let Some = decoder.next_frame?
// Or get all at once (this also enforces `max_animation_ms` against
// the cumulative timestamp).
decoder.reset;
let frames = decoder.decode_all?;
ICC Profiles & Metadata
use ;
// Embed ICC profile
let webp_with_icc = embed_icc?;
// Extract — apply `max_metadata_bytes` to bound the ICCP/EXIF/XMP
// chunk size even if the bitstream declares it as huge. Without
// limits, an internal 256 MiB hard cap still applies.
let limits = none.with_max_metadata_bytes;
if let Some = get_icc_profile_with_limits?
// EXIF data
let webp_with_exif = embed_exif?;
if let Some = get_exif_with_limits?
Streaming Decode
use ;
let mut decoder = new?;
// Feed data as it arrives
for chunk in network_stream
let = decoder.finish?;
Cooperative Cancellation
Encoding can be cancelled cooperatively using the enough crate:
use ;
use ;
use Arc;
// Create a cancellation flag
let cancelled = new;
let flag = cancelled.clone;
// Custom Stop implementation
;
// In another thread: flag.store(true, Ordering::Relaxed);
match new_rgba
.quality
.encode
For ready-to-use cancellation primitives (timeouts, channels, etc.), see the almost-enough crate.
Feature Flags
| Feature | Default | Description |
|---|---|---|
decode |
Yes | WebP decoding |
encode |
Yes | WebP encoding |
std |
Yes | Use std (disable for no_std + alloc) |
animation |
No | Animated WebP support |
icc |
No | ICC/EXIF/XMP metadata |
streaming |
No | Incremental decode/encode |
# All features
= { = "0.1", = ["animation", "icc", "streaming"] }
# no_std
= { = "0.1", = false, = ["decode", "encode"] }
Content Presets
Choose a preset to optimize for your content type:
| Preset | Best For | Characteristics |
|---|---|---|
Default |
General use | Balanced settings |
Photo |
Photographs | Better color, outdoor scenes |
Picture |
Indoor/portraits | Skin tone optimization |
Drawing |
Line art | High contrast, sharp edges |
Icon |
Small images | Color preservation |
Text |
Screenshots | Crisp text rendering |
use ;
let webp = new_rgba
.preset
.encode?;
Platform Support
| Platform | Status |
|---|---|
| Linux x64/ARM64 | ✅ Full support |
| macOS x64/ARM64 | ✅ Full support |
| Windows x64/ARM64 | ✅ Full support |
| WebAssembly (emscripten) | ✅ Supported |
| WebAssembly (wasm32-unknown-unknown) | ❌ Not supported (use zenwebp, which is native to this target) |
| MIPS / MIPS DSP | ✅ Inherits libwebp's hand-tuned DSP-R2 paths |
Building for WebAssembly
# Install emscripten
&& &&
# Add target and build
Migration from Other Crates
From webp crate
// Before
use ;
// After - use compat shim
use ;
// API is compatible, just change the import
From webp-animation crate
// Before
use ;
// After - use compat shim
use ;
// Uses finalize() instead of finish() to match original API
Performance Tips
- Use appropriate
method- Higher values (4-6) give better compression but are slower - Choose the right preset - Presets tune internal parameters for content type
- Consider
sharp_yuv- Better color accuracy at slight speed cost - Batch frames - For animations, encode multiple frames before finalizing
- Pre-allocate buffers - Use
StreamingDecoder::with_buffer()to avoid allocations
Minimum Supported Rust Version
Rust 1.80 or later.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributing
Contributions welcome! Please open issues and pull requests on GitHub.
AI-Generated Code Notice
This crate was developed with assistance from Claude (Anthropic). Not all code has been manually reviewed. Please review critical paths before production use.