oxideav-webp
Pure-Rust WebP image codec and container — RIFF/WEBP simple
(lossy VP8 + lossless VP8L) + extended (VP8X with ALPH, ICCP,
EXIF, XMP ) + animated (ANIM/ANMF) decode, plus single-frame
encode on both the VP8 lossy and VP8L lossless paths. Zero C
dependencies.
VP8 lossy decoding and encoding both go through
oxideav-vp8 (also pure-Rust).
VP8L lossless is a self-contained implementation of Google's Huffman +
LZ77 + colour-cache + four-transform bitstream in this crate.
Part of the oxideav framework but usable standalone.
Installation
[]
= "0.1"
= "0.1"
= "0.1"
= "0.0"
Quick use
.webp files carry one or many frames, so the typical path is: open
the file as a container, pull packets, decode them. Output is always
PixelFormat::Rgba regardless of whether the source chunk was VP8
(lossy YUV → RGB) or VP8L (native RGBA).
use CodecRegistry;
use ContainerRegistry;
use Frame;
let mut codecs = new;
let mut containers = new;
register;
let input: = Boxnew;
let mut dmx = containers.open?;
let stream = &dmx.streams;
let mut dec = new;
loop
# Ok::
For a one-shot decode of an in-memory buffer, skip the registry dance:
let bytes = read?;
let img = decode_webp?;
for frame in &img.frames
# Ok::
Encoder — VP8L (lossless, RGBA in)
use ;
let mut params = video;
params.width = Some;
params.height = Some;
params.pixel_format = Some;
let mut enc = codecs.make_encoder?;
enc.send_frame?;
let pkt = enc.receive_packet?; // complete .webp file
The registered webp_vp8l encoder returns a complete RIFF-wrapped
.webp file. Fully-opaque frames use the simple RIFF/WEBP/VP8L
layout; frames with any transparent pixel switch to the extended
RIFF/WEBP/VP8X + VP8L layout so the VP8X header advertises the
alpha flag (required by any spec-compliant reader).
If you need a bare VP8L bitstream (for embedding in another container,
say), call oxideav_webp::encode_vp8l_argb directly — that entry
point still returns the header-to-data bytes with no RIFF wrapper.
Encoder — VP8 (lossy)
let mut params = video;
params.width = Some;
params.height = Some;
params.pixel_format = Some; // or PixelFormat::Rgba
let mut enc = codecs.make_encoder?;
enc.send_frame?;
let pkt = enc.receive_packet?; // complete .webp file
Two input pixel formats are accepted:
Yuv420P— emits the simpleRIFF/WEBP/VP8layout.Rgba— converts RGB to YUV 4:2:0 (BT.601 limited range) for the VP8 keyframe and compresses the alpha plane into anALPHsidecar chunk. Emits the extendedRIFF/WEBP/VP8X + ALPH + VP8layout with the VP8XALPHAflag set.
Quality control: the VP8 lossy encoder exposes two equivalent factory entry points for picking a target compression level —
encoder_vp8::make_encoder_with_quality(¶ms, quality)— takes a libwebp-stylequality: f32in0.0..=100.0(higher = better quality / larger file; the libwebp default is75.0).encoder_vp8::make_encoder_with_qindex(¶ms, qindex)— takes the underlying VP8 qindex in0..=127(lower = better) for callers that already speak the libvpx scale.
The quality → qindex mapping is the linear inversion
qindex = round((100 - quality) * 1.27), so the API surface lines up
with libwebp but the perceptual behaviour does not: libwebp also
adjusts its quantizer matrices, AC/DC deltas, and segment-level QP
based on quality, none of which we currently do. Round-2 work would
tune the quantizer matrix and segment QPs to track libwebp's
perceptual targets at matching quality values.
Scope
Encoder scope (current):
- VP8L lossless from RGBA, single frame. Emits subtract-green +
colour (G↔R/B decorrelation) + tile-based predictor transforms plus
a tunable colour cache, which puts the ratio in the neighbourhood
of libwebp on smooth/photographic content. The default
encode_vp8l_argbentry point now runs a 32-trial RDO sweep over every combination of the four optional transforms × four colour-cache widths ({off, 6, 8, 10} bits) and keeps the smallest encoded variant — callers that want a fixed configuration can still callencode_vp8l_argb_withdirectly. Still missing vs libwebp: the palette (colour-indexing) transform, meta-Huffman grouping, and a wider predictor-mode pool (the encoder currently picks between modes 0/1/2/11 per tile rather than probing all 14). - VP8 lossy from YUV420P or RGBA, single frame. When given RGBA the
alpha plane is emitted as a VP8L-compressed
ALPHchunk inside the extended (VP8X) container. Default qindex fromoxideav-vp8is used unless the caller selects a specific one viaencoder_vp8::make_encoder_with_qindex(VP8 qindex0..=127, lower = better) or the libwebp-styleencoder_vp8::make_encoder_with_quality(0.0..=100.0, higher = better) — see the encoder section above for the mapping caveat. VP8Xextended header is emitted automatically whenever the output carries anALPHsidecar or optional ICC / EXIF / XMP metadata via theriff::WebpMetadatahelper.- Animated WebP encode via [
build_animated_webp] — emits aVP8X + ANIM + ANMF...ANMFfile from a slice ofAnimFrames with per-frame durations, x/y offsets, blend, and disposal flags. Each ANMF wraps aVP8Llossless sub-chunk (mixed lossy + lossless animations are not yet produced; the decoder accepts both shapes).
Decoder scope:
VP8simple lossy (throughoxideav-vp8),VP8Llossless,VP8Xextended withALPH(raw / filtered / VP8L-compressed alpha plane), andANIM/ANMFanimation with per-frame disposal + blend modes composited onto an internal RGBA canvas.ICCP/EXIF/XMPchunks are recognised and skipped; their payloads are not surfaced on the public API.
Codec / container IDs
- Codec:
"webp_vp8l"(VP8L encoder + standalone VP8L decoder); accepted pixel formatRgba. - Codec:
"webp_vp8"(VP8 lossy encoder path); accepted pixel formatYuv420P. - Container:
"webp", matches.webpby extension +RIFF/WEBPmagic.
Single-image WebPs decode to one VideoFrame; animated WebPs produce
N frames with PTS in milliseconds (the ANMF native unit).
License
MIT — see LICENSE.