Skip to main content

webp_rust/
lib.rs

1//! Pure Rust WebP decode and still-image encode helpers.
2//!
3//! The top-level API is intentionally small:
4//! - [`decode`] decodes a still WebP image into [`ImageBuffer`]
5//! - [`encode`] encodes an [`ImageBuffer`] as lossy or lossless WebP
6//! - [`encode_lossy`] encodes an [`ImageBuffer`] as lossy WebP
7//! - [`encode_lossless`] encodes an [`ImageBuffer`] as lossless WebP
8//!
9//! Lower-level codec and container entry points remain available under
10//! [`decoder`] and [`encoder`].
11
12use std::path::Path;
13
14pub mod compat;
15pub mod decoder;
16pub mod encoder;
17mod image;
18#[doc(hidden)]
19pub mod legacy;
20
21pub use compat::{
22    CallbackResponse, DataMap, DecodeOptions, DrawCallback, DrawOptions, ImageRect, InitOptions,
23    Metadata, NextBlend, NextDispose, NextOption, NextOptions, ResponseCommand, RGBA,
24};
25pub use decoder::DecoderError;
26pub use encoder::{EncoderError, LosslessEncodingOptions, LossyEncodingOptions};
27pub use image::ImageBuffer;
28pub use legacy::{read_header, read_u24, AnimationControl, AnimationFrame, WebpHeader};
29
30/// Top-level still-image WebP compression mode.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum WebpEncoding {
33    /// Encode as lossless `VP8L`.
34    Lossless,
35    /// Encode as lossy `VP8`.
36    Lossy,
37}
38
39/// Decodes a still WebP image from memory into an RGBA buffer.
40///
41/// Animated WebP is rejected by this helper. Use
42/// [`decoder::decode_animation_webp`] for animated input.
43pub fn decode(data: &[u8]) -> Result<ImageBuffer, DecoderError> {
44    let features = decoder::get_features(data)?;
45    if features.has_animation {
46        return Err(DecoderError::Unsupported(
47            "animated WebP requires animation decoder API",
48        ));
49    }
50
51    let image = match features.format {
52        decoder::WebpFormat::Lossy => decoder::decode_lossy_webp_to_rgba(data)?,
53        decoder::WebpFormat::Lossless => decoder::decode_lossless_webp_to_rgba(data)?,
54        decoder::WebpFormat::Undefined => {
55            return Err(DecoderError::Unsupported("unsupported WebP format"))
56        }
57    };
58
59    Ok(ImageBuffer {
60        width: image.width,
61        height: image.height,
62        rgba: image.rgba,
63    })
64}
65
66fn to_lossless_options(optimize: usize) -> Result<LosslessEncodingOptions, EncoderError> {
67    let optimization_level = u8::try_from(optimize)
68        .map_err(|_| EncoderError::InvalidParam("lossless optimization level must be in 0..=9"))?;
69    Ok(LosslessEncodingOptions { optimization_level })
70}
71
72fn to_lossy_options(optimize: usize, quality: usize) -> Result<LossyEncodingOptions, EncoderError> {
73    let optimization_level = u8::try_from(optimize)
74        .map_err(|_| EncoderError::InvalidParam("lossy optimization level must be in 0..=9"))?;
75    let quality = u8::try_from(quality)
76        .map_err(|_| EncoderError::InvalidParam("quality must be in 0..=100"))?;
77    Ok(LossyEncodingOptions {
78        quality,
79        optimization_level,
80    })
81}
82
83/// Encodes an image as a still WebP container.
84///
85/// `optimize` is interpreted as `0..=9` for [`WebpEncoding::Lossless`] and
86/// `0..=9` for [`WebpEncoding::Lossy`]. `quality` is used only for lossy
87/// encoding and must be in `0..=100`.
88///
89/// If `exif` is present it is embedded as a raw `EXIF` chunk.
90pub fn encode(
91    image: &ImageBuffer,
92    optimize: usize,
93    quality: usize,
94    compression: WebpEncoding,
95    exif: Option<&[u8]>,
96) -> Result<Vec<u8>, EncoderError> {
97    match compression {
98        WebpEncoding::Lossless => encode_lossless(image, optimize, exif),
99        WebpEncoding::Lossy => encode_lossy(image, optimize, quality, exif),
100    }
101}
102
103/// Encodes an image as a still lossy WebP container.
104///
105/// `optimize` must be in `0..=9`. `quality` must be in `0..=100`.
106///
107/// If `exif` is present it is embedded as a raw `EXIF` chunk.
108pub fn encode_lossy(
109    image: &ImageBuffer,
110    optimize: usize,
111    quality: usize,
112    exif: Option<&[u8]>,
113) -> Result<Vec<u8>, EncoderError> {
114    let options = to_lossy_options(optimize, quality)?;
115    encoder::encode_lossy_image_to_webp_with_options_and_exif(image, &options, exif)
116}
117
118/// Encodes an image as a still lossless WebP container.
119///
120/// `optimize` must be in `0..=9`.
121///
122/// If `exif` is present it is embedded as a raw `EXIF` chunk.
123pub fn encode_lossless(
124    image: &ImageBuffer,
125    optimize: usize,
126    exif: Option<&[u8]>,
127) -> Result<Vec<u8>, EncoderError> {
128    let options = to_lossless_options(optimize)?;
129    encoder::encode_lossless_image_to_webp_with_options_and_exif(image, &options, exif)
130}
131
132/// Compatibility alias for [`decode`].
133pub fn image_from_bytes(data: &[u8]) -> Result<ImageBuffer, DecoderError> {
134    decode(data)
135}
136
137/// Reads a still WebP image from disk and decodes it to RGBA.
138#[cfg(not(target_family = "wasm"))]
139pub fn decode_file<P: AsRef<Path>>(filename: P) -> Result<ImageBuffer, Box<dyn std::error::Error>> {
140    let data = std::fs::read(filename)?;
141    Ok(decode(&data)?)
142}
143
144/// Compatibility alias for [`decode_file`].
145#[cfg(not(target_family = "wasm"))]
146pub fn image_from_file<P: AsRef<Path>>(
147    filename: P,
148) -> Result<ImageBuffer, Box<dyn std::error::Error>> {
149    decode_file(filename)
150}