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