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/image/src/codecs/gif.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/image/src/codecs/gif.rs')
-rw-r--r-- | vendor/image/src/codecs/gif.rs | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/gif.rs b/vendor/image/src/codecs/gif.rs new file mode 100644 index 0000000..dcbd841 --- /dev/null +++ b/vendor/image/src/codecs/gif.rs @@ -0,0 +1,606 @@ +//! Decoding of GIF Images +//! +//! GIF (Graphics Interchange Format) is an image format that supports lossless compression. +//! +//! # Related Links +//! * <http://www.w3.org/Graphics/GIF/spec-gif89a.txt> - The GIF Specification +//! +//! # Examples +//! ```rust,no_run +//! use image::codecs::gif::{GifDecoder, GifEncoder}; +//! use image::{ImageDecoder, AnimationDecoder}; +//! use std::fs::File; +//! # fn main() -> std::io::Result<()> { +//! // Decode a gif into frames +//! let file_in = File::open("foo.gif")?; +//! let mut decoder = GifDecoder::new(file_in).unwrap(); +//! let frames = decoder.into_frames(); +//! let frames = frames.collect_frames().expect("error decoding gif"); +//! +//! // Encode frames into a gif and save to a file +//! let mut file_out = File::open("out.gif")?; +//! let mut encoder = GifEncoder::new(file_out); +//! encoder.encode_frames(frames.into_iter()); +//! # Ok(()) +//! # } +//! ``` +#![allow(clippy::while_let_loop)] + +use std::convert::TryFrom; +use std::convert::TryInto; +use std::io::{self, Cursor, Read, Write}; +use std::marker::PhantomData; +use std::mem; + +use gif::ColorOutput; +use gif::{DisposalMethod, Frame}; +use num_rational::Ratio; + +use crate::animation; +use crate::color::{ColorType, Rgba}; +use crate::error::{ + DecodingError, EncodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind, + UnsupportedError, UnsupportedErrorKind, +}; +use crate::image::{self, AnimationDecoder, ImageDecoder, ImageFormat}; +use crate::io::Limits; +use crate::traits::Pixel; +use crate::ImageBuffer; + +/// GIF decoder +pub struct GifDecoder<R: Read> { + reader: gif::Decoder<R>, + limits: Limits, +} + +impl<R: Read> GifDecoder<R> { + /// Creates a new decoder that decodes the input steam `r` + pub fn new(r: R) -> ImageResult<GifDecoder<R>> { + let mut decoder = gif::DecodeOptions::new(); + decoder.set_color_output(ColorOutput::RGBA); + + Ok(GifDecoder { + reader: decoder.read_info(r).map_err(ImageError::from_decoding)?, + limits: Limits::default(), + }) + } + + /// Creates a new decoder that decodes the input steam `r`, using limits `limits` + pub fn with_limits(r: R, limits: Limits) -> ImageResult<GifDecoder<R>> { + let mut decoder = gif::DecodeOptions::new(); + decoder.set_color_output(ColorOutput::RGBA); + + Ok(GifDecoder { + reader: decoder.read_info(r).map_err(ImageError::from_decoding)?, + limits, + }) + } +} + +/// Wrapper struct around a `Cursor<Vec<u8>>` +pub struct GifReader<R>(Cursor<Vec<u8>>, PhantomData<R>); +impl<R> Read for GifReader<R> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.0.read(buf) + } + fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { + if self.0.position() == 0 && buf.is_empty() { + mem::swap(buf, self.0.get_mut()); + Ok(buf.len()) + } else { + self.0.read_to_end(buf) + } + } +} + +impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder<R> { + type Reader = GifReader<R>; + + fn dimensions(&self) -> (u32, u32) { + ( + u32::from(self.reader.width()), + u32::from(self.reader.height()), + ) + } + + fn color_type(&self) -> ColorType { + ColorType::Rgba8 + } + + fn into_reader(self) -> ImageResult<Self::Reader> { + Ok(GifReader( + Cursor::new(image::decoder_to_vec(self)?), + PhantomData, + )) + } + + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + + let frame = match self + .reader + .next_frame_info() + .map_err(ImageError::from_decoding)? + { + Some(frame) => FrameInfo::new_from_frame(frame), + None => { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::NoMoreData, + ))) + } + }; + + let (width, height) = self.dimensions(); + + if frame.left == 0 + && frame.width == width + && (frame.top as u64 + frame.height as u64 <= height as u64) + { + // If the frame matches the logical screen, or, as a more general case, + // fits into it and touches its left and right borders, then + // we can directly write it into the buffer without causing line wraparound. + let line_length = usize::try_from(width) + .unwrap() + .checked_mul(self.color_type().bytes_per_pixel() as usize) + .unwrap(); + + // isolate the portion of the buffer to read the frame data into. + // the chunks above and below it are going to be zeroed. + let (blank_top, rest) = + buf.split_at_mut(line_length.checked_mul(frame.top as usize).unwrap()); + let (buf, blank_bottom) = + rest.split_at_mut(line_length.checked_mul(frame.height as usize).unwrap()); + + debug_assert_eq!(buf.len(), self.reader.buffer_size()); + + // this is only necessary in case the buffer is not zeroed + for b in blank_top { + *b = 0; + } + // fill the middle section with the frame data + self.reader + .read_into_buffer(buf) + .map_err(ImageError::from_decoding)?; + // this is only necessary in case the buffer is not zeroed + for b in blank_bottom { + *b = 0; + } + } else { + // If the frame does not match the logical screen, read into an extra buffer + // and 'insert' the frame from left/top to logical screen width/height. + let buffer_size = self.reader.buffer_size(); + + self.limits.reserve_usize(buffer_size)?; + + let mut frame_buffer = vec![0; buffer_size]; + + self.limits.free_usize(buffer_size); + + self.reader + .read_into_buffer(&mut frame_buffer[..]) + .map_err(ImageError::from_decoding)?; + + let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer); + let image_buffer = ImageBuffer::from_raw(width, height, buf); + + // `buffer_size` uses wrapping arithmetic, thus might not report the + // correct storage requirement if the result does not fit in `usize`. + // `ImageBuffer::from_raw` detects overflow and reports by returning `None`. + if frame_buffer.is_none() || image_buffer.is_none() { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Gif.into(), + UnsupportedErrorKind::GenericFeature(format!( + "Image dimensions ({}, {}) are too large", + frame.width, frame.height + )), + ), + )); + } + + let frame_buffer = frame_buffer.unwrap(); + let mut image_buffer = image_buffer.unwrap(); + + for (x, y, pixel) in image_buffer.enumerate_pixels_mut() { + let frame_x = x.wrapping_sub(frame.left); + let frame_y = y.wrapping_sub(frame.top); + + if frame_x < frame.width && frame_y < frame.height { + *pixel = *frame_buffer.get_pixel(frame_x, frame_y); + } else { + // this is only necessary in case the buffer is not zeroed + *pixel = Rgba([0, 0, 0, 0]); + } + } + } + + Ok(()) + } +} + +struct GifFrameIterator<R: Read> { + reader: gif::Decoder<R>, + + width: u32, + height: u32, + + non_disposed_frame: ImageBuffer<Rgba<u8>, Vec<u8>>, +} + +impl<R: Read> GifFrameIterator<R> { + fn new(decoder: GifDecoder<R>) -> GifFrameIterator<R> { + let (width, height) = decoder.dimensions(); + + // intentionally ignore the background color for web compatibility + + // create the first non disposed frame + let non_disposed_frame = ImageBuffer::from_pixel(width, height, Rgba([0, 0, 0, 0])); + + GifFrameIterator { + reader: decoder.reader, + width, + height, + non_disposed_frame, + } + } +} + +impl<R: Read> Iterator for GifFrameIterator<R> { + type Item = ImageResult<animation::Frame>; + + fn next(&mut self) -> Option<ImageResult<animation::Frame>> { + // begin looping over each frame + + let frame = match self.reader.next_frame_info() { + Ok(frame_info) => { + if let Some(frame) = frame_info { + FrameInfo::new_from_frame(frame) + } else { + // no more frames + return None; + } + } + Err(err) => return Some(Err(ImageError::from_decoding(err))), + }; + + let mut vec = vec![0; self.reader.buffer_size()]; + if let Err(err) = self.reader.read_into_buffer(&mut vec) { + return Some(Err(ImageError::from_decoding(err))); + } + + // create the image buffer from the raw frame. + // `buffer_size` uses wrapping arithmetic, thus might not report the + // correct storage requirement if the result does not fit in `usize`. + // on the other hand, `ImageBuffer::from_raw` detects overflow and + // reports by returning `None`. + let mut frame_buffer = match ImageBuffer::from_raw(frame.width, frame.height, vec) { + Some(frame_buffer) => frame_buffer, + None => { + return Some(Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Gif.into(), + UnsupportedErrorKind::GenericFeature(format!( + "Image dimensions ({}, {}) are too large", + frame.width, frame.height + )), + ), + ))) + } + }; + + // blend the current frame with the non-disposed frame, then update + // the non-disposed frame according to the disposal method. + fn blend_and_dispose_pixel( + dispose: DisposalMethod, + previous: &mut Rgba<u8>, + current: &mut Rgba<u8>, + ) { + let pixel_alpha = current.channels()[3]; + if pixel_alpha == 0 { + *current = *previous; + } + + match dispose { + DisposalMethod::Any | DisposalMethod::Keep => { + // do not dispose + // (keep pixels from this frame) + // note: the `Any` disposal method is underspecified in the GIF + // spec, but most viewers treat it identically to `Keep` + *previous = *current; + } + DisposalMethod::Background => { + // restore to background color + // (background shows through transparent pixels in the next frame) + *previous = Rgba([0, 0, 0, 0]); + } + DisposalMethod::Previous => { + // restore to previous + // (dispose frames leaving the last none disposal frame) + } + } + } + + // if `frame_buffer`'s frame exactly matches the entire image, then + // use it directly, else create a new buffer to hold the composited + // image. + let image_buffer = if (frame.left, frame.top) == (0, 0) + && (self.width, self.height) == frame_buffer.dimensions() + { + for (x, y, pixel) in frame_buffer.enumerate_pixels_mut() { + let previous_pixel = self.non_disposed_frame.get_pixel_mut(x, y); + blend_and_dispose_pixel(frame.disposal_method, previous_pixel, pixel); + } + frame_buffer + } else { + ImageBuffer::from_fn(self.width, self.height, |x, y| { + let frame_x = x.wrapping_sub(frame.left); + let frame_y = y.wrapping_sub(frame.top); + let previous_pixel = self.non_disposed_frame.get_pixel_mut(x, y); + + if frame_x < frame_buffer.width() && frame_y < frame_buffer.height() { + let mut pixel = *frame_buffer.get_pixel(frame_x, frame_y); + blend_and_dispose_pixel(frame.disposal_method, previous_pixel, &mut pixel); + pixel + } else { + // out of bounds, return pixel from previous frame + *previous_pixel + } + }) + }; + + Some(Ok(animation::Frame::from_parts( + image_buffer, + 0, + 0, + frame.delay, + ))) + } +} + +impl<'a, R: Read + 'a> AnimationDecoder<'a> for GifDecoder<R> { + fn into_frames(self) -> animation::Frames<'a> { + animation::Frames::new(Box::new(GifFrameIterator::new(self))) + } +} + +struct FrameInfo { + left: u32, + top: u32, + width: u32, + height: u32, + disposal_method: DisposalMethod, + delay: animation::Delay, +} + +impl FrameInfo { + fn new_from_frame(frame: &Frame) -> FrameInfo { + FrameInfo { + left: u32::from(frame.left), + top: u32::from(frame.top), + width: u32::from(frame.width), + height: u32::from(frame.height), + disposal_method: frame.dispose, + // frame.delay is in units of 10ms so frame.delay*10 is in ms + delay: animation::Delay::from_ratio(Ratio::new(u32::from(frame.delay) * 10, 1)), + } + } +} + +/// Number of repetitions for a GIF animation +#[derive(Clone, Copy, Debug)] +pub enum Repeat { + /// Finite number of repetitions + Finite(u16), + /// Looping GIF + Infinite, +} + +impl Repeat { + pub(crate) fn to_gif_enum(&self) -> gif::Repeat { + match self { + Repeat::Finite(n) => gif::Repeat::Finite(*n), + Repeat::Infinite => gif::Repeat::Infinite, + } + } +} + +/// GIF encoder. +pub struct GifEncoder<W: Write> { + w: Option<W>, + gif_encoder: Option<gif::Encoder<W>>, + speed: i32, + repeat: Option<Repeat>, +} + +impl<W: Write> GifEncoder<W> { + /// Creates a new GIF encoder with a speed of 1. This prioritizes quality over performance at any cost. + pub fn new(w: W) -> GifEncoder<W> { + Self::new_with_speed(w, 1) + } + + /// Create a new GIF encoder, and has the speed parameter `speed`. See + /// [`Frame::from_rgba_speed`](https://docs.rs/gif/latest/gif/struct.Frame.html#method.from_rgba_speed) + /// for more information. + pub fn new_with_speed(w: W, speed: i32) -> GifEncoder<W> { + assert!( + (1..=30).contains(&speed), + "speed needs to be in the range [1, 30]" + ); + GifEncoder { + w: Some(w), + gif_encoder: None, + speed, + repeat: None, + } + } + + /// Set the repeat behaviour of the encoded GIF + pub fn set_repeat(&mut self, repeat: Repeat) -> ImageResult<()> { + if let Some(ref mut encoder) = self.gif_encoder { + encoder + .set_repeat(repeat.to_gif_enum()) + .map_err(ImageError::from_encoding)?; + } + self.repeat = Some(repeat); + Ok(()) + } + + /// Encode a single image. + pub fn encode( + &mut self, + data: &[u8], + width: u32, + height: u32, + color: ColorType, + ) -> ImageResult<()> { + let (width, height) = self.gif_dimensions(width, height)?; + match color { + ColorType::Rgb8 => self.encode_gif(Frame::from_rgb(width, height, data)), + ColorType::Rgba8 => { + self.encode_gif(Frame::from_rgba(width, height, &mut data.to_owned())) + } + _ => Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Gif.into(), + UnsupportedErrorKind::Color(color.into()), + ), + )), + } + } + + /// Encode one frame of animation. + pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> { + let frame = self.convert_frame(img_frame)?; + self.encode_gif(frame) + } + + /// Encodes Frames. + /// Consider using `try_encode_frames` instead to encode an `animation::Frames` like iterator. + pub fn encode_frames<F>(&mut self, frames: F) -> ImageResult<()> + where + F: IntoIterator<Item = animation::Frame>, + { + for img_frame in frames { + self.encode_frame(img_frame)?; + } + Ok(()) + } + + /// Try to encode a collection of `ImageResult<animation::Frame>` objects. + /// Use this function to encode an `animation::Frames` like iterator. + /// Whenever an `Err` item is encountered, that value is returned without further actions. + pub fn try_encode_frames<F>(&mut self, frames: F) -> ImageResult<()> + where + F: IntoIterator<Item = ImageResult<animation::Frame>>, + { + for img_frame in frames { + self.encode_frame(img_frame?)?; + } + Ok(()) + } + + pub(crate) fn convert_frame( + &mut self, + img_frame: animation::Frame, + ) -> ImageResult<Frame<'static>> { + // get the delay before converting img_frame + let frame_delay = img_frame.delay().into_ratio().to_integer(); + // convert img_frame into RgbaImage + let mut rbga_frame = img_frame.into_buffer(); + let (width, height) = self.gif_dimensions(rbga_frame.width(), rbga_frame.height())?; + + // Create the gif::Frame from the animation::Frame + let mut frame = Frame::from_rgba_speed(width, height, &mut rbga_frame, self.speed); + // Saturate the conversion to u16::MAX instead of returning an error as that + // would require a new special cased variant in ParameterErrorKind which most + // likely couldn't be reused for other cases. This isn't a bad trade-off given + // that the current algorithm is already lossy. + frame.delay = (frame_delay / 10).try_into().unwrap_or(std::u16::MAX); + + Ok(frame) + } + + fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> { + fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> { + let width = u16::try_from(width).ok()?; + let height = u16::try_from(height).ok()?; + Some((width, height)) + } + + // TODO: this is not very idiomatic yet. Should return an EncodingError. + inner_dimensions(width, height).ok_or_else(|| { + ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::DimensionMismatch, + )) + }) + } + + pub(crate) fn encode_gif(&mut self, mut frame: Frame) -> ImageResult<()> { + let gif_encoder; + if let Some(ref mut encoder) = self.gif_encoder { + gif_encoder = encoder; + } else { + let writer = self.w.take().unwrap(); + let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[]) + .map_err(ImageError::from_encoding)?; + if let Some(ref repeat) = self.repeat { + encoder + .set_repeat(repeat.to_gif_enum()) + .map_err(ImageError::from_encoding)?; + } + self.gif_encoder = Some(encoder); + gif_encoder = self.gif_encoder.as_mut().unwrap() + } + + frame.dispose = gif::DisposalMethod::Background; + + gif_encoder + .write_frame(&frame) + .map_err(ImageError::from_encoding) + } +} + +impl ImageError { + fn from_decoding(err: gif::DecodingError) -> ImageError { + use gif::DecodingError::*; + match err { + err @ Format(_) => { + ImageError::Decoding(DecodingError::new(ImageFormat::Gif.into(), err)) + } + Io(io_err) => ImageError::IoError(io_err), + } + } + + fn from_encoding(err: gif::EncodingError) -> ImageError { + use gif::EncodingError::*; + match err { + err @ Format(_) => { + ImageError::Encoding(EncodingError::new(ImageFormat::Gif.into(), err)) + } + Io(io_err) => ImageError::IoError(io_err), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn frames_exceeding_logical_screen_size() { + // This is a gif with 10x10 logical screen, but a 16x16 frame + 6px offset inside. + let data = vec![ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0A, 0x00, 0x0A, 0x00, 0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0E, 0xFF, 0x1F, 0x21, 0xF9, 0x04, 0x09, 0x64, 0x00, 0x00, 0x00, 0x2C, + 0x06, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x23, 0x84, 0x8F, 0xA9, + 0xBB, 0xE1, 0xE8, 0x42, 0x8A, 0x0F, 0x50, 0x79, 0xAE, 0xD1, 0xF9, 0x7A, 0xE8, 0x71, + 0x5B, 0x48, 0x81, 0x64, 0xD5, 0x91, 0xCA, 0x89, 0x4D, 0x21, 0x63, 0x89, 0x4C, 0x09, + 0x77, 0xF5, 0x6D, 0x14, 0x00, 0x3B, + ]; + + let decoder = GifDecoder::new(Cursor::new(data)).unwrap(); + let mut buf = vec![0u8; decoder.total_bytes() as usize]; + + assert!(decoder.read_image(&mut buf).is_ok()); + } +} |