Skip to main content

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