677 lines
24 KiB
Rust
677 lines
24 KiB
Rust
//! 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;
|
|
//! use std::io::BufReader;
|
|
//! # fn main() -> std::io::Result<()> {
|
|
//! // Decode a gif into frames
|
|
//! let file_in = BufReader::new(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::io::{self, BufRead, Cursor, Read, Seek, Write};
|
|
use std::marker::PhantomData;
|
|
use std::mem;
|
|
|
|
use gif::ColorOutput;
|
|
use gif::{DisposalMethod, Frame};
|
|
|
|
use crate::animation::{self, Ratio};
|
|
use crate::color::{ColorType, Rgba};
|
|
use crate::error::{
|
|
DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind,
|
|
ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind,
|
|
};
|
|
use crate::traits::Pixel;
|
|
use crate::{
|
|
AnimationDecoder, ExtendedColorType, ImageBuffer, ImageDecoder, ImageEncoder, ImageFormat,
|
|
Limits,
|
|
};
|
|
|
|
/// 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::no_limits(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Wrapper struct around a `Cursor<Vec<u8>>`
|
|
#[allow(dead_code)]
|
|
#[deprecated]
|
|
pub struct GifReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
|
|
#[allow(deprecated)]
|
|
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<R: BufRead + Seek> ImageDecoder for GifDecoder<R> {
|
|
fn dimensions(&self) -> (u32, u32) {
|
|
(
|
|
u32::from(self.reader.width()),
|
|
u32::from(self.reader.height()),
|
|
)
|
|
}
|
|
|
|
fn color_type(&self) -> ColorType {
|
|
ColorType::Rgba8
|
|
}
|
|
|
|
fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
|
|
limits.check_support(&crate::LimitSupport::default())?;
|
|
|
|
let (width, height) = self.dimensions();
|
|
limits.check_dimensions(width, height)?;
|
|
|
|
self.limits = limits;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
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
|
|
&& (u64::from(frame.top) + u64::from(frame.height) <= u64::from(height))
|
|
{
|
|
// 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 = (frame.width as usize)
|
|
.checked_mul(frame.height as usize)
|
|
.and_then(|s| s.checked_mul(4))
|
|
.ok_or(ImageError::Limits(LimitError::from_kind(
|
|
LimitErrorKind::InsufficientMemory,
|
|
)))?;
|
|
|
|
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(())
|
|
}
|
|
|
|
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
|
|
(*self).read_image(buf)
|
|
}
|
|
}
|
|
|
|
struct GifFrameIterator<R: Read> {
|
|
reader: gif::Decoder<R>,
|
|
|
|
width: u32,
|
|
height: u32,
|
|
|
|
non_disposed_frame: Option<ImageBuffer<Rgba<u8>, Vec<u8>>>,
|
|
limits: Limits,
|
|
// `is_end` is used to indicate whether the iterator has reached the end of the frames.
|
|
// Or encounter any un-recoverable error.
|
|
is_end: bool,
|
|
}
|
|
|
|
impl<R: BufRead + Seek> GifFrameIterator<R> {
|
|
fn new(decoder: GifDecoder<R>) -> GifFrameIterator<R> {
|
|
let (width, height) = decoder.dimensions();
|
|
let limits = decoder.limits.clone();
|
|
|
|
// intentionally ignore the background color for web compatibility
|
|
|
|
GifFrameIterator {
|
|
reader: decoder.reader,
|
|
width,
|
|
height,
|
|
non_disposed_frame: None,
|
|
limits,
|
|
is_end: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<R: Read> Iterator for GifFrameIterator<R> {
|
|
type Item = ImageResult<animation::Frame>;
|
|
|
|
fn next(&mut self) -> Option<ImageResult<animation::Frame>> {
|
|
if self.is_end {
|
|
return None;
|
|
}
|
|
|
|
// The iterator always produces RGBA8 images
|
|
const COLOR_TYPE: ColorType = ColorType::Rgba8;
|
|
|
|
// Allocate the buffer for the previous frame.
|
|
// This is done here and not in the constructor because
|
|
// the constructor cannot return an error when the allocation limit is exceeded.
|
|
if self.non_disposed_frame.is_none() {
|
|
if let Err(e) = self
|
|
.limits
|
|
.reserve_buffer(self.width, self.height, COLOR_TYPE)
|
|
{
|
|
return Some(Err(e));
|
|
}
|
|
self.non_disposed_frame = Some(ImageBuffer::from_pixel(
|
|
self.width,
|
|
self.height,
|
|
Rgba([0, 0, 0, 0]),
|
|
));
|
|
}
|
|
// Bind to a variable to avoid repeated `.unwrap()` calls
|
|
let non_disposed_frame = self.non_disposed_frame.as_mut().unwrap();
|
|
|
|
// 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) => match err {
|
|
gif::DecodingError::Io(ref e) => {
|
|
if e.kind() == io::ErrorKind::UnexpectedEof {
|
|
// end of file reached, no more frames
|
|
self.is_end = true;
|
|
}
|
|
return Some(Err(ImageError::from_decoding(err)));
|
|
}
|
|
_ => {
|
|
return Some(Err(ImageError::from_decoding(err)));
|
|
}
|
|
},
|
|
};
|
|
|
|
// All allocations we do from now on will be freed at the end of this function.
|
|
// Therefore, do not count them towards the persistent limits.
|
|
// Instead, create a local instance of `Limits` for this function alone
|
|
// which will be dropped along with all the buffers when they go out of scope.
|
|
let mut local_limits = self.limits.clone();
|
|
|
|
// Check the allocation we're about to perform against the limits
|
|
if let Err(e) = local_limits.reserve_buffer(frame.width, frame.height, COLOR_TYPE) {
|
|
return Some(Err(e));
|
|
}
|
|
// Allocate the buffer now that the limits allowed it
|
|
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 Some(mut frame_buffer) = ImageBuffer::from_raw(frame.width, frame.height, vec) else {
|
|
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 = non_disposed_frame.get_pixel_mut(x, y);
|
|
blend_and_dispose_pixel(frame.disposal_method, previous_pixel, pixel);
|
|
}
|
|
frame_buffer
|
|
} else {
|
|
// Check limits before allocating the buffer
|
|
if let Err(e) = local_limits.reserve_buffer(self.width, self.height, COLOR_TYPE) {
|
|
return Some(Err(e));
|
|
}
|
|
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 = 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: BufRead + Seek + '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: ExtendedColorType,
|
|
) -> ImageResult<()> {
|
|
let (width, height) = self.gif_dimensions(width, height)?;
|
|
match color {
|
|
ExtendedColorType::Rgb8 => {
|
|
self.encode_gif(Frame::from_rgb_speed(width, height, data, self.speed))
|
|
}
|
|
ExtendedColorType::Rgba8 => self.encode_gif(Frame::from_rgba_speed(
|
|
width,
|
|
height,
|
|
&mut data.to_owned(),
|
|
self.speed,
|
|
)),
|
|
_ => Err(ImageError::Unsupported(
|
|
UnsupportedError::from_format_and_kind(
|
|
ImageFormat::Gif.into(),
|
|
UnsupportedErrorKind::Color(color),
|
|
),
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// 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(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 = DisposalMethod::Background;
|
|
|
|
gif_encoder
|
|
.write_frame(&frame)
|
|
.map_err(ImageError::from_encoding)
|
|
}
|
|
}
|
|
impl<W: Write> ImageEncoder for GifEncoder<W> {
|
|
fn write_image(
|
|
mut self,
|
|
buf: &[u8],
|
|
width: u32,
|
|
height: u32,
|
|
color_type: ExtendedColorType,
|
|
) -> ImageResult<()> {
|
|
self.encode(buf, width, height, color_type)
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|