Coverage Report

Created: 2026-09-02 07:14

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::ImageReader;
12
//! use std::fs::File;
13
//! use std::io::BufReader;
14
//!
15
//! # fn main() -> std::io::Result<()> {
16
//! // Decode a gif into frames
17
//! let file_in = BufReader::new(File::open("foo.gif")?);
18
//! let mut decoder = Box::new(GifDecoder::new(file_in).unwrap());
19
//!
20
//! let frames = ImageReader::from_decoder(decoder).into_frames();
21
//! let frames = frames.collect_frames().expect("error decoding gif");
22
//!
23
//! // Encode frames into a gif and save to a file
24
//! let mut file_out = File::open("out.gif")?;
25
//! let mut encoder = GifEncoder::new(file_out);
26
//! encoder.encode_frames(frames.into_iter());
27
//! # Ok(())
28
//! # }
29
//! ```
30
#![allow(clippy::while_let_loop)]
31
32
use std::io::{BufRead, Read, Seek, Write};
33
use std::num::NonZeroU32;
34
35
use gif::ColorOutput;
36
use gif::{DisposalMethod, Frame};
37
38
use crate::animation;
39
use crate::color::{ColorType, Rgba};
40
use crate::error::{
41
    DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
42
    ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
43
};
44
use crate::io::{
45
    DecodedAnimationAttributes, DecodedImageAttributes, DecodedMetadataHint, DecoderPreparedImage,
46
    FormatAttributes,
47
};
48
use crate::metadata::LoopCount;
49
use crate::traits::Pixel;
50
use crate::{ExtendedColorType, ImageBuffer, ImageDecoder, ImageEncoder, ImageFormat, Limits};
51
52
/// GIF decoder
53
pub struct GifDecoder<R: Read> {
54
    options: gif::DecodeOptions,
55
    reader: Option<R>,
56
    decoder: Option<gif::Decoder<R>>,
57
    non_disposed_frame: Option<ImageBuffer<Rgba<u8>, Vec<u8>>>,
58
    limits: Limits,
59
}
60
61
const COLOR: ColorType = ColorType::Rgba8;
62
63
impl<R: Read> GifDecoder<R> {
64
    /// Creates a new decoder that decodes the input steam `r`
65
6.56k
    pub fn new(r: R) -> ImageResult<GifDecoder<R>> {
66
6.56k
        let mut options = gif::DecodeOptions::new();
67
6.56k
        options.set_color_output(ColorOutput::RGBA);
68
69
6.56k
        Ok(GifDecoder {
70
6.56k
            options,
71
6.56k
            reader: Some(r),
72
6.56k
            decoder: None,
73
6.56k
            non_disposed_frame: None,
74
6.56k
            limits: Limits::no_limits(),
75
6.56k
        })
76
6.56k
    }
77
78
    // We're manipulating the lifetime. The early return must not borrow from `self.decoder` for
79
    // the whole scope of the function thus this check does not work with if-let patterns until at
80
    // least the next generation borrow checker (as of 1.89).
81
    //
82
    // FIXME: would be nice to have a sub-object for these two attributes or an enum for the state
83
    // machine so that we can `ensure_decoder` without borrowing the whole `GifDecoder` type.
84
    #[allow(clippy::unnecessary_unwrap)]
85
38.9k
    fn ensure_decoder(&mut self) -> ImageResult<&mut gif::Decoder<R>> {
86
38.9k
        if self.decoder.is_some() {
87
32.4k
            return Ok(self.decoder.as_mut().unwrap());
88
6.56k
        }
89
90
6.56k
        let Some(reader) = self.reader.take() else {
91
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
92
0
                ParameterErrorKind::FailedAlready,
93
0
            )));
94
        };
95
96
6.56k
        let decoder = self
97
6.56k
            .options
98
6.56k
            .clone()
99
6.56k
            .read_info(reader)
100
6.56k
            .map_err(ImageError::from_decoding)?;
101
102
5.46k
        Ok(self.decoder.insert(decoder))
103
38.9k
    }
104
105
21.8k
    fn layout_from_decoder(decoder: &gif::Decoder<R>) -> crate::ImageLayout {
106
21.8k
        crate::ImageLayout::new(
107
21.8k
            decoder.width().into(),
108
21.8k
            decoder.height().into(),
109
21.8k
            ColorType::Rgba8,
110
        )
111
21.8k
    }
