rlvgl_core/plugins/
gif.rs

1//! GIF decoder returning frames.
2use crate::widget::Color;
3use alloc::vec::Vec;
4pub use gif::DecodingError;
5use gif::{ColorOutput, DecodeOptions, Frame};
6use std::io::Cursor;
7
8#[derive(Debug, Clone)]
9/// A single frame decoded from a GIF image.
10pub struct GifFrame {
11    /// Pixel data for the frame in RGB format.
12    pub pixels: Vec<Color>,
13    /// Delay time for this frame in hundredths of a second.
14    pub delay: u16,
15}
16
17/// Decode a GIF byte stream and return the frames with image dimensions.
18pub fn decode(data: &[u8]) -> Result<(Vec<GifFrame>, u16, u16), DecodingError> {
19    let mut options = DecodeOptions::new();
20    options.set_color_output(ColorOutput::RGBA);
21    let mut reader = options.read_info(Cursor::new(data))?;
22    let width = reader.width();
23    let height = reader.height();
24    let mut frames = Vec::new();
25    while let Some(frame) = reader.read_next_frame()? {
26        frames.push(convert_frame(frame, width, height));
27    }
28    Ok((frames, width, height))
29}
30
31fn convert_frame(frame: &Frame<'_>, width: u16, height: u16) -> GifFrame {
32    let mut pixels = Vec::with_capacity(width as usize * height as usize);
33    for chunk in frame.buffer.chunks_exact(4) {
34        pixels.push(Color(chunk[0], chunk[1], chunk[2], 255));
35    }
36    GifFrame {
37        pixels,
38        delay: frame.delay,
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use base64::Engine;
46
47    const RED_DOT_GIF: &str = "R0lGODdhAQABAPAAAP8AAP///yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
48
49    #[test]
50    fn decode_red_dot() {
51        let data = base64::engine::general_purpose::STANDARD
52            .decode(RED_DOT_GIF)
53            .unwrap();
54        let (frames, w, h) = decode(&data).unwrap();
55        assert_eq!((w, h), (1, 1));
56        assert_eq!(frames.len(), 1);
57        assert_eq!(frames[0].pixels, vec![Color(255, 0, 0, 255)]);
58    }
59}