Image: Zero frames in a gif

Created on 12 Feb 2018  路  10Comments  路  Source: image-rs/image

Hi there,

I'm struggling to get the frames from a gif image. I've tried a couple of different gifs and they all come out as just 1 frame. Am I doing something obviously wrong?

    let mut f = File::open(filename).expect("File not found");

    let mut decoder = gif::Decoder::new(f);

    let mut frames = decoder.into_frames().expect("error decoding gif");

    for (i, frame) in frames.enumerate() {
        let mut image = frame.into_buffer();

        // display the image:
        //render_image(image.convert(), width, height);

        println!("Frame: {}", i);
        thread::sleep(time::Duration::from_millis(100));
        println!("next frame...");
    }

Every time I run this, I only see

Frame: 0
next frame...

Thanks for any help!

Calum

bug help wanted

Most helpful comment

Hi @drozdziak1, I think that what you are asking for is possible. I'll assume you have an iterator over your buffers. (see edit)

// create a vec of frames
let frames = Vec::new();
for buffer in buffers {
   frames.push(Frame::new(buffer));
}

// encode the frames into a gif
let mut file = File::create("foo.gif")?;
let encoder = gif::Encoder::new(file);
for frame in frames {
    encoder.encode(frame);
}

I haven't tested this yet. There are probably loads of things wrong with my example like mutable errors and stuff but that's the rough idea.


Edit

So at the moment this is not possible to do using only the image crate. However, you can use the gif crate to get it working:

extern crate image;
extern crate gif;

use image::{ImageBuffer, Rgba};
use gif::{Frame, Encoder};
use std::fs::File;

pub fn create_gif() {
    // create some frames
    let mut frames = Vec::new();
    for i in 0..100 {
        let mut red = i as u8;

        let mut image = ImageBuffer::new(100, 100);
        for (x, y, pixel) in image.enumerate_pixels_mut() {
            let mut green = x as u8;
            let mut blue = y as u8;
            *pixel = Rgba([red, green, blue, 1]);
        }
        frames.push(Frame::from_rgba(100, 100, image.clone().into_raw().as_mut_slice()));
    }

    // save frames to file
    if let Ok(file) = File::create("foo.gif") {
        if let Ok(mut encoder) = Encoder::new(file, 100, 100, &[]) {
            for frame in frames {
                encoder.write_frame(&frame);
            }
        }
    }
}

output

foo
_does not repeat. refresh to see again_

All 10 comments

Indeed, outputting the frames is just not implemented.

It鈥檚 actually not that easy to decide what you want to do with it at the end. Would it be preferable to output the actual frame as stored in the file or to process it according to the setting in the gif (e.g. blend, add the background color etc.) such that you get the frames you would want to display.

Since you've marked this as help wanted, could I work on it and make a pull request? I was hoping to be able to use each frame as an individual Rgbimage since I already have my own functions to display them.

I noticed you removed the easy tag
Do you anticipate this to be a hard task? I'm willing to spend time doing some background reading if necessary.

Just to keep you updated on where I'm heading with this, I've tried to use the decoder_to_image function but the only issue is that I want to call it multiple times but my value is moved after the first iteration. Do you think I'm going about it right?

gif.rs

pub use self::gif::{Frame};
use animation::{Frame as Image_Frame, Frames as Image_Frames};
use dynimage::decoder_to_image;

...

fn is_animated(&mut self) -> ImageResult<bool> {
    Ok(true)
}

fn into_frames(self) -> ImageResult<Image_Frames> {
    let mut frames = Vec::new();
    loop {
        let image_result = decoder_to_image(self); // <-- self moved here
        match image_result {
            Ok(dynamic_image) => {
                let image = dynamic_image.to_rgba();
                frames.push(Image_Frame::new(image));
            },
            Err(_) => break
        };
    }

    Ok(Image_Frames::new(frames))
}

I understand what you mean now about adding the background. I've got the function currently returning all the frames from the gif, but when I tested it on a gif file I end up with a lot of missing pixels. Presumably, these missing pixels can be taken from another frame. I'm trying to work it out.

fn into_frames(mut self) -> ImageResult<Image_Frames> {
    let mut frames = Vec::new();
    let reader = try!(self.get_reader());
    loop {
        if let None = try!(reader.next_frame_info()) {
            break;
        }
        let mut vec = vec![0; reader.buffer_size()];
        try!(reader.fill_buffer(&mut vec));
        if let Some(image_buffer) = ImageBuffer::from_raw(
            reader.width() as u32,
            reader.height() as u32,
            vec
        ) {
            let frame = Image_Frame::new(image_buffer);
            frames.push(frame);
        };
    }
    Ok(Image_Frames::new(frames))
}

You鈥檒l need to use the gif crate directly. Have a look at at the disposal method. It determines how to handle the frames

https://docs.rs/gif/0.9.2/gif/enum.DisposalMethod.html

Thanks for the pointer. I'm making decent progress now. Hopefully won't take much longer.

Can you please implement it in a way that the actual frames are produced. I.e. all the same size, with blending etc. I鈥檓 not sure but probably one should collapse frames with a delay of 0 into a single one.

Okay, I will do. I'll be taking some holiday this weekend so will probably get it finished sometime next week.

Hi, I'm working on a little project where I render a Mandelbrot set animation with changing parameters and I basically need the opposite of what you @calum are working on - a way to take my buffers and transform them into frames and then encode them in a GIF. Is that possible to do yet from the image crate?

Hi @drozdziak1, I think that what you are asking for is possible. I'll assume you have an iterator over your buffers. (see edit)

// create a vec of frames
let frames = Vec::new();
for buffer in buffers {
   frames.push(Frame::new(buffer));
}

// encode the frames into a gif
let mut file = File::create("foo.gif")?;
let encoder = gif::Encoder::new(file);
for frame in frames {
    encoder.encode(frame);
}

I haven't tested this yet. There are probably loads of things wrong with my example like mutable errors and stuff but that's the rough idea.


Edit

So at the moment this is not possible to do using only the image crate. However, you can use the gif crate to get it working:

extern crate image;
extern crate gif;

use image::{ImageBuffer, Rgba};
use gif::{Frame, Encoder};
use std::fs::File;

pub fn create_gif() {
    // create some frames
    let mut frames = Vec::new();
    for i in 0..100 {
        let mut red = i as u8;

        let mut image = ImageBuffer::new(100, 100);
        for (x, y, pixel) in image.enumerate_pixels_mut() {
            let mut green = x as u8;
            let mut blue = y as u8;
            *pixel = Rgba([red, green, blue, 1]);
        }
        frames.push(Frame::from_rgba(100, 100, image.clone().into_raw().as_mut_slice()));
    }

    // save frames to file
    if let Ok(file) = File::create("foo.gif") {
        if let Ok(mut encoder) = Encoder::new(file, 100, 100, &[]) {
            for frame in frames {
                encoder.write_frame(&frame);
            }
        }
    }
}

output

foo
_does not repeat. refresh to see again_

Was this page helpful?
0 / 5 - 0 ratings

Related issues

usagi picture usagi  路  8Comments

franleplant picture franleplant  路  3Comments

martinlindhe picture martinlindhe  路  6Comments

fintelia picture fintelia  路  8Comments

qarmin picture qarmin  路  8Comments