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}