1use crate::error::{Error, Result};
2use crate::format::Format;
3
4use super::{Codec, EncodeOptions, ImageData};
5
6pub struct WebPCodec;
8
9impl Codec for WebPCodec {
10 fn format(&self) -> Format {
11 Format::WebP
12 }
13
14 fn decode(&self, data: &[u8]) -> Result<ImageData> {
15 let img = image::load_from_memory_with_format(data, image::ImageFormat::WebP)
16 .map_err(|e| Error::Decode(format!("webp decode: {e}")))?;
17
18 let rgba = img.to_rgba8();
19 let width = rgba.width();
20 let height = rgba.height();
21
22 Ok(ImageData::new(width, height, rgba.into_raw()))
23 }
24
25 fn encode(&self, image: &ImageData, options: &EncodeOptions) -> Result<Vec<u8>> {
26 let encoder = webp::Encoder::from_rgba(&image.data, image.width, image.height);
27 let encoded = encoder.encode(options.quality as f32);
28
29 Ok(encoded.to_vec())
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 fn create_test_image(width: u32, height: u32) -> ImageData {
38 let size = (width * height * 4) as usize;
39 let mut data = vec![0u8; size];
40 for y in 0..height {
41 for x in 0..width {
42 let i = ((y * width + x) * 4) as usize;
43 data[i] = (x * 255 / width) as u8; data[i + 1] = (y * 255 / height) as u8; data[i + 2] = 128; data[i + 3] = 255; }
48 }
49 ImageData::new(width, height, data)
50 }
51
52 #[test]
53 fn encode_and_decode_roundtrip() {
54 let codec = WebPCodec;
55 let original = create_test_image(64, 48);
56 let options = EncodeOptions { quality: 90 };
57
58 let encoded = codec.encode(&original, &options).expect("encode failed");
59
60 let decoded = codec.decode(&encoded).expect("decode failed");
62 assert_eq!(decoded.width, original.width);
63 assert_eq!(decoded.height, original.height);
64 assert_eq!(
65 decoded.data.len(),
66 (decoded.width * decoded.height * 4) as usize
67 );
68 }
69
70 #[test]
71 fn lower_quality_produces_smaller_file() {
72 let codec = WebPCodec;
73 let image = create_test_image(128, 96);
74
75 let high = codec
76 .encode(&image, &EncodeOptions { quality: 95 })
77 .expect("encode q95 failed");
78 let low = codec
79 .encode(&image, &EncodeOptions { quality: 20 })
80 .expect("encode q20 failed");
81
82 assert!(
83 low.len() < high.len(),
84 "low quality ({} bytes) should be smaller than high quality ({} bytes)",
85 low.len(),
86 high.len(),
87 );
88 }
89}