112
}
113
114
impl<R: BufRead + Seek> ImageDecoder for GifDecoder<R> {
115
13.4k
    fn format_attributes(&self) -> FormatAttributes {
116
13.4k
        FormatAttributes {
117
13.4k
            // FIXME: may appear anywhere.
118
13.4k
            xmp: DecodedMetadataHint::InHeader,
119
13.4k
            icc: DecodedMetadataHint::InHeader,
120
13.4k
            iptc: DecodedMetadataHint::None,
121
13.4k
            // FIXME: there is some in a Photoshop 8BIM extension which we do not collect.
122
13.4k
            exif: DecodedMetadataHint::Unsupported,
123
13.4k
            supports_animation: true,
124
13.4k
            ..FormatAttributes::default()
125
13.4k
        }
126
13.4k
    }
127
128
0
    fn animation_attributes(&mut self) -> Option<DecodedAnimationAttributes> {
129
0
        let decoder = self.ensure_decoder().ok()?;
130
0
        let loop_count = match decoder.repeat() {
131
0
            gif::Repeat::Finite(n @ 1..) => {
132
0
                LoopCount::Finite(NonZeroU32::new(n.into()).expect("repeat is non-zero"))
133
            }
134
0
            gif::Repeat::Finite(0) | gif::Repeat::Infinite => LoopCount::Infinite,
135
        };
136
137
0
        Some(DecodedAnimationAttributes { loop_count })
138
0
    }
139
140
17.4k
    fn prepare_image(&mut self) -> ImageResult<DecoderPreparedImage> {
141
17.4k
        let decoder = self.ensure_decoder()?;
142
16.3k
        Ok(Self::layout_from_decoder(decoder).into())
143
17.4k
    }
144
145
6.56k
    fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
146
6.56k
        limits.check_support(&crate::LimitSupport::default())?;
147
148
6.56k
        let layout = self.prepare_image()?;
149
5.46k
        limits.check_layout_dimensions(&layout)?;
150
151
5.46k
        self.limits = limits;
152
153
5.46k
        Ok(())
154
6.56k
    }
