Coverage Report

Created: 2025-10-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/gif-0.13.3/src/common.rs
Line
Count
Source
1
use std::borrow::Cow;
2
#[cfg(feature = "color_quant")]
3
use std::collections::{HashMap, HashSet};
4
5
/// Disposal method
6
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
7
#[repr(u8)]
8
pub enum DisposalMethod {
9
    /// `StreamingDecoder` is not required to take any action.
10
    Any = 0,
11
    /// Do not dispose.
12
    Keep = 1,
13
    /// Restore to background color.
14
    Background = 2,
15
    /// Restore to previous.
16
    Previous = 3,
17
}
18
19
impl DisposalMethod {
20
    /// Converts `u8` to `Option<Self>`
21
    #[must_use]
22
970
    pub const fn from_u8(n: u8) -> Option<Self> {
23
970
        match n {
24
741
            0 => Some(Self::Any),
25
92
            1 => Some(Self::Keep),
26
11
            2 => Some(Self::Background),
27
4
            3 => Some(Self::Previous),
28
122
            _ => None,
29
        }
30
970
    }
31
}
32
33
/// Known GIF block labels.
34
///
35
/// Note that the block uniquely specifies the layout of bytes that follow and how they are
36
/// framed. For example, the header always has a fixed length but is followed by a variable amount
37
/// of additional data. An image descriptor may be followed by a local color table depending on
38
/// information read in it. Therefore, it doesn't make sense to continue parsing after encountering
39
/// an unknown block as the semantics of following bytes are unclear.
40
///
41
/// The extension block provides a common framing for an arbitrary amount of application specific
42
/// data which may be ignored.
43
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
44
#[repr(u8)]
45
pub enum Block {
46
    /// Image block.
47
    Image = 0x2C,
48
    /// Extension block.
49
    Extension = 0x21,
50
    /// Image trailer.
51
    Trailer = 0x3B,
52
}
53
54
impl Block {
55
    /// Converts `u8` to `Option<Self>`
56
    #[must_use]
57
4.80k
    pub const fn from_u8(n: u8) -> Option<Self> {
58
4.80k
        match n {
59
1.52k
            0x2C => Some(Self::Image),
60
3.22k
            0x21 => Some(Self::Extension),
61
6
            0x3B => Some(Self::Trailer),
62
59
            _ => None,
63
        }
64
4.80k
    }
65
}
66
67
/// A newtype wrapper around an arbitrary extension ID.
68
///
69
/// An extension is some amount of byte data organized in sub-blocks so that one can skip over it
70
/// without knowing the semantics. Though technically you likely want to use a `Application`
71
/// extension, the library tries to stay flexible here.
72
///
73
/// This allows us to customize the set of impls compared to a raw `u8`. It also clarifies the
74
/// intent and gives some inherent methods for interoperability with known extension types.
75
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
76
pub struct AnyExtension(pub u8);
77
78
/// Known GIF extension labels.
79
///
80
/// These are extensions which may be interpreted by the library and to which a specification with
81
/// the internal data layout is known.
82
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
83
#[repr(u8)]
84
pub enum Extension {
85
    /// Plain Text extension.
86
    ///
87
    /// This instructs the decoder to render a text as characters in a grid of cells, in a
88
    /// mono-spaced font of its choosing. This is seldom actually implemented and ignored by
89
    /// ImageMagick. The color is always taken from the global table which further complicates any
90
    /// use. No real information on the frame sequencing of this block is available in the
91
    /// standard.
92
    Text = 0x01,
93
    /// Control extension.
94
    Control = 0xF9,
95
    /// Comment extension.
96
    Comment = 0xFE,
97
    /// Application extension.
98
    ///
99
    /// See [ImageMagick] for an idea of commonly recognized extensions.
100
    ///
101
    /// [ImageMagick]: https://github.com/ImageMagick/ImageMagick/blob/b0b58c6303195928060f55f9c3ca8233ab7f7733/coders/gif.c#L1128
102
    Application = 0xFF,
103
}
104
105
impl AnyExtension {
106
    /// Decode the label as a known extension.
107
    #[must_use]
108
6.25k
    pub const fn into_known(self) -> Option<Extension> {
109
6.25k
        Extension::from_u8(self.0)
110
6.25k
    }
111
}
112
113
impl From<Extension> for AnyExtension {
114
0
    fn from(ext: Extension) -> Self {
115
0
        Self(ext as u8)
116
0
    }
117
}
118
119
impl Extension {
120
    /// Converts `u8` to a `Extension` if it is known.
121
    #[must_use]
122
6.25k
    pub const fn from_u8(n: u8) -> Option<Self> {
123
6.25k
        match n {
124
457
            0x01 => Some(Self::Text),
125
2.00k
            0xF9 => Some(Self::Control),
126
1.04k
            0xFE => Some(Self::Comment),
127
2.75k
            0xFF => Some(Self::Application),
128
2
            _ => None,
129
        }
130
6.25k
    }
131
}
132
133
/// A GIF frame
134
#[derive(Debug, Clone)]
135
pub struct Frame<'a> {
136
    /// Frame delay in units of 10 ms.
