quad_gif/
lib.rs

1//! Simple crate to load and draw a GIF animation using Macroquad.
2//!
3//! The animation will loop forever, regardless of how many iterations are set in
4//! the file.
5//!
6//! ```rust
7//! use macroquad::prelude::*;
8//! use quad_gif;
9//!
10//! #[macroquad::main("quad-gif")]
11//! async fn main() {
12//!     let mut animation = quad_gif::GifAnimation::load("animation.gif".to_string()).await;
13//!
14//!     clear_background(WHITE);
15//!     loop {
16//!         animation.draw();
17//!         animation.tick();
18//!         next_frame().await
19//!     }
20//! }
21//! ```
22
23use macroquad::prelude::*;
24use rgb::ComponentBytes;
25
26/// Struct containing textures to display every frame.
27pub struct GifAnimation {
28    pub frames: Vec<AnimationFrame>,
29    pub width: u16,
30    pub height: u16,
31    pub current_frame: usize,
32    elapsed_time: f32,
33    paused: bool,
34}
35
36impl GifAnimation {
37    /// Instantiate with a vector of [`AnimationFrame`], width and height.
38    ///
39    /// Can be used to create an animation from your own textures instead
40    /// of loading a GIF file.
41    ///
42    /// [`AnimationFrame`]: struct.AnimationFrame
43    pub fn new(frames: Vec<AnimationFrame>, width: u16, height: u16) -> Self {
44        Self {
45            frames,
46            width,
47            height,
48            current_frame: 0,
49            elapsed_time: 0.,
50            paused: false,
51        }
52    }
53
54    /// Load and decode a GIF file using Macroquad.
55    ///
56    /// ```rust
57    /// let mut gif_animation = GifAnimation::load("filename.gif").await;
58    /// ```
59    pub async fn load(filename: String) -> Self {
60        let file_bytes = load_file(&filename).await.expect("Couldn't load file");
61        Self::from_gif_bytes(&file_bytes)
62    }
63
64    /// Instantiate a new `GifAnimation` from bytes.
65    ///
66    /// ```rust
67    /// let bytes: [u8] = ...
68    /// let mut gif_animation = GifAnimation::from_gif_bytes(&bytes);
69    /// ```
70    pub fn from_gif_bytes(file_bytes: &[u8]) -> GifAnimation {
71        let (frames, width, height) = Self::decode_gif(&file_bytes);
72        GifAnimation::new(frames, width, height)
73    }
74
75    fn decode_gif(file: &[u8]) -> (Vec<AnimationFrame>, u16, u16) {
76        let mut options = gif::DecodeOptions::new();
77        options.set_color_output(gif::ColorOutput::Indexed);
78        let mut decoder = options.read_info(&*file).unwrap();
79        let mut screen = gif_dispose::Screen::new_decoder(&decoder);
80
81        let mut frames: Vec<AnimationFrame> = Vec::new();
82        while let Some(frame) = decoder.read_next_frame().unwrap() {
83            screen.blit_frame(&frame).expect("Couldn't blit frame");
84            let (pixels, frame_width, frame_height) = screen.pixels.as_contiguous_buf();
85            frames.push(AnimationFrame {
86                texture: Texture2D::from_rgba8(
87                    frame_width as u16,
88                    frame_height as u16,
89                    pixels.as_bytes(),
90                ),
91                delay: frame.delay as f32 / 100.,
92            });
93        }
94        (frames, decoder.width(), decoder.height())
95    }
96
97    fn pos_x(&self) -> f32 {
98        screen_width() / 2. - self.width as f32 / 2.
99    }
100
101    fn pos_y(&self) -> f32 {
102        screen_height() / 2. - self.height as f32 / 2.
103    }
104
105    /// Draw the texture of the current frame at the middle of the screen.
106    ///
107    /// ```rust
108    /// gif_animation.draw();
109    /// ```
110    pub fn draw(&self) {
111        self.draw_at(self.pos_x(), self.pos_y());
112    }
113
114    /// Draw the texture of the current frame at given X/Y position.
115    ///
116    /// ```rust
117    /// gif_animation.draw_at(42.0, 47.0);
118    /// ```
119    pub fn draw_at(&self, pos_x: f32, pos_y: f32) {
120        draw_texture_ex(
121            &self.frame().texture,
122            pos_x,
123            pos_y,
124            WHITE,
125            DrawTextureParams::default(),
126        );
127    }
128
129    /// Update method that needs to be called in the loop to
130    /// advance to next frame when necessary.
131    ///
132    /// ```rust
133    /// gif_animation.tick();
134    /// ```
135    pub fn tick(&mut self) {
136        if !self.paused {
137            self.elapsed_time += get_frame_time();
138            if self.elapsed_time > self.frame().delay {
139                self.advance_frame();
140            }
141        }
142    }
143
144    /// Toggle whether the animation should be playing or be paused.
145    ///
146    /// ```rust
147    /// gif_animation.toggle_paused();
148    /// ```
149    pub fn toggle_paused(&mut self) {
150        self.paused ^= true;
151    }
152
153    pub fn frame(&self) -> &AnimationFrame {
154        self.frames.get(self.current_frame).unwrap()
155    }
156
157    fn advance_frame(&mut self) {
158        self.current_frame = if self.current_frame == self.frames.len() - 1 {
159            0
160        } else {
161            self.current_frame + 1
162        };
163        self.elapsed_time = 0.0;
164    }
165}
166
167/// Struct for a single frame. Contains the texture to draw,
168/// and a delay for how many seconds the frame should show before
169/// advancing to the next frame.
170#[derive(Debug)]
171pub struct AnimationFrame {
172    pub texture: Texture2D,
173    pub delay: f32,
174}