Skip to main content

geng_utils/
gif.rs

1use geng::prelude::*;
2
3/// A single GIF frame that has a texture and a duration.
4pub struct GifFrame {
5    pub texture: ugli::Texture,
6    /// Duration of the frame in seconds.
7    pub duration: f32,
8}
9
10/// GIF load options.
11#[derive(Debug, Clone, Default)]
12pub struct GifOptions {
13    pub frame: geng::asset::TextureOptions,
14}
15
16/// Load GIF frame from the given file path.
17pub async fn load_gif(
18    manager: &geng::asset::Manager,
19    path: impl AsRef<std::path::Path>,
20    options: GifOptions,
21) -> anyhow::Result<Vec<GifFrame>> {
22    use image::AnimationDecoder;
23
24    let path = path.as_ref();
25    log::debug!("Loading gif at {:?}", path);
26
27    let data = <Vec<u8> as geng::asset::Load>::load(manager, path, &())
28        .await
29        .context("when loading gif bytes")?;
30    let gif = image::codecs::gif::GifDecoder::new(data.as_slice()).context("when decoding gif")?;
31    let frames = gif
32        .into_frames()
33        .map(|frame| {
34            let frame = frame.unwrap();
35            let (n, d) = frame.delay().numer_denom_ms();
36            let duration = n as f32 / d as f32 / 1000.0;
37
38            let mut image = frame.into_buffer();
39            if options.frame.premultiply_alpha {
40                for pixel in image.pixels_mut() {
41                    use image::Pixel;
42                    *pixel = pixel.map_without_alpha(|x| {
43                        (x as f32 * (pixel[3] as f32 / 0xff as f32)).round() as u8
44                    });
45                }
46            }
47
48            let mut texture = ugli::Texture::from_image_image(manager.ugli(), image);
49            texture.set_filter(options.frame.filter);
50            texture.set_wrap_mode(options.frame.wrap_mode);
51
52            GifFrame { texture, duration }
53        })
54        .collect();
55
56    Ok(frames)
57}