137
    pub delay: u16,
138
    /// Disposal method.
139
    pub dispose: DisposalMethod,
140
    /// Transparent index (if available).
141
    pub transparent: Option<u8>,
142
    /// True if the frame needs user input to be displayed.
143
    pub needs_user_input: bool,
144
    /// Offset from the top border of the canvas.
145
    pub top: u16,
146
    /// Offset from the left border of the canvas.
147
    pub left: u16,
148
    /// Width of the frame.
149
    pub width: u16,
150
    /// Height of the frame.
151
    pub height: u16,
152
    /// True if the image is interlaced.
153
    pub interlaced: bool,
154
    /// Frame local color palette if available.
155
    pub palette: Option<Vec<u8>>,
156
    /// Buffer containing the image data.
157
    /// Only indices unless configured differently.
158
    pub buffer: Cow<'a, [u8]>,
159
}
160
161
impl Default for Frame<'_> {
162
3.54k
    fn default() -> Self {
163
3.54k
        Frame {
164
3.54k
            delay: 0,
165
3.54k
            dispose: DisposalMethod::Keep,
166
3.54k
            transparent: None,
167
3.54k
            needs_user_input: false,
168
3.54k
            top: 0,
169
3.54k
            left: 0,
170
3.54k
            width: 0,
171
3.54k
            height: 0,
172
3.54k
            interlaced: false,
173
3.54k
            palette: None,
174
3.54k
            buffer: Cow::Borrowed(&[]),
175
3.54k
        }
176
3.54k
    }
177
}
178
179
impl Frame<'static> {
180
    /// Creates a frame from pixels in RGBA format.
181
    ///
182
    /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit
183
    /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque
184
    /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be
185
    /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes.
186
    ///
187
    /// *Note: This method is not optimized for speed.*
188
    ///
189
    /// # Panics:
190
    /// *   If the length of pixels does not equal `width * height * 4`.
191
    #[cfg(feature = "color_quant")]
192
    #[track_caller]
193
0
    pub fn from_rgba(width: u16, height: u16, pixels: &mut [u8]) -> Self {
194
0
        Frame::from_rgba_speed(width, height, pixels, 1)
195
0
    }
196
197
    /// Creates a frame from pixels in RGBA format.
198
    ///
199
    /// `speed` is a value in the range [1, 30].
200
    /// The higher the value the faster it runs at the cost of image quality.
201
    /// A `speed` of 10 is a good compromise between speed and quality.
202
    ///
203
    /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit
204
    /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque
205
    /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be
206
    /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes.
207
    ///
208
    /// # Panics:
209
    /// *   If the length of pixels does not equal `width * height * 4`.
210
    /// *   If `speed < 1` or `speed > 30`
211
    #[cfg(feature = "color_quant")]
212
    #[track_caller]
