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