155
156
5.45k
    fn read_image(&mut self, buf: &mut [u8]) -> ImageResult<DecodedImageAttributes> {
157
5.45k
        let decoder = self.ensure_decoder()?;
158
159
5.45k
        let layout @ crate::ImageLayout {
160
5.45k
            width,
161
5.45k
            height,
162
5.45k
            color,
163
5.45k
        } = Self::layout_from_decoder(decoder);
164
165
        // Allocate the buffer for the previous frame.
166
        // This is done here and not in the constructor because
167
        // the constructor cannot return an error when the allocation limit is exceeded.
168
5.45k
        if self.non_disposed_frame.is_none() {
169
5.45k
            self.limits.reserve_buffer(width, height, color)?;
170
5.45k
            self.non_disposed_frame =
171
5.45k
                Some(ImageBuffer::from_pixel(width, height, Rgba([0, 0, 0, 0])));
172
0
        }
173
174
        // Initialized from `ensure_decoder` above, re-acquired for borrow checker.
175
5.45k
        let decoder = self.decoder.as_mut().unwrap();
176
5.45k
        assert_eq!(u64::try_from(buf.len()), Ok(layout.total_bytes()));
177
178
5.45k
        let frame = match decoder
179
5.45k
            .next_frame_info()
180
5.45k
            .map_err(ImageError::from_decoding)?
181
        {
182
5.21k
            Some(frame) => FrameInfo::new_from_frame(frame),
183
            None => {
184
5
                return Err(ImageError::Parameter(ParameterError::from_kind(
185
5
                    ParameterErrorKind::NoMoreData,
186
5
                )))
187
            }
188
        };
189
190
5.21k
        let frame_start_len = if (frame.left, frame.width) == (0, width)
191
132
            && (u64::from(frame.top) + u64::from(frame.height) <= u64::from(height))
192
        {
193
            // If the frame matches the logical screen, or, as a more general case,
194
            // fits into it and touches its left and right borders, then
195
            // we can directly write it into the buffer without causing line wraparound.
196
86
            let line_length = usize::try_from(width)
197
86
                .unwrap()
198
86
                .checked_mul(COLOR.bytes_per_pixel() as usize)
199
86
                .unwrap();
200
201
86
            let frame_start = line_length.checked_mul(frame.top as usize).unwrap();
202
86
            let frame_len = line_length.checked_mul(frame.height as usize).unwrap();
203
86
            Some((frame_start, frame_len))
204
        } else {
205
5.12k
            None
206
        };
207
208
5.21k
        if let Some((frame_start, frame_len)) = frame_start_len {
209
            // isolate the portion of the buffer to read the frame data into.
210
            // the rows above and below it are outside this frame's own pixel data, so they
211
            // must passthrough the previous frame's composited state (as if by
212
            // `DisposalMethod::Previous`), matching the row-based path below.
213
86
            let non_disposed_frame = self.non_disposed_frame.as_ref().unwrap();
214
86
            let (blank_top, rest) = buf.split_at_mut(frame_start);
215
86
            let (buf, blank_bottom) = rest.split_at_mut(frame_len);
216
217
86
            debug_assert_eq!(buf.len(), decoder.buffer_size());
218
219
86
            blank_top.copy_from_slice(&non_disposed_frame.subpixels()[..frame_start]);
220
221
            // fill the middle section with the frame data
222
86
            decoder
223
86
                .read_into_buffer(buf)
224
86
                .map_err(ImageError::from_decoding)?;
225
226
52
            blank_bottom
227
52
                .copy_from_slice(&non_disposed_frame.subpixels()[frame_start + frame_len..]);
228
        } else {
229
            // If the frame does not match the logical screen, read into an extra buffer
230
            // and 'insert' the frame from left/top to logical screen width/height.
231
5.12k
            let buffer_size = (frame.width as usize)
232
5.12k
                .checked_mul(frame.height as usize)
233
5.12k
                .and_then(|s| s.checked_mul(4))
234
5.12k
                .ok_or(ImageError::Limits(LimitError::from_kind(
235
5.12k
                    LimitErrorKind::InsufficientMemory,
236
5.12k
                )))?;
237
238
5.12k
            self.limits.reserve_usize(buffer_size)?;
239
5.12k
            let mut frame_buffer = vec![0; buffer_size];
240
5.12k
            self.limits.free_usize(buffer_size);
241
242
5.12k
            let decoder = self.ensure_decoder()?;
243
244
5.12k
            decoder
245
5.12k
                .read_into_buffer(&mut frame_buffer[..])
246
5.12k
                .map_err(ImageError::from_decoding)?;
247
248
3.94k
            let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer);
249
3.94k
            let image_buffer = ImageBuffer::from_raw(width, height, &mut *buf);
250
251
            // `buffer_size` uses wrapping arithmetic, thus might not report the
252
            // correct storage requirement if the result does not fit in `usize`.
253
            // `ImageBuffer::from_raw` detects overflow and reports by returning `None`.
254
3.94k
            if frame_buffer.is_none() || image_buffer.is_none() {
255
0
                return Err(ImageError::Unsupported(
256
0
                    UnsupportedError::from_format_and_kind(
257
0
                        ImageFormat::Gif.into(),
258
0
                        UnsupportedErrorKind::GenericFeature(format!(
259
0
                            "Image dimensions ({}, {}) are too large",
260
0
                            frame.width, frame.height
261
0
                        )),
262
0
                    ),
263
0
                ));
264
3.94k
            }
265
266
3.94k
            let frame_buffer = frame_buffer.unwrap();
267
3.94k
            let mut image_buffer = image_buffer.unwrap();
268
269
14.6G
            for (x, y, pixel) in image_buffer.enumerate_pixels_mut() {
270
14.6G
                let frame_x = x.wrapping_sub(frame.left);
271
14.6G
                let frame_y = y.wrapping_sub(frame.top);
272
273
14.6G
                if frame_x < frame.width && frame_y < frame.height {
274
232k
                    *pixel = *frame_buffer.get_pixel(frame_x, frame_y);
275
14.6G
                } else {
276
14.6G
                    // this is only necessary in case the buffer is not zeroed
277
14.6G
                    *pixel = Rgba([0, 0, 0, 0]);
278
14.6G
                }
279
            }
280
        }
