image-rs / image-gif

GIF en- and decoder
Apache License 2.0
148 stars 42 forks source link

How to convert a variety of images into frames of a gif #143

Closed LukasDeco closed 1 year ago

LukasDeco commented 1 year ago

I have some code where I am trying to dynamically generate a gif from various urls. The urls could point to JPGs or PNGs.

Here's my code so far:

        let gif_path = "./src/assets/test-gifs/test.gif".to_string();
        let mut image = File::create(gif_path.clone()).unwrap();
        // Create a vector of image buffers from the given image URLs
        let image_buffers: Vec<Vec<u8>> = self.download_images(product_images).await?;
        let color_map = &[0xFF, 0xFF, 0xFF, 0xFF, 0, 0];
        let (width, height) = (400, 400);
        let mut encoder = Encoder::new(&mut image, width, height, color_map).unwrap();
        encoder.set_repeat(Repeat::Infinite).unwrap();
        for state in image_buffers {
            let mut state_copy = state.clone();
            let mut frame = Frame::from_rgba(width, height, &mut state_copy);
            frame.width = width;
            frame.height = height;
            frame.buffer = Cow::Borrowed(&*state);
            encoder.write_frame(&frame).unwrap();
        }

And this is the function download_images:

        let mut image_buffers = Vec::new();
        for image_url in product_images {
            let task = task::spawn_blocking(move || {
                let response = reqwest::blocking::get::<String>(image_url.to_owned())?;
                let bytes = response.bytes()?;
                Ok::<Vec<u8>, anyhow::Error>(bytes.to_vec())
            });
            let image_bytes = task.await??;
            image_buffers.push(image_bytes);
        }
        Ok(image_buffers)

But here's the error I encounter on the Frame::from_rgba call:

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `640000`,
 right: `1064214`: Too much or too little pixel data for the given width and height to create a GIF Frame'

Is there some kind of conversion I need to do on the image before I call Frame::from_rgba? I guess maybe resize the image, but is that all? I'm a newb to Rust so thanks in advance.

fintelia commented 1 year ago

You need to decode the PNG / JPEG, then resize, then pass it to from_rgba. To do the decoding step you probably want to use the main image crate

LukasDeco commented 1 year ago

Thank you @fintelia! Can I use gif crate to decode it? Or you are saying to use image crate to decode it?

fintelia commented 1 year ago

The gif crate can only decode GIFs. If you want to decode a PNG or JPEG, you'll need to use image (which internally calls into dedicated crates for each format: png, jpeg_decoder, etc.)

LukasDeco commented 1 year ago

Thanks @fintelia! I was able to make it work using image::load_from_memory and then I used the imageops::resize crate function to resize it and convert it to an ImageBuffer. Then using the ImageBuffer I can pass that into Frame::from_rgba if I call into_raw() on each buffer as I pass it to the frame. and it works like a charm! Thanks for pointing me to the image crate!