213
0
    pub fn from_rgba_speed(width: u16, height: u16, pixels: &mut [u8], speed: i32) -> Self {
214
0
        assert_eq!(width as usize * height as usize * 4, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame");
215
0
        assert!(speed >= 1 && speed <= 30, "speed needs to be in the range [1, 30]");
216
0
        let mut transparent = None;
217
0
        for pix in pixels.chunks_exact_mut(4) {
218
0
            if pix[3] != 0 {
219
0
                pix[3] = 0xFF;
220
0
            } else {
221
0
                transparent = Some([pix[0], pix[1], pix[2], pix[3]]);
222
0
            }
223
        }
224
225
        // Attempt to build a palette of all colors. If we go over 256 colors,
226
        // switch to the NeuQuant algorithm.
227
0
        let mut colors: HashSet<(u8, u8, u8, u8)> = HashSet::new();
228
0
        for pixel in pixels.chunks_exact(4) {
229
0
            if colors.insert((pixel[0], pixel[1], pixel[2], pixel[3])) && colors.len() > 256 {
230
                // > 256 colours, let's use NeuQuant.
231
0
                let nq = color_quant::NeuQuant::new(speed, 256, pixels);
232
233
                return Frame {
234
0
                    width,
235
0
                    height,
236
0
                    buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| nq.index_of(pix) as u8).collect()),
237
0
                    palette: Some(nq.color_map_rgb()),
238
0
                    transparent: transparent.map(|t| nq.index_of(&t) as u8),
239
0
                    ..Frame::default()
240
                };
241
0
            }
242
        }
243
244
        // Palette size <= 256 elements, we can build an exact palette.
245
0
        let mut colors_vec: Vec<(u8, u8, u8, u8)> = colors.into_iter().collect();
246
0
        colors_vec.sort_unstable();
247
0
        let palette = colors_vec.iter().flat_map(|&(r, g, b, _a)| [r, g, b]).collect();
248
0
        let colors_lookup: HashMap<(u8, u8, u8, u8), u8> = colors_vec.into_iter().zip(0..=255).collect();
249
250
0
        let index_of = | pixel: &[u8] |
251
0
            colors_lookup.get(&(pixel[0], pixel[1], pixel[2], pixel[3])).copied().unwrap_or(0);
252
253
        Frame {
254
0
            width,
255
0
            height,
256
0
            buffer: Cow::Owned(pixels.chunks_exact(4).map(index_of).collect()),
257
0
            palette: Some(palette),
258
0
            transparent: transparent.map(|t| index_of(&t)),
259
0
            ..Frame::default()
260
        }
261
0
    }
262
263
    /// Creates a frame from a palette and indexed pixels.
264
    ///
265
    /// # Panics:
266
    /// *   If the length of pixels does not equal `width * height`.
267
    /// *   If the length of palette > `256 * 3`.
268
    #[track_caller]
269
0
    pub fn from_palette_pixels(width: u16, height: u16, pixels: impl Into<Vec<u8>>, palette: impl Into<Vec<u8>>, transparent: Option<u8>) -> Self {
270
0
        let pixels = pixels.into();
271
0
        let palette = palette.into();
272
0
        assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame");
273
0
        assert!(palette.len() <= 256*3, "Too many palette values to create a GIF Frame");
274
275
0
        Frame {
276
0
            width,
277
0
            height,
278
0
            buffer: Cow::Owned(pixels),
279
0
            palette: Some(palette),
280
0
            transparent,
281
0
            ..Frame::default()
282
0
        }
283
0
    }
284
285
    /// Creates a frame from indexed pixels in the global palette.
286
    ///
287
    /// # Panics:
288
    /// *   If the length of pixels does not equal `width * height`.
289
    #[track_caller]
290
0
    pub fn from_indexed_pixels(width: u16, height: u16, pixels: impl Into<Vec<u8>>, transparent: Option<u8>) -> Self {
291
0
        let pixels = pixels.into();
292
0
        assert_eq!(width as usize * height as usize, pixels.len(), "Too many or too little pixels for the given width and height to create a GIF Frame");
293
294
0
        Frame {
295
0
            width,
296
0
            height,
297
0
            buffer: Cow::Owned(pixels),
298
0
            palette: None,
299
0
            transparent,
300
0
            ..Frame::default()
301
0
        }
302
0
    }
303
304
    /// Creates a frame from pixels in RGB format.
