1use std::io::Cursor;
15
16use image::{AnimationDecoder, ImageDecoder, ImageFormat};
17
18#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct Pixels {
22 pub width: u32,
23 pub height: u32,
24 pub rgba: Vec<u8>,
25}
26
27#[derive(Clone, Debug, PartialEq)]
31pub struct Animation {
32 pub width: u32,
33 pub height: u32,
34 pub frames: Vec<AnimationFrame>,
35 pub loops: Option<u32>,
38}
39
40#[derive(Clone, Debug, PartialEq)]
43pub struct AnimationFrame {
44 pub rgba: Vec<u8>,
45 pub delay: f64,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq)]
50pub struct Showing {
51 pub index: usize,
53 pub next: f64,
56}
57
58const GIF_FLOOR: f64 = 0.011;
61const GIF_SLOW: f64 = 0.1;
62
63fn format_of(bytes: &[u8]) -> Result<ImageFormat, String> {
64 let format = image::guess_format(bytes)
65 .map_err(|_| "not an image kui decodes: PNG, JPEG, WebP or GIF".to_string())?;
66 match format {
67 ImageFormat::Png | ImageFormat::Jpeg | ImageFormat::WebP | ImageFormat::Gif => Ok(format),
68 other => Err(format!(
69 "{other:?} is not a format kui decodes: PNG, JPEG, WebP or GIF"
70 )),
71 }
72}
73
74pub fn decode_image(bytes: &[u8]) -> Result<Pixels, String> {
78 let format = format_of(bytes)?;
79 let (w, h) = image::ImageReader::with_format(Cursor::new(bytes), format)
84 .into_dimensions()
85 .map_err(|e| format!("decoding a {format:?}: {e}"))?;
86 if frame_bytes(w, h) > MAX_FRAME_BYTES {
87 return Err(too_big(format, w, h));
88 }
89 let img = image::load_from_memory_with_format(bytes, format)
90 .map_err(|e| format!("decoding a {format:?}: {e}"))?
91 .into_rgba8();
92 Ok(Pixels {
93 width: img.width(),
94 height: img.height(),
95 rgba: img.into_raw(),
96 })
97}
98
99pub fn decode_animation(bytes: &[u8]) -> Result<Animation, String> {
110 let format = format_of(bytes)?;
111 let err = |e: image::ImageError| format!("decoding a {format:?}: {e}");
112 let (width, height, loops, frames) = match format {
113 ImageFormat::Gif => {
114 let mut d = image::codecs::gif::GifDecoder::new(Cursor::new(bytes)).map_err(err)?;
115 d.set_limits(image::Limits::default()).map_err(err)?;
116 let (w, h) = d.dimensions();
117 let loops = loops_of(d.loop_count());
118 (w, h, loops, d.into_frames())
119 }
120 ImageFormat::Png => {
121 let d = image::codecs::png::PngDecoder::with_limits(
122 Cursor::new(bytes),
123 image::Limits::default(),
124 )
125 .map_err(err)?;
126 if !d.is_apng().map_err(err)? {
127 return still(bytes);
128 }
129 let (w, h) = d.dimensions();
130 let a = d.apng().map_err(err)?;
131 let loops = loops_of(a.loop_count());
132 (w, h, loops, a.into_frames())
133 }
134 ImageFormat::WebP => {
135 let d = image::codecs::webp::WebPDecoder::new(Cursor::new(bytes)).map_err(err)?;
136 if !d.has_animation() {
137 return still(bytes);
138 }
139 let (w, h) = d.dimensions();
140 if frame_bytes(w, h) > MAX_FRAME_BYTES {
142 return Err(too_big(format, w, h));
143 }
144 let loops = loops_of(d.loop_count());
145 (w, h, loops, d.into_frames())
146 }
147 _ => return still(bytes),
148 };
149 if frame_bytes(width, height) > MAX_FRAME_BYTES {
150 return Err(too_big(format, width, height));
151 }
152 let gif = format == ImageFormat::Gif;
153 let mut out: Vec<AnimationFrame> = Vec::new();
154 for f in frames {
155 let f = f.map_err(err)?;
156 if (out.len() as u64 + 1) * frame_bytes(width, height) > MAX_ANIMATION_BYTES {
157 return Err(format!(
158 "a {format:?} of {width}x{height} with more frames than {} MiB holds",
159 MAX_ANIMATION_BYTES >> 20
160 ));
161 }
162 let (num, den) = f.delay().numer_denom_ms();
163 let mut delay = f64::from(num) / f64::from(den.max(1)) / 1000.0;
164 if gif && delay < GIF_FLOOR {
165 delay = GIF_SLOW;
166 }
167 let (left, top) = (f.left(), f.top());
168 let buf = f.into_buffer();
169 let rgba = if (buf.width(), buf.height(), left, top) == (width, height, 0, 0) {
170 buf.into_raw()
171 } else {
172 let mut canvas = out
175 .last()
176 .map(|p| p.rgba.clone())
177 .unwrap_or_else(|| vec![0; width as usize * height as usize * 4]);
178 blit(&mut canvas, width, height, &buf, left, top);
179 canvas
180 };
181 out.push(AnimationFrame { rgba, delay });
182 }
183 if out.is_empty() {
184 return Err(format!("a {format:?} with no frames"));
185 }
186 Ok(Animation {
187 width,
188 height,
189 frames: out,
190 loops,
191 })
192}
193
194pub const MAX_ANIMATION_BYTES: u64 = 1 << 30;
198
199const MAX_FRAME_BYTES: u64 = 512 << 20;
202
203fn frame_bytes(w: u32, h: u32) -> u64 {
204 u64::from(w) * u64::from(h) * 4
205}
206
207fn too_big(format: ImageFormat, w: u32, h: u32) -> String {
208 format!(
209 "a {format:?} of {w}x{h} is more than one frame may hold ({} MiB)",
210 MAX_FRAME_BYTES >> 20
211 )
212}
213
214fn still(bytes: &[u8]) -> Result<Animation, String> {
215 let p = decode_image(bytes)?;
216 Ok(Animation {
217 width: p.width,
218 height: p.height,
219 frames: vec![AnimationFrame {
220 rgba: p.rgba,
221 delay: f64::INFINITY,
222 }],
223 loops: Some(1),
224 })
225}
226
227fn loops_of(count: image::metadata::LoopCount) -> Option<u32> {
228 match count {
229 image::metadata::LoopCount::Infinite => None,
230 image::metadata::LoopCount::Finite(n) => Some(n.get()),
231 }
232}
233
234fn blit(canvas: &mut [u8], w: u32, h: u32, src: &image::RgbaImage, left: u32, top: u32) {
235 for (y, row) in src.rows().enumerate() {
236 let cy = top as usize + y;
237 if cy >= h as usize {
238 break;
239 }
240 for (x, px) in row.enumerate() {
241 let cx = left as usize + x;
242 if cx >= w as usize {
243 break;
244 }
245 if px.0[3] != 0 {
246 let at = (cy * w as usize + cx) * 4;
247 canvas[at..at + 4].copy_from_slice(&px.0);
248 }
249 }
250 }
251}
252
253impl Animation {
254 pub fn duration(&self) -> f64 {
257 self.frames.iter().map(|f| f.delay).sum()
258 }
259
260 pub fn at(&self, elapsed: f64) -> Showing {
267 let last = self.frames.len().saturating_sub(1);
268 let total = self.duration();
269 if self.frames.len() < 2 || !total.is_finite() || total <= 0.0 {
270 return Showing {
271 index: 0,
272 next: f64::INFINITY,
273 };
274 }
275 let elapsed = elapsed.max(0.0);
276 let pass = (elapsed / total).floor();
277 if let Some(n) = self.loops
278 && pass >= f64::from(n)
279 {
280 return Showing {
281 index: last,
282 next: f64::INFINITY,
283 };
284 }
285 let mut t = pass * total;
286 for (i, f) in self.frames.iter().enumerate() {
287 t += f.delay;
288 if elapsed < t {
289 return Showing { index: i, next: t };
290 }
291 }
292 Showing {
293 index: last,
294 next: (pass + 1.0) * total + self.frames[0].delay,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use gif::Repeat;
303 use image::{Rgba, RgbaImage};
304
305 fn gif(repeat: gif::Repeat) -> Vec<u8> {
308 let mut out = Vec::new();
309 {
310 let mut enc = gif::Encoder::new(&mut out, 4, 2, &[]).unwrap();
311 enc.set_repeat(repeat).unwrap();
312 let mut frame = |w: u16, h: u16, left: u16, top: u16, c: [u8; 4], cs: u16| {
313 let mut px: Vec<u8> = c.repeat(usize::from(w * h));
314 let mut f = gif::Frame::from_rgba(w, h, &mut px);
315 (f.left, f.top, f.delay) = (left, top, cs);
316 enc.write_frame(&f).unwrap();
317 };
318 frame(4, 2, 0, 0, [255, 0, 0, 255], 5);
319 frame(4, 2, 0, 0, [0, 255, 0, 255], 10);
320 frame(2, 1, 1, 1, [0, 0, 255, 255], 0);
321 }
322 out
323 }
324
325 fn png() -> Vec<u8> {
326 let img = RgbaImage::from_fn(3, 2, |x, y| Rgba([x as u8 * 80, y as u8 * 100, 7, 200]));
327 let mut out = Cursor::new(Vec::new());
328 img.write_to(&mut out, ImageFormat::Png).unwrap();
329 out.into_inner()
330 }
331
332 #[test]
336 fn a_canvas_past_the_ceiling_is_refused() {
337 let gif = b"GIF89a\xcc\xcc\xcc\x43\x3c\x00\x3c\x00\x3c";
338 assert!(decode_animation(gif).is_err());
339 assert!(decode_image(gif).is_err());
340 assert_eq!(frame_bytes(52428, 17356), 3_639_761_472);
341 assert!(frame_bytes(52428, 17356) > MAX_FRAME_BYTES);
342 }
343
344 #[test]
345 fn a_png_decodes_to_straight_rgba() {
346 let p = decode_image(&png()).unwrap();
347 assert_eq!((p.width, p.height), (3, 2));
348 assert_eq!(p.rgba.len(), 3 * 2 * 4);
349 assert_eq!(&p.rgba[4..8], &[80, 0, 7, 200], "not premultiplied");
350 }
351
352 #[test]
353 fn a_gif_decodes_to_its_first_frame_and_to_every_frame() {
354 let bytes = gif(Repeat::Infinite);
355 let first = decode_image(&bytes).unwrap();
356 assert_eq!((first.width, first.height), (4, 2));
357 assert_eq!(&first.rgba[..4], &[255, 0, 0, 255]);
358 let a = decode_animation(&bytes).unwrap();
359 assert_eq!(a.frames.len(), 3);
360 assert_eq!(a.loops, None);
361 let delays: Vec<f64> = a.frames.iter().map(|f| f.delay).collect();
362 assert_eq!(delays, vec![0.05, 0.1, 0.1], "a 0 ms frame shows for 100");
363 for f in &a.frames {
364 assert_eq!(f.rgba.len(), 4 * 2 * 4, "each frame the whole canvas");
365 }
366 let third = &a.frames[2].rgba;
367 assert_eq!(
368 &third[..4],
369 &[0, 255, 0, 255],
370 "the green kept under the patch"
371 );
372 assert_eq!(&third[(4 + 1) * 4..(4 + 1) * 4 + 4], &[0, 0, 255, 255]);
373 }
374
375 #[test]
376 fn at_walks_the_frames_and_loops() {
377 let a = decode_animation(&gif(Repeat::Infinite)).unwrap();
378 let near = |s: Showing, i: usize, next: f64| s.index == i && (s.next - next).abs() < 1e-9;
379 assert!(near(a.at(0.0), 0, 0.05));
380 assert!(near(a.at(0.07), 1, 0.15));
381 assert!(near(a.at(0.2), 2, 0.25));
382 assert!(near(a.at(0.26), 0, 0.30), "{:?}", a.at(0.26));
383 assert!(near(a.at(-1.0), 0, 0.05), "before the start");
384 }
385
386 #[test]
387 fn a_finite_gif_rests_on_its_last_frame() {
388 let a = decode_animation(&gif(Repeat::Finite(1))).unwrap();
389 assert_eq!(a.loops, Some(1));
390 assert_eq!(a.at(0.1).index, 1);
391 let end = a.at(0.3);
392 assert_eq!(end.index, 2);
393 assert!(end.next.is_infinite());
394 }
395
396 #[test]
397 fn a_still_is_one_frame_for_ever() {
398 let a = decode_animation(&png()).unwrap();
399 assert_eq!(a.frames.len(), 1);
400 let s = a.at(100.0);
401 assert_eq!(s.index, 0);
402 assert!(s.next.is_infinite());
403 }
404
405 #[test]
406 fn what_is_not_an_image_is_named() {
407 let e = decode_image(b"hello, world").unwrap_err();
408 assert!(e.contains("PNG, JPEG, WebP or GIF"), "{e}");
409 let mut cut = png();
410 cut.truncate(40);
411 let e = decode_image(&cut).unwrap_err();
412 assert!(e.contains("decoding a Png"), "{e}");
413 }
414}