Skip to main content

webpx/compat/
webp.rs

1//! Compatibility shim for the `webp` crate (0.3.x).
2//!
3//! This module provides an API-compatible interface to ease migration
4//! from the `webp` crate to `webpx`.
5//!
6//! # Migration
7//!
8//! Replace your imports:
9//! ```rust,ignore
10//! // Before
11//! use webp::{Encoder, Decoder, PixelLayout};
12//!
13//! // After
14//! use webpx::compat::webp::{Encoder, Decoder, PixelLayout};
15//! ```
16//!
17//! # Example
18//!
19//! ```rust
20//! use webpx::compat::webp::{Encoder, Decoder, PixelLayout};
21//!
22//! // Encode
23//! let rgba = vec![255u8, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255];
24//! let encoder = Encoder::new(&rgba, PixelLayout::Rgba, 2, 2);
25//! let webp_data = encoder.encode(85.0);
26//!
27//! // Decode
28//! let decoder = Decoder::new(&webp_data);
29//! if let Some(image) = decoder.decode() {
30//!     assert_eq!(image.width(), 2);
31//!     assert_eq!(image.height(), 2);
32//! }
33//! ```
34
35use alloc::vec::Vec;
36use core::ops::Deref;
37
38/// Pixel layout for raw image data.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum PixelLayout {
41    /// RGB (3 bytes per pixel).
42    Rgb,
43    /// RGBA (4 bytes per pixel).
44    Rgba,
45}
46
47impl PixelLayout {
48    /// Bytes per pixel for this layout.
49    pub fn bytes_per_pixel(&self) -> u8 {
50        match self {
51            PixelLayout::Rgb => 3,
52            PixelLayout::Rgba => 4,
53        }
54    }
55}
56
57/// Owned WebP memory buffer.
58///
59/// Provides `Deref<Target = [u8]>` for compatibility with `webp::WebPMemory`.
60#[derive(Debug)]
61pub struct WebPMemory(Vec<u8>);
62
63impl WebPMemory {
64    /// Check if the buffer is empty.
65    pub fn is_empty(&self) -> bool {
66        self.0.is_empty()
67    }
68
69    /// Get the length of the buffer.
70    pub fn len(&self) -> usize {
71        self.0.len()
72    }
73}
74
75impl Deref for WebPMemory {
76    type Target = [u8];
77
78    fn deref(&self) -> &Self::Target {
79        &self.0
80    }
81}
82
83impl AsRef<[u8]> for WebPMemory {
84    fn as_ref(&self) -> &[u8] {
85        &self.0
86    }
87}
88
89/// Decoded WebP image.
90#[derive(Debug)]
91pub struct WebPImage {
92    data: Vec<u8>,
93    layout: PixelLayout,
94    width: u32,
95    height: u32,
96}
97
98impl WebPImage {
99    /// Get the pixel data.
100    pub fn data(&self) -> &[u8] {
101        &self.data
102    }
103
104    /// Get the pixel layout.
105    pub fn layout(&self) -> PixelLayout {
106        self.layout
107    }
108
109    /// Get image width.
110    pub fn width(&self) -> u32 {
111        self.width
112    }
113
114    /// Get image height.
115    pub fn height(&self) -> u32 {
116        self.height
117    }
118}
119
120/// Bitstream features extracted from WebP data.
121#[derive(Debug)]
122pub struct BitstreamFeatures {
123    width: u32,
124    height: u32,
125    has_alpha: bool,
126    has_animation: bool,
127}
128
129impl BitstreamFeatures {
130    /// Extract features from WebP data.
131    pub fn new(data: &[u8]) -> Option<Self> {
132        crate::ImageInfo::from_webp(data).ok().map(|info| Self {
133            width: info.width,
134            height: info.height,
135            has_alpha: info.has_alpha,
136            has_animation: info.has_animation,
137        })
138    }
139
140    /// Get image width.
141    pub fn width(&self) -> u32 {
142        self.width
143    }
144
145    /// Get image height.
146    pub fn height(&self) -> u32 {
147        self.height
148    }
149
150    /// Check if the image has an alpha channel.
151    pub fn has_alpha(&self) -> bool {
152        self.has_alpha
153    }
154
155    /// Check if the image is animated.
156    pub fn has_animation(&self) -> bool {
157        self.has_animation
158    }
159}
160
161/// WebP encoder (compatible with `webp::Encoder`).
162pub struct Encoder<'a> {
163    image: &'a [u8],
164    layout: PixelLayout,
165    width: u32,
166    height: u32,
167}
168
169impl<'a> Encoder<'a> {
170    /// Create a new encoder from raw image data.
171    pub fn new(image: &'a [u8], layout: PixelLayout, width: u32, height: u32) -> Self {
172        Self {
173            image,
174            layout,
175            width,
176            height,
177        }
178    }
179
180    /// Create an encoder from RGB data.
181    pub fn from_rgb(image: &'a [u8], width: u32, height: u32) -> Self {
182        Self::new(image, PixelLayout::Rgb, width, height)
183    }
184
185    /// Create an encoder from RGBA data.
186    pub fn from_rgba(image: &'a [u8], width: u32, height: u32) -> Self {
187        Self::new(image, PixelLayout::Rgba, width, height)
188    }
189
190    /// Encode with the given quality (0-100).
191    pub fn encode(&self, quality: f32) -> WebPMemory {
192        self.encode_simple(false, quality)
193            .unwrap_or_else(|_| WebPMemory(Vec::new()))
194    }
195
196    /// Encode losslessly.
197    pub fn encode_lossless(&self) -> WebPMemory {
198        self.encode_simple(true, 75.0)
199            .unwrap_or_else(|_| WebPMemory(Vec::new()))
200    }
201
202    /// Encode with simple options.
203    pub fn encode_simple(&self, lossless: bool, quality: f32) -> crate::Result<WebPMemory> {
204        use crate::Unstoppable;
205
206        let config = crate::EncoderConfig::new()
207            .quality(quality)
208            .lossless(lossless);
209
210        let data = match self.layout {
211            PixelLayout::Rgba => {
212                config.encode_rgba(self.image, self.width, self.height, Unstoppable)?
213            }
214            PixelLayout::Rgb => {
215                config.encode_rgb(self.image, self.width, self.height, Unstoppable)?
216            }
217        };
218
219        Ok(WebPMemory(data))
220    }
221}
222
223/// WebP decoder (compatible with `webp::Decoder`).
224pub struct Decoder<'a> {
225    data: &'a [u8],
226}
227
228impl<'a> Decoder<'a> {
229    /// Create a new decoder from WebP data.
230    pub fn new(data: &'a [u8]) -> Self {
231        Self { data }
232    }
233
234    /// Decode the WebP data.
235    ///
236    /// Returns `None` if decoding fails or the image is animated.
237    pub fn decode(&self) -> Option<WebPImage> {
238        let features = BitstreamFeatures::new(self.data)?;
239
240        // webp crate doesn't support animation
241        if features.has_animation() {
242            return None;
243        }
244
245        let (data, width, height) = if features.has_alpha() {
246            crate::decode_rgba(self.data).ok()?
247        } else {
248            crate::decode_rgb(self.data).ok()?
249        };
250
251        let layout = if features.has_alpha() {
252            PixelLayout::Rgba
253        } else {
254            PixelLayout::Rgb
255        };
256
257        Some(WebPImage {
258            data,
259            layout,
260            width,
261            height,
262        })
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn test_encode_decode_roundtrip() {
272        let rgba = vec![
273            255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255,
274        ];
275        let encoder = Encoder::from_rgba(&rgba, 2, 2);
276        let webp = encoder.encode_lossless();
277
278        assert!(!webp.is_empty());
279
280        let decoder = Decoder::new(&webp);
281        let image = decoder.decode().expect("decode failed");
282        assert_eq!(image.width(), 2);
283        assert_eq!(image.height(), 2);
284        // Note: The decoded layout depends on whether libwebp reports has_alpha
285        // Lossless with all-opaque alpha may not set the flag
286        assert!(image.layout() == PixelLayout::Rgba || image.layout() == PixelLayout::Rgb);
287    }
288
289    #[test]
290    fn test_bitstream_features() {
291        let rgba = vec![0u8; 4 * 4 * 4];
292        let encoder = Encoder::from_rgba(&rgba, 4, 4);
293        let webp = encoder.encode(85.0);
294
295        let features = BitstreamFeatures::new(&webp).expect("features");
296        assert_eq!(features.width(), 4);
297        assert_eq!(features.height(), 4);
298        assert!(!features.has_animation());
299    }
300}