Skip to main content

zenwebp/
lib.rs

1//! Decoding and Encoding of WebP Images
2//!
3//! Copyright (C) 2025 Imazen LLC
4//!
5//! This program is free software: you can redistribute it and/or modify
6//! it under the terms of the GNU Affero General Public License as published
7//! by the Free Software Foundation, either version 3 of the License, or
8//! (at your option) any later version.
9//!
10//! For commercial licensing inquiries: support@imazen.io
11//!
12//! This crate provides both encoding and decoding of WebP images.
13//!
14//! # Features
15//!
16//! - `std` (default): Enables `encode_to_writer()`. Everything else works without it.
17//! - `fast-yuv` (default): Optimized YUV conversion via the `yuv` crate.
18//! - `pixel-types`: Type-safe pixel formats via the `rgb` crate.
19//!
20//! # no_std Support
21//!
22//! Both encoding and decoding work in `no_std` environments (requires `alloc`):
23//! ```toml
24//! [dependencies]
25//! zenwebp = { version = "...", default-features = false }
26//! ```
27//!
28//! Only [`EncodeRequest::encode_to`] requires `std` (for `std::io::Write`).
29//!
30//! # Encoding
31//!
32//! Use [`LossyConfig`] or [`LosslessConfig`] with [`EncodeRequest`]:
33//!
34//! ```rust
35//! use zenwebp::{EncodeRequest, LossyConfig, PixelLayout};
36//!
37//! let config = LossyConfig::new().with_quality(85.0).with_method(4);
38//! let rgba_data = vec![255u8; 4 * 4 * 4];
39//! let webp = EncodeRequest::lossy(&config, &rgba_data, PixelLayout::Rgba8, 4, 4)
40//!     .encode()?;
41//! # Ok::<(), whereat::At<zenwebp::EncodeError>>(())
42//! ```
43//!
44//! # Decoding
45//!
46//! Use the [`oneshot`] convenience functions:
47//!
48//! ```rust,no_run
49//! let webp_data: &[u8] = &[]; // your WebP data
50//! let (pixels, width, height) = zenwebp::oneshot::decode_rgba(webp_data)?;
51//! # Ok::<(), whereat::At<zenwebp::DecodeError>>(())
52//! ```
53//!
54//! Or [`WebPDecoder`] for two-phase decoding (inspect headers before allocating):
55//!
56//! ```rust,no_run
57//! use zenwebp::WebPDecoder;
58//!
59//! let webp_data: &[u8] = &[]; // your WebP data
60//! let mut decoder = WebPDecoder::build(webp_data)?;
61//! let info = decoder.info();
62//! println!("{}x{}, alpha={}", info.width, info.height, info.has_alpha);
63//!
64//! let mut output = vec![0u8; decoder.output_buffer_size().unwrap()];
65//! decoder.read_image(&mut output)?;
66//! # Ok::<(), zenwebp::DecodeError>(())
67//! ```
68//!
69//! # ICC Color Profiles
70//!
71//! WebP supports embedded ICC profiles via the ICCP chunk (VP8X extended format).
72//! zenwebp preserves ICC profiles through encode and decode but does **not** apply
73//! color management — pixels are returned in whatever color space they were encoded
74//! in. This matches libwebp's behavior.
75//!
76//! **Decoding:** Use [`ImageInfo::icc_profile`] to extract the ICC profile after
77//! probing headers. Pass it to your color management library (e.g., `lcms2`) to
78//! convert pixels to your target color space.
79//!
80//! ```rust,no_run
81//! let webp_data: &[u8] = &[];
82//! let info = zenwebp::ImageInfo::from_webp(webp_data)?;
83//! if let Some(icc) = &info.icc_profile {
84//!     // Pass icc bytes to your CMS for color conversion
85//! }
86//! # Ok::<(), whereat::At<zenwebp::DecodeError>>(())
87//! ```
88//!
89//! **Encoding:** Embed an ICC profile with [`EncodeRequest::with_icc_profile()`]:
90//!
91//! ```rust,no_run
92//! # let icc_bytes: &[u8] = &[];
93//! # let rgba_data = vec![255u8; 4 * 4 * 4];
94//! use zenwebp::{EncodeRequest, LossyConfig, PixelLayout};
95//! let webp = EncodeRequest::lossy(&LossyConfig::new(), &rgba_data, PixelLayout::Rgba8, 4, 4)
96//!     .with_icc_profile(icc_bytes)
97//!     .encode()?;
98//! # Ok::<(), whereat::At<zenwebp::EncodeError>>(())
99//! ```
100//!
101//! **Post-hoc:** The [`metadata`] module can extract, embed, or remove ICC profiles
102//! from already-encoded WebP data without re-encoding pixels.
103//!
104//! # Safety
105//!
106//! This crate uses `#![forbid(unsafe_code)]` to prevent direct unsafe usage in source.
107//! We rely on the [`archmage`] crate for safe SIMD intrinsics. The `#[arcane]` proc
108//! macro generates unsafe blocks internally (which bypass the `forbid` lint due to
109//! proc-macro span handling). The soundness of our SIMD code depends on archmage's
110//! token-based safety model being correct.
111//!
112//! [`archmage`]: https://docs.rs/archmage
113
114#![cfg_attr(not(feature = "std"), no_std)]
115#![forbid(unsafe_code)]
116#![deny(missing_docs)]
117// Clippy style lints — intentional patterns throughout the codebase.
118#![allow(
119    clippy::needless_range_loop,   // explicit `for i in 0..n { arr[i] }` is clearer in codec code
120    clippy::too_many_arguments,    // codec functions pass many buffers/params
121    clippy::manual_div_ceil,       // `(x + y - 1) / y` is the standard idiom
122    clippy::manual_is_multiple_of, // `x % y == 0` is clearer than `.is_multiple_of()`
123    clippy::manual_repeat_n,       // `repeat().take()` vs `repeat_n` — not available on MSRV
124)]
125// Enable nightly benchmark functionality if "_benchmarks" feature is enabled.
126#![cfg_attr(all(test, feature = "_benchmarks"), feature(test))]
127
128extern crate alloc;
129
130whereat::define_at_crate_info!();
131
132#[cfg(all(test, feature = "_benchmarks"))]
133extern crate test;
134
135// Core modules (internal — public API is re-exported at crate root)
136pub(crate) mod common;
137pub mod decoder;
138/// Encoder detection and quality estimation from WebP file headers.
139pub mod detect;
140pub mod encoder;
141mod exif_orientation;
142/// WebP mux/demux and animation encoding.
143pub mod mux;
144
145// Slice reader utility (used by decoder and mux)
146mod slice_reader;
147
148/// Type-safe pixel format traits for decoding and encoding.
149#[cfg(feature = "pixel-types")]
150pub mod pixel;
151
152/// Resource estimation heuristics for encoding and decoding operations.
153pub mod heuristics;
154
155/// One-shot decode convenience functions (`decode_rgba`, `decode_rgb`, etc.).
156pub mod oneshot;
157
158// Re-export core decoder types
159pub use decoder::{
160    DecodeConfig, DecodeError, DecodeRequest, DecodeResult, ImageInfo, Limits, StreamingDecoder,
161    WebPDecoder,
162};
163
164// Re-export Orientation from zenpixels (canonical EXIF orientation for the zen ecosystem)
165pub use zenpixels::Orientation;
166
167// Re-export core encoder types
168pub use encoder::{
169    EncodeError, EncodeRequest, EncodeResult, EncoderConfig, ImageMetadata, LosslessConfig,
170    LossyConfig, PixelLayout, Preset,
171};
172
173// #[cfg(feature = "zennode")]
174// pub mod zennode_defs;
175
176#[cfg(feature = "zencodec")]
177mod codec;
178
179/// zencodec trait implementations for WebP encoding and decoding.
180#[cfg(feature = "zencodec")]
181pub mod zencodec {
182    pub use crate::codec::{
183        WebpAnimationFrameDecoder, WebpAnimationFrameEncoder, WebpDecodeJob, WebpDecoder,
184        WebpDecoderConfig, WebpEncodeJob, WebpEncoder, WebpEncoderConfig, WebpStreamingDecoder,
185    };
186}
187
188/// Standalone metadata convenience functions for already-encoded WebP data.
189///
190/// For embedding metadata during encoding, use
191/// [`EncodeRequest::with_metadata`] instead.
192pub mod metadata;
193
194/// Test-only helpers exposed for integration tests.
195///
196/// Not part of the public API; do not use in production code.
197#[doc(hidden)]
198pub mod test_helpers {
199    /// Scalar RGB->YUV420 conversion (same path the encoder uses).
200    ///
201    /// Returns (y, u, v) planes with macroblock-aligned dimensions.
202    pub fn convert_image_yuv_rgb(
203        image_data: &[u8],
204        width: u16,
205        height: u16,
206        stride: usize,
207    ) -> (
208        alloc::vec::Vec<u8>,
209        alloc::vec::Vec<u8>,
210        alloc::vec::Vec<u8>,
211    ) {
212        crate::decoder::yuv::convert_image_yuv::<3>(image_data, width, height, stride)
213    }
214
215    /// Expose the forward gamma LUT for verification tests.
216    /// sRGB byte -> linear^0.80 (scale 0..4095), 256 entries.
217    pub fn gamma_to_linear_tab() -> &'static [u16; 256] {
218        &crate::decoder::yuv::GAMMA_TO_LINEAR_TAB
219    }
220
221    /// Expose the inverse gamma LUT for verification tests.
222    /// Linear^0.80 -> gamma-space byte, 33 entries.
223    pub fn linear_to_gamma_tab() -> &'static [u8; 33] {
224        &crate::decoder::yuv::LINEAR_TO_GAMMA_TAB
225    }
226}