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/encoder.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/encoder.rs')
-rw-r--r-- | vendor/gif/src/encoder.rs | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/vendor/gif/src/encoder.rs b/vendor/gif/src/encoder.rs new file mode 100644 index 0000000..693a8cb --- /dev/null +++ b/vendor/gif/src/encoder.rs @@ -0,0 +1,434 @@ +//! # Minimal gif encoder +use std::io; +use std::io::prelude::*; +use std::fmt; +use std::error; +use std::borrow::Cow; + +use weezl::{BitOrder, encode::Encoder as LzwEncoder}; + +use crate::traits::{WriteBytesExt}; +use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame}; + +#[derive(Debug)] +enum FormatErrorKind { + /// The image has too many colors. + TooManyColors, + /// The image has no color palette which is required. + MissingColorPalette, +} + +/// The image has incorrect properties, making it impossible to encode as a gif. +#[derive(Debug)] +pub struct EncodingFormatError { + kind: FormatErrorKind +} + +impl error::Error for EncodingFormatError {} +impl fmt::Display for EncodingFormatError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + FormatErrorKind::TooManyColors => write!(fmt, "the image has too many colors"), + FormatErrorKind::MissingColorPalette => write!(fmt, "the GIF format requires a color palette but none was given") + } + } +} + +impl From<FormatErrorKind> for EncodingFormatError { + fn from(kind: FormatErrorKind) -> Self { + EncodingFormatError { kind } + } +} + +#[derive(Debug)] +/// Encoding error. +pub enum EncodingError { + /// Returned if the to image is not encodable as a gif. + Format(EncodingFormatError), + /// Wraps `std::io::Error`. + Io(io::Error), +} + +impl fmt::Display for EncodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + EncodingError::Io(err) => err.fmt(fmt), + EncodingError::Format(err) => err.fmt(fmt), + } + } +} + +impl error::Error for EncodingError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + EncodingError::Io(err) => Some(err), + EncodingError::Format(err) => Some(err), + } + } +} + +impl From<io::Error> for EncodingError { + fn from(err: io::Error) -> Self { + EncodingError::Io(err) + } +} + +impl From<EncodingFormatError> for EncodingError { + fn from(err: EncodingFormatError) -> Self { + EncodingError::Format(err) + } +} + +impl From<FormatErrorKind> for EncodingError { + fn from(kind: FormatErrorKind) -> Self { + EncodingError::Format(kind.into()) + } +} + + +/// Number of repetitions +#[derive(Copy, Clone, Debug)] +pub enum Repeat { + /// Finite number of repetitions + Finite(u16), + /// Infinite number of repetitions + Infinite +} + +/// Extension data. +pub enum ExtensionData { + /// Control extension. Use `ExtensionData::new_control_ext` to construct. + Control { + /// Flags. + flags: u8, + /// Frame delay. + delay: u16, + /// Transparent index. + trns: u8 + }, + /// Sets the number of repetitions + Repetitions(Repeat) +} + +impl ExtensionData { + /// Constructor for control extension data. + /// + /// `delay` is given in units of 10 ms. + pub fn new_control_ext(delay: u16, dispose: DisposalMethod, + needs_user_input: bool, trns: Option<u8>) -> ExtensionData { + let mut flags = 0; + let trns = match trns { + Some(trns) => { + flags |= 1; + trns as u8 + }, + None => 0 + }; + flags |= (needs_user_input as u8) << 1; + flags |= (dispose as u8) << 2; + ExtensionData::Control { + flags: flags, + delay: delay, + trns: trns + } + } +} + +impl<W: Write> Encoder<W> { + /// Creates a new encoder. + /// + /// `global_palette` gives the global color palette in the format `[r, g, b, ...]`, + /// if no global palette shall be used an empty slice may be supplied. + pub fn new(w: W, width: u16, height: u16, global_palette: &[u8]) -> Result<Self, EncodingError> { + let buffer_size = (width as usize) * (height as usize); + Encoder { + w: Some(w), + global_palette: false, + width: width, + height: height, + buffer: Vec::with_capacity(buffer_size) + }.write_global_palette(global_palette) + } + + /// Write an extension block that signals a repeat behaviour. + pub fn set_repeat(&mut self, repeat: Repeat) -> Result<(), EncodingError> { + self.write_extension(ExtensionData::Repetitions(repeat)) + } + + /// Writes the global color palette. + pub fn write_global_palette(mut self, palette: &[u8]) -> Result<Self, EncodingError> { + self.global_palette = true; + let mut flags = 0; + flags |= 0b1000_0000; + let num_colors = palette.len() / 3; + if num_colors > 256 { + return Err(EncodingError::from(FormatErrorKind::TooManyColors)); + } + // Size of global color table. + flags |= flag_size(num_colors); + // Color resolution .. FIXME. This is mostly ignored (by ImageMagick at least) but hey, we + // should use some sensible value here or even allow configuring it? + flags |= flag_size(num_colors) << 4; // wtf flag + self.write_screen_desc(flags)?; + self.write_color_table(palette)?; + Ok(self) + } + + /// Writes a frame to the image. + /// + /// Note: This function also writes a control extension if necessary. + pub fn write_frame(&mut self, frame: &Frame) -> Result<(), EncodingError> { + self.write_frame_header(frame)?; + self.write_image_block(&frame.buffer) + } + + fn write_frame_header(&mut self, frame: &Frame) -> Result<(), EncodingError> { + // TODO commented off to pass test in lib.rs + //if frame.delay > 0 || frame.transparent.is_some() { + self.write_extension(ExtensionData::new_control_ext( + frame.delay, + frame.dispose, + frame.needs_user_input, + frame.transparent + + ))?; + //} + let writer = self.w.as_mut().unwrap(); + writer.write_le(Block::Image as u8)?; + writer.write_le(frame.left)?; + writer.write_le(frame.top)?; + writer.write_le(frame.width)?; + writer.write_le(frame.height)?; + let mut flags = 0; + if frame.interlaced { + flags |= 0b0100_0000; + } + match frame.palette { + Some(ref palette) => { + flags |= 0b1000_0000; + let num_colors = palette.len() / 3; + if num_colors > 256 { + return Err(EncodingError::from(FormatErrorKind::TooManyColors)); + } + flags |= flag_size(num_colors); + writer.write_le(flags)?; + self.write_color_table(palette) + }, + None => if !self.global_palette { + Err(EncodingError::from(FormatErrorKind::MissingColorPalette)) + } else { + writer.write_le(flags).map_err(Into::into) + } + } + } + + fn write_image_block(&mut self, data: &[u8]) -> Result<(), EncodingError> { + self.buffer.clear(); + lzw_encode(data, &mut self.buffer); + + let writer = self.w.as_mut().unwrap(); + Self::write_encoded_image_block(writer, &self.buffer) + } + + fn write_encoded_image_block(writer: &mut W, data_with_min_code_size: &[u8]) -> Result<(), EncodingError> { + let (&min_code_size, data) = data_with_min_code_size.split_first().unwrap_or((&2, &[])); + writer.write_le(min_code_size)?; + + // Write blocks. `chunks_exact` seems to be slightly faster + // than `chunks` according to both Rust docs and benchmark results. + let mut iter = data.chunks_exact(0xFF); + while let Some(full_block) = iter.next() { + writer.write_le(0xFFu8)?; + writer.write_all(full_block)?; + } + let last_block = iter.remainder(); + if !last_block.is_empty() { + writer.write_le(last_block.len() as u8)?; + writer.write_all(last_block)?; + } + writer.write_le(0u8).map_err(Into::into) + } + + fn write_color_table(&mut self, table: &[u8]) -> Result<(), EncodingError> { + let writer = self.w.as_mut().unwrap(); + let num_colors = table.len() / 3; + if num_colors > 256 { + return Err(EncodingError::from(FormatErrorKind::TooManyColors)); + } + let size = flag_size(num_colors); + writer.write_all(&table[..num_colors * 3])?; + // Waste some space as of gif spec + for _ in 0..((2 << size) - num_colors) { + writer.write_all(&[0, 0, 0])? + } + Ok(()) + } + + /// Writes an extension to the image. + /// + /// It is normally not necessary to call this method manually. + pub fn write_extension(&mut self, extension: ExtensionData) -> Result<(), EncodingError> { + use self::ExtensionData::*; + // 0 finite repetitions can only be achieved + // if the corresponting extension is not written + if let Repetitions(Repeat::Finite(0)) = extension { + return Ok(()) + } + let writer = self.w.as_mut().unwrap(); + writer.write_le(Block::Extension as u8)?; + match extension { + Control { flags, delay, trns } => { + writer.write_le(Extension::Control as u8)?; + writer.write_le(4u8)?; + writer.write_le(flags)?; + writer.write_le(delay)?; + writer.write_le(trns)?; + } + Repetitions(repeat) => { + writer.write_le(Extension::Application as u8)?; + writer.write_le(11u8)?; + writer.write_all(b"NETSCAPE2.0")?; + writer.write_le(3u8)?; + writer.write_le(1u8)?; + match repeat { + Repeat::Finite(no) => writer.write_le(no)?, + Repeat::Infinite => writer.write_le(0u16)?, + } + } + } + writer.write_le(0u8).map_err(Into::into) + } + + /// Writes a raw extension to the image. + /// + /// This method can be used to write an unsupported extension to the file. `func` is the extension + /// identifier (e.g. `Extension::Application as u8`). `data` are the extension payload blocks. If any + /// contained slice has a lenght > 255 it is automatically divided into sub-blocks. + pub fn write_raw_extension(&mut self, func: AnyExtension, data: &[&[u8]]) -> io::Result<()> { + let writer = self.w.as_mut().unwrap(); + writer.write_le(Block::Extension as u8)?; + writer.write_le(func.0)?; + for block in data { + for chunk in block.chunks(0xFF) { + writer.write_le(chunk.len() as u8)?; + writer.write_all(chunk)?; + } + } + writer.write_le(0u8) + } + + /// Writes a frame to the image, but expects `Frame.buffer` to contain LZW-encoded data + /// from [`Frame::make_lzw_pre_encoded`]. + /// + /// Note: This function also writes a control extension if necessary. + pub fn write_lzw_pre_encoded_frame(&mut self, frame: &Frame) -> Result<(), EncodingError> { + self.write_frame_header(frame)?; + let writer = self.w.as_mut().unwrap(); + Self::write_encoded_image_block(writer, &frame.buffer) + } + + /// Writes the logical screen desriptor + fn write_screen_desc(&mut self, flags: u8) -> io::Result<()> { + let writer = self.w.as_mut().unwrap(); + writer.write_all(b"GIF89a")?; + writer.write_le(self.width)?; + writer.write_le(self.height)?; + writer.write_le(flags)?; // packed field + writer.write_le(0u8)?; // bg index + writer.write_le(0u8) // aspect ratio + } + + /// Gets a reference to the writer instance used by this encoder. + pub fn get_ref(&self) -> &W { + self.w.as_ref().unwrap() + } + + /// Gets a mutable reference to the writer instance used by this encoder. + /// + /// It is inadvisable to directly write to the underlying writer. + pub fn get_mut(&mut self) -> &mut W { + self.w.as_mut().unwrap() + } + + /// Returns writer instance used by this encoder + pub fn into_inner(mut self) -> io::Result<W> { + self.write_trailer()?; + Ok(self.w.take().unwrap()) + } + + /// Write the final tailer. + fn write_trailer(&mut self) -> io::Result<()> { + self.w.as_mut().unwrap().write_le(Block::Trailer as u8) + } +} + +/// Encodes the data into the provided buffer. +/// +/// The first byte is the minimum code size, followed by LZW data. +fn lzw_encode(data: &[u8], buffer: &mut Vec<u8>) { + let min_code_size = match flag_size(1 + data.iter().copied().max().unwrap_or(0) as usize) + 1 { + 1 => 2, // As per gif spec: The minimal code size has to be >= 2 + n => n + }; + buffer.push(min_code_size); + let mut enc = LzwEncoder::new(BitOrder::Lsb, min_code_size); + let len = enc.into_vec(buffer).encode_all(data).consumed_out; + buffer.truncate(len+1); +} + +impl Frame<'_> { + /// Replace frame's buffer with a LZW-compressed one for use with [`Encoder::write_lzw_pre_encoded_frame`]. + /// + /// Frames can be compressed in any order, separately from the `Encoder`, which can be used to compress frames in parallel. + pub fn make_lzw_pre_encoded(&mut self) { + let mut buffer = Vec::with_capacity(self.buffer.len() / 2); + lzw_encode(&self.buffer, &mut buffer); + self.buffer = Cow::Owned(buffer); + } +} + +/// GIF encoder. +pub struct Encoder<W: Write> { + w: Option<W>, + global_palette: bool, + width: u16, + height: u16, + buffer: Vec<u8> +} + +impl<W: Write> Drop for Encoder<W> { + + #[cfg(feature = "raii_no_panic")] + fn drop(&mut self) { + if self.w.is_some() { + let _ = self.write_trailer(); + } + } + + #[cfg(not(feature = "raii_no_panic"))] + fn drop(&mut self) { + if self.w.is_some() { + self.write_trailer().unwrap(); + } + } +} + +// Color table size converted to flag bits +fn flag_size(size: usize) -> u8 { + match size { + 0 ..=2 => 0, + 3 ..=4 => 1, + 5 ..=8 => 2, + 9 ..=16 => 3, + 17 ..=32 => 4, + 33 ..=64 => 5, + 65 ..=128 => 6, + 129..=256 => 7, + _ => 7 + } +} + +#[test] +fn error_cast() { + let _ : Box<dyn error::Error> = EncodingError::from(FormatErrorKind::MissingColorPalette).into(); +} |