281
282
        // Bind to a variable to avoid repeated `.unwrap()` calls
283
3.99k
        let non_disposed_frame = self.non_disposed_frame.as_mut().unwrap();
284
285
        // if `frame_buffer`'s frame exactly matches the entire image, then
286
        // use it directly, else create a new buffer to hold the composited
287
        // image.
288
3.99k
        if let Some((frame_start, frame_len)) = frame_start_len {
289
52
            // We can blend pixels in a fully contiguous region instead of row-by-row.
290
52
            let non_disposed_data =
291
52
                &mut non_disposed_frame.subpixels_mut()[frame_start..][..frame_len];
292
52
            let frame_data = &mut buf[frame_start..][..frame_len];
293
52
            blend_and_dispose_region(frame.disposal_method, non_disposed_data, frame_data);
294
52
        } else {
295
            // We have validated bounds already so no checked math.
296
3.94k
            let effective_left = frame.left.min(width);
297
3.94k
            let effective_width = (width - effective_left).min(frame.width);
298
299
3.94k
            let row_len = width as usize * COLOR.bytes_per_pixel() as usize;
300
3.94k
            let data_len = effective_width as usize * COLOR.bytes_per_pixel() as usize;
301
3.94k
            let row_skip = effective_left as usize * COLOR.bytes_per_pixel() as usize;
302
303
            // process rows before, within and after the frame. Everything not in bounds is copied
304
            // as if by `DisposalMethod::Previous`.
305
1.45M
            for y in 0..frame.top {
306
1.45M
                if y >= height {
307
358
                    break;
308
1.45M
                }
309
310
1.45M
                let start = y as usize * row_len;
311
1.45M
                let non_disposed_data = &mut non_disposed_frame.subpixels_mut()[start..][..row_len];
312
1.45M
                let frame_data = &mut buf[start..][..row_len];
313
1.45M
                frame_data.copy_from_slice(non_disposed_data);
314
            }
315
316
115k
            for y in frame.top..(frame.top + frame.height) {
317
115k
                if y >= height {
318
198
                    break;
319
115k
                }
320
321
115k
                let start = y as usize * row_len;
322
323
115k
                let non_disposed_data = &mut non_disposed_frame.subpixels_mut()[start..][..row_len];
324
115k
                let frame_data = &mut buf[start..][..row_len];
325
326
115k
                frame_data[..row_skip].copy_from_slice(&non_disposed_data[..row_skip]);
327
328
115k
                blend_and_dispose_region(
329
115k
                    frame.disposal_method,
330
115k
                    &mut non_disposed_data[row_skip..][..data_len],
331
115k
                    &mut frame_data[row_skip..][..data_len],
332
                );
333
334
115k
                let after_frame = row_skip + data_len;
335
115k
                frame_data[after_frame..].copy_from_slice(&non_disposed_data[after_frame..]);
336
            }
337
338
12.9M
            for y in (frame.top + frame.height)..height {
339
12.9M
                if y >= height {
340
0
                    break;
341
12.9M
                }
342
343
12.9M
                let start = y as usize * row_len;
344
12.9M
                let non_disposed_data = &mut non_disposed_frame.subpixels_mut()[start..][..row_len];
345
12.9M
                let frame_data = &mut buf[start..][..row_len];
346
12.9M
                frame_data.copy_from_slice(non_disposed_data);
347
            }
348
        }
349
350
3.99k
        Ok(DecodedImageAttributes {
351
3.99k
            delay: Some(frame.delay),
352
3.99k
            ..Default::default()
353
3.99k
        })
354
5.45k
    }
355
356
5.46k
    fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
357
5.46k
        let decoder = self.ensure_decoder()?;
358
        // Similar to XMP metadata
359
5.46k
        Ok(decoder.icc_profile().map(Vec::from))
360
5.46k
    }
361
362
5.46k
    fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
363
5.46k
        let decoder = self.ensure_decoder()?;
364
        // XMP metadata must be part of the header which is read with `read_info`.
365
5.46k
        Ok(decoder.xmp_metadata().map(Vec::from))
366
5.46k
    }
