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