aboutsummaryrefslogtreecommitdiff
path: root/vendor/image/src/codecs/gif.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/image/src/codecs/gif.rs')
-rw-r--r--vendor/image/src/codecs/gif.rs606
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());
+ }
+}