367
}
368
369
115k
fn blend_and_dispose_region(
370
115k
    dispose: DisposalMethod,
371
115k
    non_disposed_data: &mut [u8],
372
115k
    frame_data: &mut [u8],
373
115k
) {
374
115k
    let non_disposed_data = Rgba::<u8>::pixels_from_channels_mut(non_disposed_data);
375
115k
    let frame_data = Rgba::<u8>::pixels_from_channels_mut(frame_data);
376
377
233k
    for (disposed, pixel) in non_disposed_data.iter_mut().zip(frame_data.iter_mut()) {
378
233k
        // FIXME: internal dispatch on disposal method may be slow, investigate if this is
379
233k
        // properly and reliably vectorized.
380
233k
        blend_and_dispose_pixel(dispose, disposed, pixel);
381
233k
    }
382
115k
}
383
384
// blend the current frame with the non-disposed frame, then update
385
// the non-disposed frame according to the disposal method.
386
#[inline]
387
233k
fn blend_and_dispose_pixel(
388
233k
    dispose: DisposalMethod,
389
233k
    previous: &mut Rgba<u8>,
390
233k
    current: &mut Rgba<u8>,
391
233k
) {
392
    // Instead of only checking the alpha channel, use a bitmask to check
393
    // the entire pixel and allow for better auto-vectorization.
394
    // Makes it about 5% to 10% faster
395
    const ALPHA_MASK: u32 = u32::from_ne_bytes([0, 0, 0, 255]);
396
233k
    let pixel_alpha = u32::from_ne_bytes(current.0) & ALPHA_MASK;
397
233k
    if pixel_alpha == 0 {
398
9.89k
        *current = *previous;
399
223k
    }
400
401
233k
    match dispose {
402
223k
        DisposalMethod::Any | DisposalMethod::Keep => {
403
223k
            // do not dispose
404
223k
            // (keep pixels from this frame)
405
223k
            // note: the `Any` disposal method is underspecified in the GIF
406
223k
            // spec, but most viewers treat it identically to `Keep`
407
223k
            *previous = *current;
408
223k
        }
409
5.30k
        DisposalMethod::Background => {
410
5.30k
            // restore to background color
411
5.30k
            // (background shows through transparent pixels in the next frame)
412
5.30k
            *previous = Rgba([0, 0, 0, 0]);
413
5.30k
        }
414
4.64k
        DisposalMethod::Previous => {
415
4.64k
            // restore to previous
416
4.64k
            // (dispose frames leaving the last none disposal frame)
417
4.64k
        }
418
    }
419
233k
}
420
421
struct FrameInfo {
422
    left: u32,
423
    top: u32,
424
    width: u32,
425
    height: u32,
426
    disposal_method: DisposalMethod,
427
    delay: animation::Delay,
428
}
429
430
impl FrameInfo {
431
5.21k
    fn new_from_frame(frame: &Frame) -> FrameInfo {
432
5.21k
        FrameInfo {
433
5.21k
            left: u32::from(frame.left),
434
5.21k
            top: u32::from(frame.top),
435
5.21k
            width: u32::from(frame.width),
436
5.21k
            height: u32::from(frame.height),
437
5.21k
            disposal_method: frame.dispose,
438
5.21k
            // frame.delay is in units of 10ms so frame.delay*10 is in ms
439
5.21k
            delay: animation::Delay::from_millis(u32::from(frame.delay) * 10),
440
5.21k
        }
441
5.21k
    }
442
}
443
444
/// Number of repetitions for a GIF animation
445
#[derive(Clone, Copy, Debug)]
446
pub enum Repeat {
447
    /// Finite number of repetitions
448
    Finite(u16),
449
    /// Looping GIF
450
    Infinite,
451
}
452
453
impl Repeat {
454
0
    pub(crate) fn to_gif_enum(self) -> gif::Repeat {
455
0
        match self {
456
0
            Repeat::Finite(n) => gif::Repeat::Finite(n),
457
0
            Repeat::Infinite => gif::Repeat::Infinite,
458
        }
459
0
    }
460
}
461
462
/// GIF encoder.
463
pub struct GifEncoder<W: Write> {
464
    w: Option<W>,
465
    gif_encoder: Option<gif::Encoder<W>>,
466
    speed: i32,
467
    repeat: Option<Repeat>,
468
}
469
470
impl<W: Write> GifEncoder<W> {
471
    /// Creates a new GIF encoder with a speed of 10. This provides a good balance between quality and encoding speed.
472
0
    pub fn new(w: W) -> GifEncoder<W> {
473
0
        Self::new_with_speed(w, 10)
474
0
    }
Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::new
Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new
475
476
    /// Create a new GIF encoder, and has the speed parameter `speed`. See
477
    /// [`Frame::from_rgba_speed`] 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
    }
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
490
491
    /// Set the repeat behaviour of the encoded GIF
