1use std::{cmp::max, error, fmt};
2
3use image::{codecs::png::PngEncoder, EncodableLayout as _, ImageEncoder as _, Pixel, Rgba};
4use webp_animation::Decoder as AwebPDecoder;
6
7#[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 let mut buf = Vec::with_capacity(image.as_bytes().len());
64
65 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#[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}