Coverage Report

Created: 2025-10-10 07:21

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