305
    ///
306
    /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame.
307
    /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have
308
    /// independent palettes.
309
    ///
310
    /// *Note: This method is not optimized for speed.*
311
    ///
312
    /// # Panics:
313
    /// *   If the length of pixels does not equal `width * height * 3`.
314
    #[cfg(feature = "color_quant")]
315
    #[must_use]
316
    #[track_caller]
317
0
    pub fn from_rgb(width: u16, height: u16, pixels: &[u8]) -> Self {
318
0
        Frame::from_rgb_speed(width, height, pixels, 1)
319
0
    }
320
321
    /// Creates a frame from pixels in RGB format.
322
    ///
323
    /// `speed` is a value in the range [1, 30].
324
    ///
325
    /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame.
326
    /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have
327
    /// independent palettes.
328
    ///
329
    /// The higher the value the faster it runs at the cost of image quality.
330
    /// A `speed` of 10 is a good compromise between speed and quality.
331
    ///
332
    /// # Panics:
333
    /// *   If the length of pixels does not equal `width * height * 3`.
334
    /// *   If `speed < 1` or `speed > 30`
335
    #[cfg(feature = "color_quant")]
336
    #[must_use]
337
    #[track_caller]
338
0
    pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Self {
339
0
        assert_eq!(width as usize * height as usize * 3, pixels.len(), "Too much or too little pixel data for the given width and height to create a GIF Frame");
340
0
        let mut vec: Vec<u8> = Vec::new();
341
0
        vec.try_reserve_exact(pixels.len() + width as usize * height as usize).expect("OOM");
342
0
        for v in pixels.chunks_exact(3) {
343
0
            vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF]);
344
0
        }
345
0
        Frame::from_rgba_speed(width, height, &mut vec, speed)
346
0
    }
347
348
    /// Leaves empty buffer and empty palette behind
349
    #[inline]
350
1.46k
    pub(crate) fn take(&mut self) -> Self {
351
1.46k
        Frame {
352
1.46k
            delay: self.delay,
353
1.46k
            dispose: self.dispose,
354
1.46k
            transparent: self.transparent,
355
1.46k
            needs_user_input: self.needs_user_input,
356
1.46k
            top: self.top,
357
1.46k
            left: self.left,
358
1.46k
            width: self.width,
359
1.46k
            height: self.height,
360
1.46k
            interlaced: self.interlaced,
361
1.46k
            palette: std::mem::take(&mut self.palette),
362
1.46k
            buffer: std::mem::replace(&mut self.buffer, Cow::Borrowed(&[])),
363
1.46k
        }
364
1.46k
    }
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
<gif::common::Frame>::take
Line
Count
Source
350
1.46k
    pub(crate) fn take(&mut self) -> Self {
351
1.46k
        Frame {
352
1.46k
            delay: self.delay,
353
1.46k
            dispose: self.dispose,
354
1.46k
            transparent: self.transparent,
355
1.46k
            needs_user_input: self.needs_user_input,
356
1.46k
            top: self.top,
357
1.46k
            left: self.left,
358
1.46k
            width: self.width,
359
1.46k
            height: self.height,
360
1.46k
            interlaced: self.interlaced,
361
1.46k
            palette: std::mem::take(&mut self.palette),
362
1.46k
            buffer: std::mem::replace(&mut self.buffer, Cow::Borrowed(&[])),
363
1.46k
        }
364
1.46k
    }
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
Unexecuted instantiation: <gif::common::Frame>::take
365
}
366
367
#[test]
368
#[cfg(feature = "color_quant")]
369
// Creating the `colors_lookup` hashmap in Frame::from_rgba_speed panics due to
370
// overflow while bypassing NeuQuant and zipping a RangeFrom with 256 colors.
371
// Changing .zip(0_u8..) to .zip(0_u8..=255) fixes this issue.
372
fn rgba_speed_avoid_panic_256_colors() {
373
    let side = 16;
374
    let pixel_data: Vec<u8> = (0..=255).flat_map(|a| [a, a, a]).collect();
375
    let _ = Frame::from_rgb(side, side, &pixel_data);
376
}