Coverage Report

Created: 2025-07-04 06:57

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