diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/gif/src/common.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/gif/src/common.rs')
-rw-r--r-- | vendor/gif/src/common.rs | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/vendor/gif/src/common.rs b/vendor/gif/src/common.rs new file mode 100644 index 0000000..20ba99b --- /dev/null +++ b/vendor/gif/src/common.rs @@ -0,0 +1,346 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::collections::HashSet; + +/// Disposal method +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum DisposalMethod { + /// StreamingDecoder is not required to take any action. + Any = 0, + /// Do not dispose. + Keep = 1, + /// Restore to background color. + Background = 2, + /// Restore to previous. + Previous = 3, +} + +impl DisposalMethod { + /// Converts `u8` to `Option<Self>` + pub fn from_u8(n: u8) -> Option<DisposalMethod> { + match n { + 0 => Some(DisposalMethod::Any), + 1 => Some(DisposalMethod::Keep), + 2 => Some(DisposalMethod::Background), + 3 => Some(DisposalMethod::Previous), + _ => None + } + } +} + +/// Known GIF block labels. +/// +/// Note that the block uniquely specifies the layout of bytes that follow and how they are +/// framed. For example, the header always has a fixed length but is followed by a variable amount +/// of additional data. An image descriptor may be followed by a local color table depending on +/// information read in it. Therefore, it doesn't make sense to continue parsing after encountering +/// an unknown block as the semantics of following bytes are unclear. +/// +/// The extension block provides a common framing for an arbitrary amount of application specific +/// data which may be ignored. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Block { + /// Image block. + Image = 0x2C, + /// Extension block. + Extension = 0x21, + /// Image trailer. + Trailer = 0x3B, +} + +impl Block { + /// Converts `u8` to `Option<Self>` + pub fn from_u8(n: u8) -> Option<Block> { + match n { + 0x2C => Some(Block::Image), + 0x21 => Some(Block::Extension), + 0x3B => Some(Block::Trailer), + _ => None + } + } +} + +/// A newtype wrapper around an arbitrary extension ID. +/// +/// An extension is some amount of byte data organized in sub-blocks so that one can skip over it +/// without knowing the semantics. Though technically you likely want to use a `Application` +/// extension, the library tries to stay flexible here. +/// +/// This allows us to customize the set of impls compared to a raw `u8`. It also clarifies the +/// intent and gives some inherent methods for interoperability with known extension types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct AnyExtension(pub u8); + +/// Known GIF extension labels. +/// +/// These are extensions which may be interpreted by the library and to which a specification with +/// the internal data layout is known. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Extension { + /// Plain Text extension. + /// + /// This instructs the decoder to render a text as characters in a grid of cells, in a + /// mono-spaced font of its choosing. This is seldom actually implemented and ignored by + /// ImageMagick. The color is always taken from the global table which further complicates any + /// use. No real information on the frame sequencing of this block is available in the + /// standard. + Text = 0x01, + /// Control extension. + Control = 0xF9, + /// Comment extension. + Comment = 0xFE, + /// Application extension. + /// + /// See [ImageMagick] for an idea of commonly recognized extensions. + /// + /// [ImageMagick]: https://github.com/ImageMagick/ImageMagick/blob/b0b58c6303195928060f55f9c3ca8233ab7f7733/coders/gif.c#L1128 + Application = 0xFF, +} + +impl AnyExtension { + /// Decode the label as a known extension. + pub fn into_known(self) -> Option<Extension> { + Extension::from_u8(self.0) + } +} + +impl From<Extension> for AnyExtension { + fn from(ext: Extension) -> Self { + AnyExtension(ext as u8) + } +} + +impl Extension { + /// Converts `u8` to a `Extension` if it is known. + pub fn from_u8(n: u8) -> Option<Extension> { + match n { + 0x01 => Some(Extension::Text), + 0xF9 => Some(Extension::Control), + 0xFE => Some(Extension::Comment), + 0xFF => Some(Extension::Application), + _ => None + } + } +} + +/// A GIF frame +#[derive(Debug, Clone)] +pub struct Frame<'a> { + /// Frame delay in units of 10 ms. + pub delay: u16, + /// Disposal method. + pub dispose: DisposalMethod, + /// Transparent index (if available). + pub transparent: Option<u8>, + /// True if the frame needs user input to be displayed. + pub needs_user_input: bool, + /// Offset from the top border of the canvas. + pub top: u16, + /// Offset from the left border of the canvas. + pub left: u16, + /// Width of the frame. + pub width: u16, + /// Height of the frame. + pub height: u16, + /// True if the image is interlaced. + pub interlaced: bool, + /// Frame local color palette if available. + pub palette: Option<Vec<u8>>, + /// Buffer containing the image data. + /// Only indices unless configured differently. + pub buffer: Cow<'a, [u8]> +} + +impl<'a> Default for Frame<'a> { + fn default() -> Frame<'a> { + Frame { + delay: 0, + dispose: DisposalMethod::Keep, + transparent: None, + needs_user_input: false, + top: 0, + left: 0, + width: 0, + height: 0, + interlaced: false, + palette: None, + buffer: Cow::Borrowed(&[]) + } + } +} + +impl Frame<'static> { + /// Creates a frame from pixels in RGBA format. + /// + /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit + /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque + /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be + /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes. + /// + /// *Note: This method is not optimized for speed.* + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 4`. + #[cfg(feature = "color_quant")] + pub fn from_rgba(width: u16, height: u16, pixels: &mut [u8]) -> Frame<'static> { + Frame::from_rgba_speed(width, height, pixels, 1) + } + + /// Creates a frame from pixels in RGBA format. + /// + /// `speed` is a value in the range [1, 30]. + /// The higher the value the faster it runs at the cost of image quality. + /// A `speed` of 10 is a good compromise between speed and quality. + /// + /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit + /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque + /// pixel. Additionally, only 256 colors can appear in a single frame. The palette will be + /// reduced by the NeuQuant algorithm if necessary. Different frames have independent palettes. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 4`. + /// * If `speed < 1` or `speed > 30` + #[cfg(feature = "color_quant")] + pub fn from_rgba_speed(width: u16, height: u16, pixels: &mut [u8], speed: i32) -> Frame<'static> { + 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"); + assert!(speed >= 1 && speed <= 30, "speed needs to be in the range [1, 30]"); + let mut transparent = None; + for pix in pixels.chunks_exact_mut(4) { + if pix[3] != 0 { + pix[3] = 0xFF; + } else { + transparent = Some([pix[0], pix[1], pix[2], pix[3]]) + } + } + + // Attempt to build a palette of all colors. If we go over 256 colors, + // switch to the NeuQuant algorithm. + let mut colors: HashSet<(u8, u8, u8, u8)> = HashSet::new(); + for pixel in pixels.chunks_exact(4) { + if colors.insert((pixel[0], pixel[1], pixel[2], pixel[3])) && colors.len() > 256 { + // > 256 colours, let's use NeuQuant. + let nq = color_quant::NeuQuant::new(speed, 256, pixels); + + return Frame { + width, + height, + buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| nq.index_of(pix) as u8).collect()), + palette: Some(nq.color_map_rgb()), + transparent: transparent.map(|t| nq.index_of(&t) as u8), + ..Frame::default() + }; + } + } + + // Palette size <= 256 elements, we can build an exact palette. + let mut colors_vec: Vec<(u8, u8, u8, u8)> = colors.into_iter().collect(); + colors_vec.sort(); + let palette = colors_vec.iter().flat_map(|&(r, g, b, _a)| [r, g, b]).collect(); + let colors_lookup: HashMap<(u8, u8, u8, u8), u8> = colors_vec.into_iter().zip(0..=255).collect(); + + let index_of = | pixel: &[u8] | + *colors_lookup.get(&(pixel[0], pixel[1], pixel[2], pixel[3])).unwrap(); + + return Frame { + width, + height, + buffer: Cow::Owned(pixels.chunks_exact(4).map(|pix| index_of(pix)).collect()), + palette: Some(palette), + transparent: transparent.map(|t| index_of(&t)), + ..Frame::default() + } + } + + /// Creates a frame from a palette and indexed pixels. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height`. + /// * If the length of palette > `256 * 3`. + pub fn from_palette_pixels(width: u16, height: u16, pixels: &[u8], palette: &[u8], transparent: Option<u8>) -> Frame<'static> { + 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"); + assert!(palette.len() <= 256*3, "Too many palette values to create a GIF Frame"); + + Frame { + width, + height, + buffer: Cow::Owned(pixels.to_vec()), + palette: Some(palette.to_vec()), + transparent, + ..Frame::default() + } + } + + /// Creates a frame from indexed pixels in the global palette. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height`. + pub fn from_indexed_pixels(width: u16, height: u16, pixels: &[u8], transparent: Option<u8>) -> Frame<'static> { + 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"); + + Frame { + width, + height, + buffer: Cow::Owned(pixels.to_vec()), + palette: None, + transparent, + ..Frame::default() + } + } + + /// Creates a frame from pixels in RGB format. + /// + /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame. + /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have + /// independent palettes. + /// + /// *Note: This method is not optimized for speed.* + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 3`. + #[cfg(feature = "color_quant")] + pub fn from_rgb(width: u16, height: u16, pixels: &[u8]) -> Frame<'static> { + Frame::from_rgb_speed(width, height, pixels, 1) + } + + /// Creates a frame from pixels in RGB format. + /// + /// `speed` is a value in the range [1, 30]. + /// + /// This is a lossy method. In the `gif` format only 256 colors can appear in a single frame. + /// The palette will be reduced by the NeuQuant algorithm if necessary. Different frames have + /// independent palettes. + /// + /// The higher the value the faster it runs at the cost of image quality. + /// A `speed` of 10 is a good compromise between speed and quality. + /// + /// # Panics: + /// * If the length of pixels does not equal `width * height * 3`. + /// * If `speed < 1` or `speed > 30` + #[cfg(feature = "color_quant")] + pub fn from_rgb_speed(width: u16, height: u16, pixels: &[u8], speed: i32) -> Frame<'static> { + 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"); + let mut vec: Vec<u8> = Vec::with_capacity(pixels.len() + width as usize * height as usize); + for v in pixels.chunks_exact(3) { + vec.extend_from_slice(&[v[0], v[1], v[2], 0xFF]) + } + Frame::from_rgba_speed(width, height, &mut vec, speed) + } + + pub(crate) fn required_bytes(&self) -> usize { + usize::from(self.width) * usize::from(self.height) + } +} + +#[test] +#[cfg(feature = "color_quant")] +// Creating the `colors_lookup` hashmap in Frame::from_rgba_speed panics due to +// overflow while bypassing NeuQuant and zipping a RangeFrom with 256 colors. +// Changing .zip(0_u8..) to .zip(0_u8..=255) fixes this issue. +fn rgba_speed_avoid_panic_256_colors() { + let side = 16; + let pixel_data: Vec<u8> = (0..=255).map(|a| vec![a, a, a]).flatten().collect(); + Frame::from_rgb(side, side, &pixel_data); +} |