/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 | | } |