dwebp/
lib.rs

1use std::{cmp::max, error, fmt};
2
3use image::{codecs::png::PngEncoder, EncodableLayout as _, ImageEncoder as _, Pixel, Rgba};
4// use image::{DynamicImage, ImageOutputFormat};
5use webp_animation::Decoder as AwebPDecoder;
6
7//
8//
9//
10#[derive(Debug, Copy, Clone, PartialEq)]
11pub enum AwebpFramePosition {
12    First,
13    Specific(usize),
14    Last,
15}
16impl Default for AwebpFramePosition {
17    fn default() -> Self {
18        Self::First
19    }
20}
21
22pub fn awebp_to_single_png(
23    awebp_bytes: impl AsRef<[u8]>,
24    frame_position: impl Into<Option<AwebpFramePosition>>,
25) -> Result<Vec<u8>, AwebpToPngError> {
26    let frame_position: AwebpFramePosition = frame_position.into().unwrap_or_default();
27
28    let awebp_decoder =
29        AwebPDecoder::new(awebp_bytes.as_ref()).map_err(|_| AwebpToPngError::DecodeAwebpFailed)?;
30
31    let awebp_decoder_iter = awebp_decoder.into_iter();
32
33    let webp_frame = match frame_position {
34        AwebpFramePosition::First => {
35            awebp_decoder_iter
36                .enumerate()
37                .find(|(i, _)| *i == 0)
38                .ok_or(AwebpToPngError::AwebpSpecificFrameIsNone)?
39                .1
40        }
41        AwebpFramePosition::Specific(n) => {
42            let n = max(1, n);
43
44            awebp_decoder_iter
45                .enumerate()
46                .find(|(i, _)| *i == n - 1)
47                .ok_or(AwebpToPngError::AwebpSpecificFrameIsNone)?
48                .1
49        }
50        AwebpFramePosition::Last => awebp_decoder_iter
51            .last()
52            .ok_or(AwebpToPngError::AwebpSpecificFrameIsNone)?,
53    };
54
55    let image = webp_frame
56        .into_image()
57        .map_err(|_| AwebpToPngError::ToImageFailed)?;
58
59    // https://github.com/image-rs/image/blob/v0.23.14/src/buffer.rs#L926
60    // https://github.com/image-rs/image/blob/v0.23.14/src/dynimage.rs#L1280
61    // https://github.com/image-rs/image/blob/v0.23.14/src/io/free_functions.rs#L174
62
63    let mut buf = Vec::with_capacity(image.as_bytes().len());
64
65    // DynamicImage::ImageRgba8(image)
66    //     .write_to(&mut buf, ImageOutputFormat::Png)
67    //     .map_err(|_| AwebpToPngError::EncodePngFailed)?;
68
69    PngEncoder::new(&mut buf)
70        .write_image(
71            image.as_bytes(),
72            image.width(),
73            image.height(),
74            Rgba::<u8>::COLOR_TYPE,
75        )
76        .map_err(|_| AwebpToPngError::EncodePngFailed)?;
77
78    Ok(buf)
79}
80
81pub fn awebp_to_multi_png(awebp_bytes: impl AsRef<[u8]>) -> Result<Vec<Vec<u8>>, AwebpToPngError> {
82    let awebp_decoder =
83        AwebPDecoder::new(awebp_bytes.as_ref()).map_err(|_| AwebpToPngError::DecodeAwebpFailed)?;
84
85    let awebp_decoder_iter = awebp_decoder.into_iter();
86
87    awebp_decoder_iter
88        .map(|webp_frame| {
89            let image = webp_frame
90                .into_image()
91                .map_err(|_| AwebpToPngError::ToImageFailed)?;
92
93            let mut buf = Vec::with_capacity(image.as_bytes().len());
94
95            PngEncoder::new(&mut buf)
96                .write_image(
97                    image.as_bytes(),
98                    image.width(),
99                    image.height(),
100                    Rgba::<u8>::COLOR_TYPE,
101                )
102                .map_err(|_| AwebpToPngError::EncodePngFailed)?;
103
104            Ok(buf)
105        })
106        .collect::<Result<_, _>>()
107}
108
109//
110//
111//
112#[derive(Debug)]
113pub enum AwebpToPngError {
114    DecodeAwebpFailed,
115    AwebpSpecificFrameIsNone,
116    ToImageFailed,
117    EncodePngFailed,
118}
119impl fmt::Display for AwebpToPngError {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        write!(f, "{:?}", self)
122    }
123}
124impl error::Error for AwebpToPngError {}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    use std::{
131        fs::{self, File},
132        io::Write as _,
133    };
134
135    use tempfile::{tempdir, Builder};
136
137    #[test]
138    fn test_awebp_to_single_png_with_animated() {
139        let awebp_bytes = include_bytes!("../tests/images/animated-webp-supported.webp");
140        let png_bytes = awebp_to_single_png(awebp_bytes, AwebpFramePosition::Last).unwrap();
141
142        let png_decoder = png::Decoder::new(&png_bytes[..]);
143        png_decoder.read_info().unwrap();
144
145        let tmp_dir = tempdir().unwrap();
146
147        let mut file = File::create(tmp_dir.path().join("animated-webp-supported.png")).unwrap();
148        file.write_all(&png_bytes[..]).unwrap();
149        file.sync_all().unwrap();
150    }
151
152    #[test]
153    fn test_awebp_to_multi_png_with_animated() {
154        let awebp_bytes = include_bytes!("../tests/images/animated-webp-supported.webp");
155        let png_bytes_list = awebp_to_multi_png(awebp_bytes).unwrap();
156
157        let tmp_dir = Builder::new()
158            .prefix("animated-webp-supported")
159            .tempdir()
160            .unwrap();
161
162        for (i, png_bytes) in png_bytes_list.into_iter().enumerate() {
163            let png_decoder = png::Decoder::new(&png_bytes[..]);
164            png_decoder.read_info().unwrap();
165
166            let mut file = File::create(tmp_dir.path().join(format!("{}.png", i))).unwrap();
167            file.write_all(&png_bytes[..]).unwrap();
168            file.sync_all().unwrap();
169        }
170
171        println!(
172            "{:?}",
173            fs::read_dir(tmp_dir.path()).unwrap().collect::<Vec<_>>()
174        );
175    }
176
177    #[test]
178    fn test_awebp_to_single_png_with_not_animated() {
179        let awebp_bytes = include_bytes!("../tests/images/3_webp_ll.webp");
180        let png_bytes = awebp_to_single_png(awebp_bytes, None).unwrap();
181
182        let png_decoder = png::Decoder::new(&png_bytes[..]);
183        png_decoder.read_info().unwrap();
184
185        let tmp_dir = tempdir().unwrap();
186
187        let mut file = File::create(tmp_dir.path().join("3_webp_ll.png")).unwrap();
188        file.write_all(&png_bytes[..]).unwrap();
189        file.sync_all().unwrap();
190    }
191
192    #[test]
193    fn test_awebp_to_multi_png_with_not_animated() {
194        let awebp_bytes = include_bytes!("../tests/images/3_webp_ll.webp");
195        let png_bytes_list = awebp_to_multi_png(awebp_bytes).unwrap();
196
197        let tmp_dir = Builder::new().prefix("3_webp_ll").tempdir().unwrap();
198
199        for (i, png_bytes) in png_bytes_list.into_iter().enumerate() {
200            let png_decoder = png::Decoder::new(&png_bytes[..]);
201            png_decoder.read_info().unwrap();
202
203            let mut file = File::create(tmp_dir.path().join(format!("{}.png", i))).unwrap();
204            file.write_all(&png_bytes[..]).unwrap();
205            file.sync_all().unwrap();
206        }
207
208        println!(
209            "{:?}",
210            fs::read_dir(tmp_dir.path()).unwrap().collect::<Vec<_>>()
211        );
212    }
213}