4 releases
| new 0.1.3 | Feb 2, 2026 |
|---|---|
| 0.1.2 | Jan 21, 2026 |
| 0.1.1 | Jan 21, 2026 |
| 0.1.0 | Jan 20, 2026 |
#852 in Images
245KB
4.5K
SLoC
webpx
Complete WebP encoding and decoding for Rust - safe bindings to Google's libwebp with support for static images, animations, ICC profiles, streaming, and no_std.
Why webpx?
- Full libwebp features - Lossy, lossless, animation, alpha, metadata
- Safe & ergonomic API - Builder patterns, strong types, comprehensive error handling
- High performance - Zero-copy where possible, direct FFI to optimized C code
- Flexible - Works with
no_std, supports WebAssembly via emscripten - Migration-friendly - Compatibility shims for
webpandwebp-animationcrates
Quick Start
[dependencies]
webpx = "0.1"
use webpx::{Encoder, decode_rgba, Unstoppable};
// Encode RGBA pixels to WebP
let webp = Encoder::new_rgba(&pixels, width, height)
.quality(85.0)
.encode(Unstoppable)?;
// Decode WebP back to RGBA
let (pixels, w, h) = decode_rgba(&webp)?;
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 |
| Cancellation | Cooperative cancellation via enough crate |
Examples
Basic Encoding
use webpx::{Encoder, Unstoppable};
// Lossy encoding (quality 0-100)
let webp = Encoder::new_rgba(&rgba_data, 640, 480)
.quality(85.0)
.encode(Unstoppable)?;
// Lossless encoding (exact pixels)
let webp = Encoder::new_rgba(&rgba_data, 640, 480)
.lossless(true)
.encode(Unstoppable)?;
// RGB without alpha
let webp = Encoder::new_rgb(&rgb_data, 640, 480)
.quality(85.0)
.encode(Unstoppable)?;
Builder API with Options
use webpx::{Encoder, Preset, Unstoppable};
let webp = Encoder::new_rgba(&rgba_data, 640, 480)
.preset(Preset::Photo) // Content-aware optimization
.quality(90.0) // Higher quality
.method(5) // Better compression (slower)
.alpha_quality(95) // High-quality alpha
.sharp_yuv(true) // Better color accuracy
.encode(Unstoppable)?;
Advanced Configuration
use webpx::EncoderConfig;
// Maximum compression (slow but smallest files)
let config = EncoderConfig::max_compression();
let webp = config.encode_rgba(&data, width, height)?;
// Maximum quality lossless
let config = EncoderConfig::max_compression_lossless();
let webp = config.encode_rgba(&data, width, height)?;
// Fine-grained control
let config = EncoderConfig::new()
.quality(85.0)
.method(6)
.filter_strength(60)
.sns_strength(80)
.segments(4)
.pass(6)
.preprocessing(4);
let (webp, stats) = config.encode_rgba_with_stats(&data, width, height)?;
println!("PSNR: {:.2} dB, size: {} bytes", stats.psnr[4], stats.coded_size);
Decoding with Processing
use webpx::Decoder;
let decoder = Decoder::new(&webp_data)?;
// Get image info without decoding
let info = decoder.info();
println!("{}x{}, alpha: {}", info.width, info.height, info.has_alpha);
// Decode with cropping and scaling
let (pixels, w, h) = decoder
.crop(100, 100, 400, 300) // Extract region
.scale(200, 150) // Resize
.decode_rgba_raw()?;
Animation
use webpx::{AnimationEncoder, AnimationDecoder};
// Create animated WebP
let mut encoder = AnimationEncoder::new(320, 240)?;
encoder.set_quality(80.0);
encoder.set_lossless(false);
encoder.add_frame_rgba(&frame1_rgba, 0)?; // Start at 0ms
encoder.add_frame_rgba(&frame2_rgba, 100)?; // Show at 100ms
encoder.add_frame_rgba(&frame3_rgba, 200)?; // Show at 200ms
let webp = encoder.finish(300)?; // End timestamp
// Decode animation
let mut decoder = AnimationDecoder::new(&webp)?;
let info = decoder.info();
println!("{} frames, {}x{}", info.frame_count, info.width, info.height);
// Iterate frames
while let Some(frame) = decoder.next_frame()? {
render(&frame.data, frame.timestamp_ms);
}
// Or get all at once
decoder.reset();
let frames = decoder.decode_all()?;
ICC Profiles & Metadata
use webpx::{embed_icc, get_icc_profile, embed_exif, get_exif};
// Embed ICC profile
let webp_with_icc = embed_icc(&webp_data, &srgb_profile)?;
// Extract ICC profile
if let Some(icc) = get_icc_profile(&webp_data)? {
println!("ICC profile: {} bytes", icc.len());
}
// EXIF data
let webp_with_exif = embed_exif(&webp_data, &exif_bytes)?;
if let Some(exif) = get_exif(&webp_data)? {
// Parse EXIF...
}
Streaming Decode
use webpx::{StreamingDecoder, DecodeStatus, ColorMode};
let mut decoder = StreamingDecoder::new(ColorMode::Rgba)?;
// Feed data as it arrives
for chunk in network_stream {
match decoder.append(&chunk)? {
DecodeStatus::Complete => break,
DecodeStatus::NeedMoreData => continue,
DecodeStatus::Partial(rows) => {
// Progressive display
if let Some((data, w, h)) = decoder.get_partial() {
display_partial(data, w, h);
}
}
_ => {} // Handle future variants
}
}
let (pixels, width, height) = decoder.finish()?;
Cooperative Cancellation
Encoding can be cancelled cooperatively using the enough crate:
use webpx::{Encoder, Error, StopReason};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
// Create a cancellation flag
let cancelled = Arc::new(AtomicBool::new(false));
let flag = cancelled.clone();
// Custom Stop implementation
struct MyCanceller(Arc<AtomicBool>);
impl enough::Stop for MyCanceller {
fn check(&self) -> Result<(), enough::StopReason> {
if self.0.load(Ordering::Relaxed) {
Err(enough::StopReason::Cancelled)
} else {
Ok(())
}
}
}
// In another thread: flag.store(true, Ordering::Relaxed);
match Encoder::new_rgba(&data, width, height)
.quality(85.0)
.encode(MyCanceller(cancelled))
{
Ok(webp) => { /* success */ },
Err(Error::Stopped(StopReason::Cancelled)) => { /* cancelled */ },
Err(e) => { /* other error */ },
}
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
webpx = { version = "0.1", features = ["animation", "icc", "streaming"] }
# no_std
webpx = { version = "0.1", default-features = false, features = ["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 webpx::{Encoder, Preset, Unstoppable};
let webp = Encoder::new_rgba(&data, w, h)
.preset(Preset::Photo)
.encode(Unstoppable)?;
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* |
*libwebp requires C compilation. For pure-Rust WASM, see image-webp (lossless only).
Building for WebAssembly
# Install emscripten
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk && ./emsdk install latest && ./emsdk activate latest
# Add target and build
rustup target add wasm32-unknown-emscripten
source ~/emsdk/emsdk_env.sh
cargo build --target wasm32-unknown-emscripten --release
Migration from Other Crates
From webp crate
// Before
use webp::{Encoder, Decoder};
// After - use compat shim
use webpx::compat::webp::{Encoder, Decoder};
// API is compatible, just change the import
From webp-animation crate
// Before
use webp_animation::{Encoder, Decoder};
// After - use compat shim
use webpx::compat::webp_animation::{Encoder, Decoder};
// 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.
Dependencies
~3MB
~64K SLoC