492
0
    pub fn set_repeat(&mut self, repeat: Repeat) -> ImageResult<()> {
493
0
        if let Some(ref mut encoder) = self.gif_encoder {
494
0
            encoder
495
0
                .set_repeat(repeat.to_gif_enum())
496
0
                .map_err(ImageError::from_encoding)?;
497
0
        }
498
0
        self.repeat = Some(repeat);
499
0
        Ok(())
500
0
    }
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
            ExtendedColorType::L8 => {
522
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}
523
524
0
                self.encode_gif(Frame::from_palette_pixels(
525
0
                    width, height, data, palette, None,
526
                ))
527
            }
528
            ExtendedColorType::La8 => {
529
0
                self.encode_gif(Frame::from_grayscale_with_alpha(width, height, data))
530
            }
531
0
            _ => Err(ImageError::Unsupported(
532
0
                UnsupportedError::from_format_and_kind(
533
0
                    ImageFormat::Gif.into(),
534
0
                    UnsupportedErrorKind::Color(color),
535
0
                ),
536
0
            )),
537
        }
538
0
    }
Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::encode
Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode
539
540
    /// Encode one frame of animation.
541
0
    pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> {
542
0
        let frame = self.convert_frame(img_frame)?;
543
0
        self.encode_gif(frame)
544
0
    }
545
546
    /// Encodes Frames.
547
    /// Consider using `try_encode_frames` instead to encode an `animation::Frames` like iterator.
548
0
    pub fn encode_frames<F>(&mut self, frames: F) -> ImageResult<()>
549
0
    where
550
0
        F: IntoIterator<Item = animation::Frame>,
551
    {
552
0
        for img_frame in frames {
553
0
            self.encode_frame(img_frame)?;
554
        }
555
0
        Ok(())
556
0
    }
557
558
    /// Try to encode a collection of `ImageResult<animation::Frame>` objects.
559
    /// Use this function to encode an `animation::Frames` like iterator.
560
    /// Whenever an `Err` item is encountered, that value is returned without further actions.
561
0
    pub fn try_encode_frames<F>(&mut self, frames: F) -> ImageResult<()>
562
0
    where
563
0
        F: IntoIterator<Item = ImageResult<animation::Frame>>,
564
    {
565
0
        for img_frame in frames {
566
0
            self.encode_frame(img_frame?)?;
567
        }
568
0
        Ok(())
569
0
    }
570
571
0
    pub(crate) fn convert_frame(
572
0
        &mut self,
573
0
        img_frame: animation::Frame,
574
0
    ) -> ImageResult<Frame<'static>> {
575
        // get the delay before converting img_frame
576
0
        let frame_delay = img_frame.delay().as_millis();
577
        // convert img_frame into RgbaImage
578
0
        let mut rbga_frame = img_frame.into_buffer();
579
0
        let (width, height) = self.gif_dimensions(rbga_frame.width(), rbga_frame.height())?;
580
581
        // Create the gif::Frame from the animation::Frame
582
0
        let mut frame = Frame::from_rgba_speed(width, height, &mut rbga_frame, self.speed);
583
        // Saturate the conversion to u16::MAX instead of returning an error as that
584
        // would require a new special cased variant in ParameterErrorKind which most
585
        // likely couldn't be reused for other cases. This isn't a bad trade-off given
586
        // that the current algorithm is already lossy.
587
0
        frame.delay = (frame_delay / 10).try_into().unwrap_or(u16::MAX);
588
589
0
        Ok(frame)
590
0
    }
