Skip to main content

ai_image/codecs/
gif.rs

1//!  Decoding of GIF Images
2//!
3//!  GIF (Graphics Interchange Format) is an image format that supports lossless compression.
4//!
5//!  # Related Links
6//!  * <http://www.w3.org/Graphics/GIF/spec-gif89a.txt> - The GIF Specification
7//!
8//! # Examples
9//! ```rust,no_run
10//! use ai_image::codecs::gif::{GifDecoder, GifEncoder};
11//! use ai_image::{ImageDecoder, AnimationDecoder};
12//! use std::fs::File;
13//! use no_std_io::io::BufReader;
14//! # fn main() -> no_std_io::io::Result<()> {
15//! // Decode a gif into frames
16//! let file_in = BufReader::new(File::open("foo.gif")?);
17//! let mut decoder = GifDecoder::new(file_in).unwrap();
18//! let frames = decoder.into_frames();
19//! let frames = frames.collect_frames().expect("error decoding gif");
20//!
21//! // Encode frames into a gif and save to a file
22//! let mut file_out = File::open("out.gif")?;
23//! let mut encoder = GifEncoder::new(file_out);
24//! encoder.encode_frames(frames.into_iter());
25//! # Ok(())
26//! # }
27//! ```
28#![allow(clippy::while_let_loop)]
29
30use alloc::{borrow::ToOwned, boxed::Box, format, vec, vec::Vec};
31use core::marker::PhantomData;
32use core::mem;
33use core::num::NonZeroU32;
34use no_std_io::io::{self, BufRead, Cursor, Read, Seek, Write};
35
36use gif::ColorOutput;
37use gif::{DisposalMethod, Frame};
38
39use crate::animation::{self, Ratio};
40use crate::color::{ColorType, Rgba};
41use crate::error::{
42    DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
43    ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
44};
45use crate::metadata::LoopCount;
46use crate::traits::Pixel;
47use crate::{
48    AnimationDecoder, ExtendedColorType, ImageBuffer, ImageDecoder, ImageEncoder, ImageFormat,
49    Limits,
50};
51
52/// GIF decoder
53pub struct GifDecoder<R: Read> {
54    reader: gif::Decoder<R>,
55    limits: Limits,
56}
57
58impl<R: Read> GifDecoder<R> {
59    /// Creates a new decoder that decodes the input steam `r`
60    pub fn new(r: R) -> ImageResult<GifDecoder<R>> {
61        let mut decoder = gif::DecodeOptions::new();
62        decoder.set_color_output(ColorOutput::RGBA);
63
64        Ok(GifDecoder {
65            reader: decoder.read_info(r).map_err(ImageError::from_decoding)?,
66            limits: Limits::no_limits(),
67        })
68    }
69}
70
71/// Wrapper struct around a `Cursor<Vec<u8>>`
72#[allow(dead_code)]
73#[deprecated]
74pub struct GifReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
75#[allow(deprecated)]
76impl<R> Read for GifReader<R> {
77    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
78        self.0.read(buf)
79    }
80
81    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
82        if self.0.position() == 0 && buf.is_empty() {
83            mem::swap(buf, self.0.get_mut());
84            Ok(buf.len())
85        } else {
86            self.0.read_to_end(buf)
87        }
88    }
89}
90
91impl<R: BufRead + Seek> ImageDecoder for GifDecoder<R> {
92    fn dimensions(&self) -> (u32, u32) {
93        (
94            u32::from(self.reader.width()),
95            u32::from(self.reader.height()),
96        )
97    }
98
99    fn color_type(&self) -> ColorType {
100        ColorType::Rgba8
101    }
102
103    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
104        limits.check_support(&crate::LimitSupport::default())?;
105
106        let (width, height) = self.dimensions();
107        limits.check_dimensions(width, height)?;
108
109        self.limits = limits;
110
111        Ok(())
112    }
113
114    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
115        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
116
117        let frame = match self
118            .reader
119            .next_frame_info()
120            .map_err(ImageError::from_decoding)?
121        {
122            Some(frame) => FrameInfo::new_from_frame(frame),
123            None => {
124                return Err(ImageError::Parameter(ParameterError::from_kind(
125                    ParameterErrorKind::NoMoreData,
126                )))
127            }
128        };
129
130        let (width, height) = self.dimensions();
131
132        if frame.left == 0
133            && frame.width == width
134            && (u64::from(frame.top) + u64::from(frame.height) <= u64::from(height))
135        {
136            // If the frame matches the logical screen, or, as a more general case,
137            // fits into it and touches its left and right borders, then
138            // we can directly write it into the buffer without causing line wraparound.
139            let line_length = usize::try_from(width)
140                .unwrap()
141                .checked_mul(self.color_type().bytes_per_pixel() as usize)
142                .unwrap();
143
144            // isolate the portion of the buffer to read the frame data into.
145            // the chunks above and below it are going to be zeroed.
146            let (blank_top, rest) =
147                buf.split_at_mut(line_length.checked_mul(frame.top as usize).unwrap());
148            let (buf, blank_bottom) =
149                rest.split_at_mut(line_length.checked_mul(frame.height as usize).unwrap());
150
151            debug_assert_eq!(buf.len(), self.reader.buffer_size());
152
153            // this is only necessary in case the buffer is not zeroed
154            for b in blank_top {
155                *b = 0;
156            }
157            // fill the middle section with the frame data
158            self.reader
159                .read_into_buffer(buf)
160                .map_err(ImageError::from_decoding)?;
161            // this is only necessary in case the buffer is not zeroed
162            for b in blank_bottom {
163                *b = 0;
164            }
165        } else {
166            // If the frame does not match the logical screen, read into an extra buffer
167            // and 'insert' the frame from left/top to logical screen width/height.
168            let buffer_size = (frame.width as usize)
169                .checked_mul(frame.height as usize)
170                .and_then(|s| s.checked_mul(4))
171                .ok_or(ImageError::Limits(LimitError::from_kind(
172                    LimitErrorKind::InsufficientMemory,
173                )))?;
174
175            self.limits.reserve_usize(buffer_size)?;
176            let mut frame_buffer = vec![0; buffer_size];
177            self.limits.free_usize(buffer_size);
178
179            self.reader
180                .read_into_buffer(&mut frame_buffer[..])
181                .map_err(ImageError::from_decoding)?;
182
183            let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer);
184            let image_buffer = ImageBuffer::from_raw(width, height, buf);
185
186            // `buffer_size` uses wrapping arithmetic, thus might not report the
187            // correct storage requirement if the result does not fit in `usize`.
188            // `ImageBuffer::from_raw` detects overflow and reports by returning `None`.
189            if frame_buffer.is_none() || image_buffer.is_none() {
190                return Err(ImageError::Unsupported(
191                    UnsupportedError::from_format_and_kind(
192                        ImageFormat::Gif.into(),
193                        UnsupportedErrorKind::GenericFeature(format!(
194                            "Image dimensions ({}, {}) are too large",
195                            frame.width, frame.height
196                        )),
197                    ),
198                ));
199            }
200
201            let frame_buffer = frame_buffer.unwrap();
202            let mut image_buffer = image_buffer.unwrap();
203
204            for (x, y, pixel) in image_buffer.enumerate_pixels_mut() {
205                let frame_x = x.wrapping_sub(frame.left);
206                let frame_y = y.wrapping_sub(frame.top);
207
208                if frame_x < frame.width && frame_y < frame.height {
209                    *pixel = *frame_buffer.get_pixel(frame_x, frame_y);
210                } else {
211                    // this is only necessary in case the buffer is not zeroed
212                    *pixel = Rgba([0, 0, 0, 0]);
213                }
214            }
215        }
216
217        Ok(())
218    }
219
220    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
221        // Similar to XMP metadata
222        Ok(self.reader.icc_profile().map(Vec::from))
223    }
224
225    fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
226        // XMP metadata must be part of the header which is read with `read_info`.
227        Ok(self.reader.xmp_metadata().map(Vec::from))
228    }
229
230    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
231        (*self).read_image(buf)
232    }
233}
234
235struct GifFrameIterator<R: Read> {
236    reader: gif::Decoder<R>,
237
238    width: u32,
239    height: u32,
240
241    non_disposed_frame: Option<ImageBuffer<Rgba<u8>, Vec<u8>>>,
242    limits: Limits,
243    // `is_end` is used to indicate whether the iterator has reached the end of the frames.
244    // Or encounter any un-recoverable error.
245    is_end: bool,
246}
247
248impl<R: BufRead + Seek> GifFrameIterator<R> {
249    fn new(decoder: GifDecoder<R>) -> GifFrameIterator<R> {
250        let (width, height) = decoder.dimensions();
251        let limits = decoder.limits.clone();
252
253        // intentionally ignore the background color for web compatibility
254
255        GifFrameIterator {
256            reader: decoder.reader,
257            width,
258            height,
259            non_disposed_frame: None,
260            limits,
261            is_end: false,
262        }
263    }
264}
265
266impl<R: Read> Iterator for GifFrameIterator<R> {
267    type Item = ImageResult<animation::Frame>;
268
269    fn next(&mut self) -> Option<ImageResult<animation::Frame>> {
270        if self.is_end {
271            return None;
272        }
273
274        // The iterator always produces RGBA8 images
275        const COLOR_TYPE: ColorType = ColorType::Rgba8;
276
277        // Allocate the buffer for the previous frame.
278        // This is done here and not in the constructor because
279        // the constructor cannot return an error when the allocation limit is exceeded.
280        if self.non_disposed_frame.is_none() {
281            if let Err(e) = self
282                .limits
283                .reserve_buffer(self.width, self.height, COLOR_TYPE)
284            {
285                return Some(Err(e));
286            }
287            self.non_disposed_frame = Some(ImageBuffer::from_pixel(
288                self.width,
289                self.height,
290                Rgba([0, 0, 0, 0]),
291            ));
292        }
293        // Bind to a variable to avoid repeated `.unwrap()` calls
294        let non_disposed_frame = self.non_disposed_frame.as_mut().unwrap();
295
296        // begin looping over each frame
297
298        let frame = match self.reader.next_frame_info() {
299            Ok(frame_info) => {
300                if let Some(frame) = frame_info {
301                    FrameInfo::new_from_frame(frame)
302                } else {
303                    // no more frames
304                    return None;
305                }
306            }
307            Err(err) => match err {
308                gif::DecodingError::Io(ref e) => {
309                    if e.kind() == io::ErrorKind::UnexpectedEof {
310                        // end of file reached, no more frames
311                        self.is_end = true;
312                    }
313                    return Some(Err(ImageError::from_decoding(err)));
314                }
315                _ => {
316                    return Some(Err(ImageError::from_decoding(err)));
317                }
318            },
319        };
320
321        // All allocations we do from now on will be freed at the end of this function.
322        // Therefore, do not count them towards the persistent limits.
323        // Instead, create a local instance of `Limits` for this function alone
324        // which will be dropped along with all the buffers when they go out of scope.
325        let mut local_limits = self.limits.clone();
326
327        // Check the allocation we're about to perform against the limits
328        if let Err(e) = local_limits.reserve_buffer(frame.width, frame.height, COLOR_TYPE) {
329            return Some(Err(e));
330        }
331        // Allocate the buffer now that the limits allowed it
332        let mut vec = vec![0; self.reader.buffer_size()];
333        if let Err(err) = self.reader.read_into_buffer(&mut vec) {
334            return Some(Err(ImageError::from_decoding(err)));
335        }
336
337        // create the image buffer from the raw frame.
338        // `buffer_size` uses wrapping arithmetic, thus might not report the
339        // correct storage requirement if the result does not fit in `usize`.
340        // on the other hand, `ImageBuffer::from_raw` detects overflow and
341        // reports by returning `None`.
342        let Some(mut frame_buffer) = ImageBuffer::from_raw(frame.width, frame.height, vec) else {
343            return Some(Err(ImageError::Unsupported(
344                UnsupportedError::from_format_and_kind(
345                    ImageFormat::Gif.into(),
346                    UnsupportedErrorKind::GenericFeature(format!(
347                        "Image dimensions ({}, {}) are too large",
348                        frame.width, frame.height
349                    )),
350                ),
351            )));
352        };
353
354        // blend the current frame with the non-disposed frame, then update
355        // the non-disposed frame according to the disposal method.
356        fn blend_and_dispose_pixel(
357            dispose: DisposalMethod,
358            previous: &mut Rgba<u8>,
359            current: &mut Rgba<u8>,
360        ) {
361            let pixel_alpha = current.channels()[3];
362            if pixel_alpha == 0 {
363                *current = *previous;
364            }
365
366            match dispose {
367                DisposalMethod::Any | DisposalMethod::Keep => {
368                    // do not dispose
369                    // (keep pixels from this frame)
370                    // note: the `Any` disposal method is underspecified in the GIF
371                    // spec, but most viewers treat it identically to `Keep`
372                    *previous = *current;
373                }
374                DisposalMethod::Background => {
375                    // restore to background color
376                    // (background shows through transparent pixels in the next frame)
377                    *previous = Rgba([0, 0, 0, 0]);
378                }
379                DisposalMethod::Previous => {
380                    // restore to previous
381                    // (dispose frames leaving the last none disposal frame)
382                }
383            }
384        }
385
386        // if `frame_buffer`'s frame exactly matches the entire image, then
387        // use it directly, else create a new buffer to hold the composited
388        // image.
389        let image_buffer = if (frame.left, frame.top) == (0, 0)
390            && (self.width, self.height) == frame_buffer.dimensions()
391        {
392            for (x, y, pixel) in frame_buffer.enumerate_pixels_mut() {
393                let previous_pixel = non_disposed_frame.get_pixel_mut(x, y);
394                blend_and_dispose_pixel(frame.disposal_method, previous_pixel, pixel);
395            }
396            frame_buffer
397        } else {
398            // Check limits before allocating the buffer
399            if let Err(e) = local_limits.reserve_buffer(self.width, self.height, COLOR_TYPE) {
400                return Some(Err(e));
401            }
402            ImageBuffer::from_fn(self.width, self.height, |x, y| {
403                let frame_x = x.wrapping_sub(frame.left);
404                let frame_y = y.wrapping_sub(frame.top);
405                let previous_pixel = non_disposed_frame.get_pixel_mut(x, y);
406
407                if frame_x < frame_buffer.width() && frame_y < frame_buffer.height() {
408                    let mut pixel = *frame_buffer.get_pixel(frame_x, frame_y);
409                    blend_and_dispose_pixel(frame.disposal_method, previous_pixel, &mut pixel);
410                    pixel
411                } else {
412                    // out of bounds, return pixel from previous frame
413                    *previous_pixel
414                }
415            })
416        };
417
418        Some(Ok(animation::Frame::from_parts(
419            image_buffer,
420            0,
421            0,
422            frame.delay,
423        )))
424    }
425}
426
427impl<'a, R: BufRead + Seek + 'a> AnimationDecoder<'a> for GifDecoder<R> {
428    fn loop_count(&self) -> LoopCount {
429        match self.reader.repeat() {
430            gif::Repeat::Finite(n @ 1..) => {
431                LoopCount::Finite(NonZeroU32::new(n.into()).expect("repeat is non-zero"))
432            }
433            gif::Repeat::Finite(0) | gif::Repeat::Infinite => LoopCount::Infinite,
434        }
435    }
436
437    fn into_frames(self) -> animation::Frames<'a> {
438        animation::Frames::new(Box::new(GifFrameIterator::new(self)))
439    }
440}
441
442struct FrameInfo {
443    left: u32,
444    top: u32,
445    width: u32,
446    height: u32,
447    disposal_method: DisposalMethod,
448    delay: animation::Delay,
449}
450
451impl FrameInfo {
452    fn new_from_frame(frame: &Frame) -> FrameInfo {
453        FrameInfo {
454            left: u32::from(frame.left),
455            top: u32::from(frame.top),
456            width: u32::from(frame.width),
457            height: u32::from(frame.height),
458            disposal_method: frame.dispose,
459            // frame.delay is in units of 10ms so frame.delay*10 is in ms
460            delay: animation::Delay::from_ratio(Ratio::new(u32::from(frame.delay) * 10, 1)),
461        }
462    }
463}
464
465/// Number of repetitions for a GIF animation
466#[derive(Clone, Copy, Debug)]
467pub enum Repeat {
468    /// Finite number of repetitions
469    Finite(u16),
470    /// Looping GIF
471    Infinite,
472}
473
474impl Repeat {
475    pub(crate) fn to_gif_enum(self) -> gif::Repeat {
476        match self {
477            Repeat::Finite(n) => gif::Repeat::Finite(n),
478            Repeat::Infinite => gif::Repeat::Infinite,
479        }
480    }
481}
482
483/// GIF encoder.
484pub struct GifEncoder<W: Write> {
485    w: Option<W>,
486    gif_encoder: Option<gif::Encoder<W>>,
487    speed: i32,
488    repeat: Option<Repeat>,
489}
490
491impl<W: Write> GifEncoder<W> {
492    /// Creates a new GIF encoder with a speed of 1. This prioritizes quality over performance at any cost.
493    pub fn new(w: W) -> GifEncoder<W> {
494        Self::new_with_speed(w, 1)
495    }
496
497    /// Create a new GIF encoder, and has the speed parameter `speed`. See
498    /// [`Frame::from_rgba_speed`](https://docs.rs/gif/latest/gif/struct.Frame.html#method.from_rgba_speed)
499    /// for more information.
500    pub fn new_with_speed(w: W, speed: i32) -> GifEncoder<W> {
501        assert!(
502            (1..=30).contains(&speed),
503            "speed needs to be in the range [1, 30]"
504        );
505        GifEncoder {
506            w: Some(w),
507            gif_encoder: None,
508            speed,
509            repeat: None,
510        }
511    }
512
513    /// Set the repeat behaviour of the encoded GIF
514    pub fn set_repeat(&mut self, repeat: Repeat) -> ImageResult<()> {
515        if let Some(ref mut encoder) = self.gif_encoder {
516            encoder
517                .set_repeat(repeat.to_gif_enum())
518                .map_err(ImageError::from_encoding)?;
519        }
520        self.repeat = Some(repeat);
521        Ok(())
522    }
523
524    /// Encode a single image.
525    pub fn encode(
526        &mut self,
527        data: &[u8],
528        width: u32,
529        height: u32,
530        color: ExtendedColorType,
531    ) -> ImageResult<()> {
532        let (width, height) = self.gif_dimensions(width, height)?;
533        match color {
534            ExtendedColorType::Rgb8 => {
535                self.encode_gif(Frame::from_rgb_speed(width, height, data, self.speed))
536            }
537            ExtendedColorType::Rgba8 => self.encode_gif(Frame::from_rgba_speed(
538                width,
539                height,
540                &mut data.to_owned(),
541                self.speed,
542            )),
543            _ => Err(ImageError::Unsupported(
544                UnsupportedError::from_format_and_kind(
545                    ImageFormat::Gif.into(),
546                    UnsupportedErrorKind::Color(color),
547                ),
548            )),
549        }
550    }
551
552    /// Encode one frame of animation.
553    pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> {
554        let frame = self.convert_frame(img_frame)?;
555        self.encode_gif(frame)
556    }
557
558    /// Encodes Frames.
559    /// Consider using `try_encode_frames` instead to encode an `animation::Frames` like iterator.
560    pub fn encode_frames<F>(&mut self, frames: F) -> ImageResult<()>
561    where
562        F: IntoIterator<Item = animation::Frame>,
563    {
564        for img_frame in frames {
565            self.encode_frame(img_frame)?;
566        }
567        Ok(())
568    }
569
570    /// Try to encode a collection of `ImageResult<animation::Frame>` objects.
571    /// Use this function to encode an `animation::Frames` like iterator.
572    /// Whenever an `Err` item is encountered, that value is returned without further actions.
573    pub fn try_encode_frames<F>(&mut self, frames: F) -> ImageResult<()>
574    where
575        F: IntoIterator<Item = ImageResult<animation::Frame>>,
576    {
577        for img_frame in frames {
578            self.encode_frame(img_frame?)?;
579        }
580        Ok(())
581    }
582
583    pub(crate) fn convert_frame(
584        &mut self,
585        img_frame: animation::Frame,
586    ) -> ImageResult<Frame<'static>> {
587        // get the delay before converting img_frame
588        let frame_delay = img_frame.delay().into_ratio().to_integer();
589        // convert img_frame into RgbaImage
590        let mut rbga_frame = img_frame.into_buffer();
591        let (width, height) = self.gif_dimensions(rbga_frame.width(), rbga_frame.height())?;
592
593        // Create the gif::Frame from the animation::Frame
594        let mut frame = Frame::from_rgba_speed(width, height, &mut rbga_frame, self.speed);
595        // Saturate the conversion to u16::MAX instead of returning an error as that
596        // would require a new special cased variant in ParameterErrorKind which most
597        // likely couldn't be reused for other cases. This isn't a bad trade-off given
598        // that the current algorithm is already lossy.
599        frame.delay = (frame_delay / 10).try_into().unwrap_or(u16::MAX);
600
601        Ok(frame)
602    }
603
604    fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> {
605        fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> {
606            let width = u16::try_from(width).ok()?;
607            let height = u16::try_from(height).ok()?;
608            Some((width, height))
609        }
610
611        // TODO: this is not very idiomatic yet. Should return an EncodingError.
612        inner_dimensions(width, height).ok_or_else(|| {
613            ImageError::Parameter(ParameterError::from_kind(
614                ParameterErrorKind::DimensionMismatch,
615            ))
616        })
617    }
618
619    pub(crate) fn encode_gif(&mut self, mut frame: Frame) -> ImageResult<()> {
620        let gif_encoder;
621        if let Some(ref mut encoder) = self.gif_encoder {
622            gif_encoder = encoder;
623        } else {
624            let writer = self.w.take().unwrap();
625            let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[])
626                .map_err(ImageError::from_encoding)?;
627            if let Some(ref repeat) = self.repeat {
628                encoder
629                    .set_repeat(repeat.to_gif_enum())
630                    .map_err(ImageError::from_encoding)?;
631            }
632            self.gif_encoder = Some(encoder);
633            gif_encoder = self.gif_encoder.as_mut().unwrap();
634        }
635
636        frame.dispose = DisposalMethod::Background;
637
638        gif_encoder
639            .write_frame(&frame)
640            .map_err(ImageError::from_encoding)
641    }
642}
643impl<W: Write> ImageEncoder for GifEncoder<W> {
644    fn write_image(
645        mut self,
646        buf: &[u8],
647        width: u32,
648        height: u32,
649        color_type: ExtendedColorType,
650    ) -> ImageResult<()> {
651        self.encode(buf, width, height, color_type)
652    }
653}
654
655impl ImageError {
656    fn from_decoding(err: gif::DecodingError) -> ImageError {
657        use gif::DecodingError::*;
658        match err {
659            Io(io_err) => ImageError::IoError(io_err),
660            other => ImageError::Decoding(DecodingError::new(ImageFormat::Gif.into(), other)),
661        }
662    }
663
664    fn from_encoding(err: gif::EncodingError) -> ImageError {
665        use gif::EncodingError::*;
666        match err {
667            Io(io_err) => ImageError::IoError(io_err),
668            other => ImageError::Encoding(EncodingError::new(ImageFormat::Gif.into(), other)),
669        }
670    }
671}
672
673#[cfg(test)]
674mod test {
675    use super::*;
676
677    #[test]
678    fn frames_exceeding_logical_screen_size() {
679        // This is a gif with 10x10 logical screen, but a 16x16 frame + 6px offset inside.
680        let data = vec![
681            0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0A, 0x00, 0x0A, 0x00, 0xF0, 0x00, 0x00, 0x00,
682            0x00, 0x00, 0x0E, 0xFF, 0x1F, 0x21, 0xF9, 0x04, 0x09, 0x64, 0x00, 0x00, 0x00, 0x2C,
683            0x06, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x23, 0x84, 0x8F, 0xA9,
684            0xBB, 0xE1, 0xE8, 0x42, 0x8A, 0x0F, 0x50, 0x79, 0xAE, 0xD1, 0xF9, 0x7A, 0xE8, 0x71,
685            0x5B, 0x48, 0x81, 0x64, 0xD5, 0x91, 0xCA, 0x89, 0x4D, 0x21, 0x63, 0x89, 0x4C, 0x09,
686            0x77, 0xF5, 0x6D, 0x14, 0x00, 0x3B,
687        ];
688
689        let decoder = GifDecoder::new(Cursor::new(data)).unwrap();
690        let mut buf = vec![0u8; decoder.total_bytes() as usize];
691
692        assert!(decoder.read_image(&mut buf).is_ok());
693    }
694}