1use std::borrow::Cow;
2#[cfg(feature = "color_quant")]
3use std::collections::{HashMap, HashSet};
4
5/// Disposal method
6#[derive(Debug, Copy, Clone, PartialEq, Eq)]
7#[repr(u8)]
8pub 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
19impl DisposalMethod {
20 /// Converts `u8` to `Option<Self>`
21 #[must_use]
22 pub fn from_u8(n: u8) -> Option<DisposalMethod> {
23 match n {
24 0 => Some(DisposalMethod::Any),
25 1 => Some(DisposalMethod::Keep),
26 2 => Some(DisposalMethod::Background),
27 3 => Some(DisposalMethod::Previous),
28 _ => None,
29 }
30 }
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)]
45pub enum Block {
46 /// Image block.
47 Image = 0x2C,
48 /// Extension block.
49 Extension = 0x21,
50 /// Image trailer.
51 Trailer = 0x3B,
52}
53
54impl Block {
55 /// Converts `u8` to `Option<Self>`
56 #[must_use]
57 pub fn from_u8(n: u8) -> Option<Block> {
58 match n {
59 0x2C => Some(Block::Image),
60 0x21 => Some(Block::Extension),
61 0x3B => Some(Block::Trailer),
62 _ => None,
63 }
64 }
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)]
76pub 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)]
84pub 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
105impl AnyExtension {
106 /// Decode the label as a known extension.
107 #[must_use]
108 pub fn into_known(self) -> Option<Extension> {
109 Extension::from_u8(self.0)
110 }
111}
112
113impl From<Extension> for AnyExtension {
114 fn from(ext: Extension) -> Self {
115 AnyExtension(ext as u8)
116 }
117}
118
119impl Extension {
120 /// Converts `u8` to a `Extension` if it is known.
121 #[must_use]
122 pub fn from_u8(n: u8) -> Option<Extension> {
123 match n {
124 0x01 => Some(Extension::Text),
125 0xF9 => Some(Extension::Control),
126 0xFE => Some(Extension::Comment),
127 0xFF => Some(Extension::Application),
128 _ => None,
129 }
130 }
131}
132
133/// A GIF frame
134#[derive(Debug, Clone)]
135pub 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
161impl<'a> Default for Frame<'a> {
162 fn default() -> Frame<'a> {
163 Frame {
164 delay: 0,
165 dispose: DisposalMethod::Keep,
166 transparent: None,
167 needs_user_input: false,
168 top: 0,
169 left: 0,
170 width: 0,
171 height: 0,
172 interlaced: false,
173 palette: None,
174 buffer: Cow::Borrowed(&[]),
175 }
176 }
177}
178
179impl 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 pub fn from_rgba(width: u16, height: u16, pixels: &mut [u8]) -> Frame<'static> {
193 Frame::from_rgba_speed(width, height, pixels, 1)
194 }
195
196 /// Creates a frame from pixels in RGBA format.
197 ///
198 /// `speed` is a value in the range [1, 30].
199 /// The higher the value the faster it runs at the cost of image quality.
200 /// A `speed` of 10 is a good compromise between speed and quality.
201 ///
202 /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit
203 /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque
204 /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be
205 /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes.
206 ///
207 /// # Panics:
208 /// * If the length of pixels does not equal `width * height * 4`.
209 /// * If `speed < 1` or `speed > 30`
210 #[cfg(feature = "color_quant")]
211 pub fn from_rgba_speed(width: u16, height: u16, pixels: &mut [u8], speed: i32) -> Frame<'static> {
212 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");
213 assert!(speed >= 1 && speed <= 30, "speed needs to be in the range [1, 30]");
214 let mut transparent = None;
215 for pix in pixels.chunks_exact_mut(4) {
216 if pix[3] != 0 {
217 pix[3] = 0xFF;
218 } else {
219 transparent = Some([pix[0], pix[1], pix[2], pix[3]]);
220 }
221 }
222
223 // Attempt to build a palette of all colors. If we go over 256 colors,
224 // switch to the NeuQuant algorithm.
225 let mut colors: HashSet<(u8, u8, u8, u8)> = HashSet::new();
226 for pixel in pixels.chunks_exact(4) {
227 if colors.insert((pixel[0], pixel[1], pixel[2], pixel[3])) && colors.len() > 256 {
228 // > 256 colours, let's use NeuQuant.
229 let nq = color_quant::NeuQuant::new(speed, 256, pixels);
230
231 return Frame {
232 width,
233 height,
234 buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| nq.index_of(pix) as u8).collect()),
235 palette: Some(nq.color_map_rgb()),
236 transparent: transparent.map(|t| nq.index_of(&t) as u8),
237 ..Frame::default()
238 };
239 }
240 }
241
242 // Palette size <= 256 elements, we can build an exact palette.
243 let mut colors_vec: Vec<(u8, u8, u8, u8)> = colors.into_iter().collect();
244 colors_vec.sort_unstable();
245 let palette = colors_vec.iter().flat_map(|&(r, g, b, _a)| [r, g, b]).collect();
246 let colors_lookup: HashMap<(u8, u8, u8, u8), u8> = colors_vec.into_iter().zip(0..=255).collect();
247
248 let index_of = | pixel: &[u8] |
249 colors_lookup.get(&(pixel[0], pixel[1], pixel[2], pixel[3])).copied().unwrap_or(0);
250
251 return Frame {
252 width,
253 height,
254 buffer: Cow::Owned(pixels.chunks_exact(4).map(index_of).collect()),
255 palette: Some(palette),
256 transparent: transparent.map(|t| index_of(&t)),
257 ..Frame::default()
258 };
259 }
260
261 /// Creates a frame from a palette and indexed pixels.
262 ///
263 /// # Panics:
264 /// * If the length of pixels does not equal `width * height`.
265 /// * If the length of palette > `256 * 3`.
266 pub fn from_palette_pixels(width: u16, height: u16, pixels: impl Into<Vec<u8>>, palette: impl Into<Vec<u8>>, transparent: Option<u8>) -> Frame<'static> {
267 let pixels = pixels.into();
268 let palette = palette.into();
269 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");
270 assert!(palette.len() <= 256*3, "Too many palette values to create a GIF Frame");
271
272 Frame {
273 width,
274 height,
275 buffer: Cow::Owned(pixels),
276 palette: Some(palette),
277 transparent,
278 ..Frame::default()
279 }
280 }
281
282 /// Creates a frame from indexed pixels in the global palette.
283 ///
284 /// # Panics:
285 /// * If the length of pixels does not equal `width * height`.
286 pub fn from_indexed_pixels(width: u16, height: u16, pixels: impl Into<Vec<u8>>, transparent: Option<u8>) -> Frame<'static> {
287 let pixels = pixels.into();
288 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");
289
290 Frame {
291 width,
292 height,
293 buffer: Cow::Owned(pixels.clone()),
294 palette: None,
295 transparent,
296 ..Frame::default()
297 }
298 }
299
300 /// Creates a frame from pixels in RGB format.
301 ///
302 /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame.
303 /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have
304 /// independent palettes.
305 ///
306 /// *Note: This method is not optimized for speed.*
307 ///
308 /// # Panics:
309 /// * If the length of pixels does not equal `width * height * 3`.
310 #[cfg(feature = "color_quant")]
311 #[must_use]
312 pub fn from_rgb(width: u16, height: u16, pixels: &[u8]) -> Frame<'static> {
313 Frame::from_rgb_speed(width, height, pixels, 1)
314 }
315
316 /// Creates a frame from pixels in RGB format.
317 ///
318 /// `speed` is a value in the range [1, 30].
319 ///
320 /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame.
321 /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have
322 /// independent palettes.
323 ///
324 /// The higher the value the faster it runs at the cost of image quality.
325 /// A `speed` of 10 is a good compromise between speed and quality.
326 ///
327 /// # Panics:
328 /// * If the length of pixels does not equal `width * height * 3`.
329 /// * If `speed < 1` or `speed > 30`
330 #[cfg(feature = "color_quant")]
331 #[must_use]
332 pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Frame<'static> {
333 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");
334 let mut vec: Vec<u8> = Vec::new();
335 vec.try_reserve_exact(pixels.len() + width as usize * height as usize).expect("OOM");
336 for v in pixels.chunks_exact(3) {
337 vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF]);
338 }
339 Frame::from_rgba_speed(width, height, &mut vec, speed)
340 }
341
342 /// Leaves empty buffer and empty palette behind
343 #[inline]
344 pub(crate) fn take(&mut self) -> Self {
345 Frame {
346 delay: self.delay,
347 dispose: self.dispose,
348 transparent: self.transparent,
349 needs_user_input: self.needs_user_input,
350 top: self.top,
351 left: self.left,
352 width: self.width,
353 height: self.height,
354 interlaced: self.interlaced,
355 palette: std::mem::take(&mut self.palette),
356 buffer: std::mem::replace(&mut self.buffer, Cow::Borrowed(&[])),
357 }
358 }
359}
360
361#[test]
362#[cfg(feature = "color_quant")]
363// Creating the `colors_lookup` hashmap in Frame::from_rgba_speed panics due to
364// overflow while bypassing NeuQuant and zipping a RangeFrom with 256 colors.
365// Changing .zip(0_u8..) to .zip(0_u8..=255) fixes this issue.
366fn rgba_speed_avoid_panic_256_colors() {
367 let side: u16 = 16;
368 let pixel_data: Vec<u8> = (0..=255).map(|a: u8| vec![a, a, a]).flatten().collect();
369 let _ = Frame::from_rgb(width:side, height:side, &pixel_data);
370}
371