591
592
0
    fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> {
593
0
        fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> {
594
0
            let width = u16::try_from(width).ok()?;
595
0
            let height = u16::try_from(height).ok()?;
596
0
            Some((width, height))
597
0
        }
598
599
        // TODO: this is not very idiomatic yet. Should return an EncodingError.
600
0
        inner_dimensions(width, height).ok_or_else(|| {
601
0
            ImageError::Parameter(ParameterError::from_kind(
602
0
                ParameterErrorKind::DimensionMismatch,
603
0
            ))
604
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}
605
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
606
607
0
    pub(crate) fn encode_gif(&mut self, mut frame: Frame) -> ImageResult<()> {
608
        let gif_encoder;
609
0
        if let Some(ref mut encoder) = self.gif_encoder {
610
0
            gif_encoder = encoder;
611
0
        } else {
612
0
            let writer = self.w.take().unwrap();
613
0
            let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[])
614
0
                .map_err(ImageError::from_encoding)?;
615
0
            if let Some(ref repeat) = self.repeat {
616
0
                encoder
617
0
                    .set_repeat(repeat.to_gif_enum())
618
0
                    .map_err(ImageError::from_encoding)?;
619
0
            }
620
0
            self.gif_encoder = Some(encoder);
621
0
            gif_encoder = self.gif_encoder.as_mut().unwrap();
622
        }
623
624
0
        frame.dispose = DisposalMethod::Background;
625
626
0
        gif_encoder
627
0
            .write_frame(&frame)
628
0
            .map_err(ImageError::from_encoding)
629
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
630
}
631
impl<W: Write> ImageEncoder for GifEncoder<W> {
632
0
    fn write_image(
633
0
        mut self,
634
0
        buf: &[u8],
635
0
        width: u32,
636
0
        height: u32,
637
0
        color_type: ExtendedColorType,
638
0
    ) -> ImageResult<()> {
639
0
        self.encode(buf, width, height, color_type)
640
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
641
}
642
643
impl ImageError {
644
2.54k
    fn from_decoding(err: gif::DecodingError) -> ImageError {
645
        use gif::DecodingError::*;
646
2.54k
        match err {
647
0
            Io(io_err) => ImageError::IoError(io_err),
648
2.54k
            other => ImageError::Decoding(DecodingError::new(ImageFormat::Gif.into(), other)),
649
        }
650
2.54k
    }
651
652
0
    fn from_encoding(err: gif::EncodingError) -> ImageError {
653
        use gif::EncodingError::*;
654
0
        match err {
655
0
            Io(io_err) => ImageError::IoError(io_err),
656
0
            other => ImageError::Encoding(EncodingError::new(ImageFormat::Gif.into(), other)),
657
        }
658
0
    }
659
}
660
661
#[cfg(test)]
662
mod test {
663
    use super::*;
664
    use std::io;
665
666
    #[test]
667
    fn frames_exceeding_logical_screen_size() {
668
        // This is a gif with 10x10 logical screen, but a 16x16 frame + 6px offset inside.
669
        let data = vec![
670
            0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0A, 0x00, 0x0A, 0x00, 0xF0, 0x00, 0x00, 0x00,
671
            0x00, 0x00, 0x0E, 0xFF, 0x1F, 0x21, 0xF9, 0x04, 0x09, 0x64, 0x00, 0x00, 0x00, 0x2C,
672
            0x06, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x23, 0x84, 0x8F, 0xA9,
673
            0xBB, 0xE1, 0xE8, 0x42, 0x8A, 0x0F, 0x50, 0x79, 0xAE, 0xD1, 0xF9, 0x7A, 0xE8, 0x71,
674
            0x5B, 0x48, 0x81, 0x64, 0xD5, 0x91, 0xCA, 0x89, 0x4D, 0x21, 0x63, 0x89, 0x4C, 0x09,
675
            0x77, 0xF5, 0x6D, 0x14, 0x00, 0x3B,
676
        ];
677
678
        let mut decoder = GifDecoder::new(io::Cursor::new(data)).unwrap();
679
        let layout = decoder.prepare_image().unwrap();
680
681
        let mut buf = vec![0u8; layout.total_bytes() as usize];
682
        assert!(decoder.read_image(&mut buf).is_ok());
683
    }
684
}