//! Decoding of GIF Images //! //! GIF (Graphics Interchange Format) is an image format that supports lossless compression. //! //! # Related Links //! * - 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 { reader: gif::Decoder, limits: Limits, } impl GifDecoder { /// Creates a new decoder that decodes the input steam `r` pub fn new(r: R) -> ImageResult> { 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> { 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>` pub struct GifReader(Cursor>, PhantomData); impl Read for GifReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { 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 { type Reader = GifReader; 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 { 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 { reader: gif::Decoder, width: u32, height: u32, non_disposed_frame: ImageBuffer, Vec>, } impl GifFrameIterator { fn new(decoder: GifDecoder) -> GifFrameIterator { 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 Iterator for GifFrameIterator { type Item = ImageResult; fn next(&mut self) -> Option> { // 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, current: &mut Rgba, ) { 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 { 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: Option, gif_encoder: Option>, speed: i32, repeat: Option, } impl GifEncoder { /// Creates a new GIF encoder with a speed of 1. This prioritizes quality over performance at any cost. pub fn new(w: W) -> GifEncoder { 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 { 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(&mut self, frames: F) -> ImageResult<()> where F: IntoIterator, { for img_frame in frames { self.encode_frame(img_frame)?; } Ok(()) } /// Try to encode a collection of `ImageResult` 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(&mut self, frames: F) -> ImageResult<()> where F: IntoIterator>, { for img_frame in frames { self.encode_frame(img_frame?)?; } Ok(()) } pub(crate) fn convert_frame( &mut self, img_frame: animation::Frame, ) -> ImageResult> { // 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()); } }