Coverage Report

Created: 2026-04-12 07:31

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