/src/image/src/codecs/gif.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Decoding of GIF Images |
2 | | //! |
3 | | //! GIF (Graphics Interchange Format) is an image format that supports lossless compression. |
4 | | //! |
5 | | //! # Related Links |
6 | | //! * <http://www.w3.org/Graphics/GIF/spec-gif89a.txt> - The GIF Specification |
7 | | //! |
8 | | //! # Examples |
9 | | //! ```rust,no_run |
10 | | //! use image::codecs::gif::{GifDecoder, GifEncoder}; |
11 | | //! use image::{ImageDecoder, AnimationDecoder}; |
12 | | //! use std::fs::File; |
13 | | //! use std::io::BufReader; |
14 | | //! # fn main() -> std::io::Result<()> { |
15 | | //! // Decode a gif into frames |
16 | | //! let file_in = BufReader::new(File::open("foo.gif")?); |
17 | | //! let mut decoder = GifDecoder::new(file_in).unwrap(); |
18 | | //! let frames = decoder.into_frames(); |
19 | | //! let frames = frames.collect_frames().expect("error decoding gif"); |
20 | | //! |
21 | | //! // Encode frames into a gif and save to a file |
22 | | //! let mut file_out = File::open("out.gif")?; |
23 | | //! let mut encoder = GifEncoder::new(file_out); |
24 | | //! encoder.encode_frames(frames.into_iter()); |
25 | | //! # Ok(()) |
26 | | //! # } |
27 | | //! ``` |
28 | | #![allow(clippy::while_let_loop)] |
29 | | |
30 | | use std::io::{self, BufRead, Cursor, Read, Seek, Write}; |
31 | | use std::marker::PhantomData; |
32 | | use std::mem; |
33 | | |
34 | | use gif::ColorOutput; |
35 | | use gif::{DisposalMethod, Frame}; |
36 | | |
37 | | use crate::animation::{self, Ratio}; |
38 | | use crate::color::{ColorType, Rgba}; |
39 | | use crate::error::{ |
40 | | DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind, |
41 | | ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, |
42 | | }; |
43 | | use crate::traits::Pixel; |
44 | | use crate::{AnimationDecoder, ExtendedColorType, ImageBuffer, ImageDecoder, ImageFormat, Limits}; |
45 | | |
46 | | /// GIF decoder |
47 | | pub struct GifDecoder<R: Read> { |
48 | | reader: gif::Decoder<R>, |
49 | | limits: Limits, |
50 | | } |
51 | | |
52 | | impl<R: Read> GifDecoder<R> { |
53 | | /// Creates a new decoder that decodes the input steam `r` |
54 | 1.85k | pub fn new(r: R) -> ImageResult<GifDecoder<R>> { |
55 | 1.85k | let mut decoder = gif::DecodeOptions::new(); |
56 | 1.85k | decoder.set_color_output(ColorOutput::RGBA); |
57 | 1.85k | |
58 | 1.85k | Ok(GifDecoder { |
59 | 1.85k | reader: decoder.read_info(r).map_err(ImageError::from_decoding)?, |
60 | 1.52k | limits: Limits::no_limits(), |
61 | | }) |
62 | 1.85k | } Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<_>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Line | Count | Source | 54 | 1.85k | pub fn new(r: R) -> ImageResult<GifDecoder<R>> { | 55 | 1.85k | let mut decoder = gif::DecodeOptions::new(); | 56 | 1.85k | decoder.set_color_output(ColorOutput::RGBA); | 57 | 1.85k | | 58 | 1.85k | Ok(GifDecoder { | 59 | 1.85k | reader: decoder.read_info(r).map_err(ImageError::from_decoding)?, | 60 | 1.52k | limits: Limits::no_limits(), | 61 | | }) | 62 | 1.85k | } |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>>>::new |
63 | | } |
64 | | |
65 | | /// Wrapper struct around a `Cursor<Vec<u8>>` |
66 | | #[allow(dead_code)] |
67 | | #[deprecated] |
68 | | pub struct GifReader<R>(Cursor<Vec<u8>>, PhantomData<R>); |
69 | | #[allow(deprecated)] |
70 | | impl<R> Read for GifReader<R> { |
71 | 0 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
72 | 0 | self.0.read(buf) |
73 | 0 | } |
74 | | |
75 | 0 | fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> { |
76 | 0 | if self.0.position() == 0 && buf.is_empty() { |
77 | 0 | mem::swap(buf, self.0.get_mut()); |
78 | 0 | Ok(buf.len()) |
79 | | } else { |
80 | 0 | self.0.read_to_end(buf) |
81 | | } |
82 | 0 | } |
83 | | } |
84 | | |
85 | | impl<R: BufRead + Seek> ImageDecoder for GifDecoder<R> { |
86 | 8.98k | fn dimensions(&self) -> (u32, u32) { |
87 | 8.98k | ( |
88 | 8.98k | u32::from(self.reader.width()), |
89 | 8.98k | u32::from(self.reader.height()), |
90 | 8.98k | ) |
91 | 8.98k | } Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<_> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Line | Count | Source | 86 | 8.98k | fn dimensions(&self) -> (u32, u32) { | 87 | 8.98k | ( | 88 | 8.98k | u32::from(self.reader.width()), | 89 | 8.98k | u32::from(self.reader.height()), | 90 | 8.98k | ) | 91 | 8.98k | } |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::dimensions |
92 | | |
93 | 6.17k | fn color_type(&self) -> ColorType { |
94 | 6.17k | ColorType::Rgba8 |
95 | 6.17k | } Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<_> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Line | Count | Source | 93 | 6.17k | fn color_type(&self) -> ColorType { | 94 | 6.17k | ColorType::Rgba8 | 95 | 6.17k | } |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::color_type |
96 | | |
97 | 1.51k | fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { |
98 | 1.51k | limits.check_support(&crate::LimitSupport::default())?; |
99 | | |
100 | 1.51k | let (width, height) = self.dimensions(); |
101 | 1.51k | limits.check_dimensions(width, height)?; |
102 | | |
103 | 1.51k | self.limits = limits; |
104 | 1.51k | |
105 | 1.51k | Ok(()) |
106 | 1.51k | } Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<_> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Line | Count | Source | 97 | 1.51k | fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { | 98 | 1.51k | limits.check_support(&crate::LimitSupport::default())?; | 99 | | | 100 | 1.51k | let (width, height) = self.dimensions(); | 101 | 1.51k | limits.check_dimensions(width, height)?; | 102 | | | 103 | 1.51k | self.limits = limits; | 104 | 1.51k | | 105 | 1.51k | Ok(()) | 106 | 1.51k | } |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::set_limits |
107 | | |
108 | 1.51k | fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { |
109 | 1.51k | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); |
110 | | |
111 | 1.51k | let frame = match self |
112 | 1.51k | .reader |
113 | 1.51k | .next_frame_info() |
114 | 1.51k | .map_err(ImageError::from_decoding)? |
115 | | { |
116 | 1.38k | Some(frame) => FrameInfo::new_from_frame(frame), |
117 | | None => { |
118 | 7 | return Err(ImageError::Parameter(ParameterError::from_kind( |
119 | 7 | ParameterErrorKind::NoMoreData, |
120 | 7 | ))) |
121 | | } |
122 | | }; |
123 | | |
124 | 1.38k | let (width, height) = self.dimensions(); |
125 | 1.38k | |
126 | 1.38k | if frame.left == 0 |
127 | 455 | && frame.width == width |
128 | 147 | && (u64::from(frame.top) + u64::from(frame.height) <= u64::from(height)) |
129 | | { |
130 | | // If the frame matches the logical screen, or, as a more general case, |
131 | | // fits into it and touches its left and right borders, then |
132 | | // we can directly write it into the buffer without causing line wraparound. |
133 | 96 | let line_length = usize::try_from(width) |
134 | 96 | .unwrap() |
135 | 96 | .checked_mul(self.color_type().bytes_per_pixel() as usize) |
136 | 96 | .unwrap(); |
137 | 96 | |
138 | 96 | // isolate the portion of the buffer to read the frame data into. |
139 | 96 | // the chunks above and below it are going to be zeroed. |
140 | 96 | let (blank_top, rest) = |
141 | 96 | buf.split_at_mut(line_length.checked_mul(frame.top as usize).unwrap()); |
142 | 96 | let (buf, blank_bottom) = |
143 | 96 | rest.split_at_mut(line_length.checked_mul(frame.height as usize).unwrap()); |
144 | 96 | |
145 | 96 | debug_assert_eq!(buf.len(), self.reader.buffer_size()); |
146 | | |
147 | | // this is only necessary in case the buffer is not zeroed |
148 | 4.69G | for b in blank_top { |
149 | 4.69G | *b = 0; |
150 | 4.69G | } |
151 | | // fill the middle section with the frame data |
152 | 96 | self.reader |
153 | 96 | .read_into_buffer(buf) |
154 | 96 | .map_err(ImageError::from_decoding)?; |
155 | | // this is only necessary in case the buffer is not zeroed |
156 | 2.13G | for b in blank_bottom { |
157 | 2.13G | *b = 0; |
158 | 2.13G | } |
159 | | } else { |
160 | | // If the frame does not match the logical screen, read into an extra buffer |
161 | | // and 'insert' the frame from left/top to logical screen width/height. |
162 | 1.29k | let buffer_size = (frame.width as usize) |
163 | 1.29k | .checked_mul(frame.height as usize) |
164 | 1.29k | .and_then(|s| s.checked_mul(4)) Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<_> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Line | Count | Source | 164 | 1.29k | .and_then(|s| s.checked_mul(4)) |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image::{closure#0} |
165 | 1.29k | .ok_or(ImageError::Limits(LimitError::from_kind( |
166 | 1.29k | LimitErrorKind::InsufficientMemory, |
167 | 1.29k | )))?; |
168 | | |
169 | 1.29k | self.limits.reserve_usize(buffer_size)?; |
170 | 1.28k | let mut frame_buffer = vec![0; buffer_size]; |
171 | 1.28k | self.limits.free_usize(buffer_size); |
172 | 1.28k | |
173 | 1.28k | self.reader |
174 | 1.28k | .read_into_buffer(&mut frame_buffer[..]) |
175 | 1.28k | .map_err(ImageError::from_decoding)?; |
176 | | |
177 | 110 | let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer); |
178 | 110 | let image_buffer = ImageBuffer::from_raw(width, height, buf); |
179 | 110 | |
180 | 110 | // `buffer_size` uses wrapping arithmetic, thus might not report the |
181 | 110 | // correct storage requirement if the result does not fit in `usize`. |
182 | 110 | // `ImageBuffer::from_raw` detects overflow and reports by returning `None`. |
183 | 110 | if frame_buffer.is_none() || image_buffer.is_none() { |
184 | 0 | return Err(ImageError::Unsupported( |
185 | 0 | UnsupportedError::from_format_and_kind( |
186 | 0 | ImageFormat::Gif.into(), |
187 | 0 | UnsupportedErrorKind::GenericFeature(format!( |
188 | 0 | "Image dimensions ({}, {}) are too large", |
189 | 0 | frame.width, frame.height |
190 | 0 | )), |
191 | 0 | ), |
192 | 0 | )); |
193 | 110 | } |
194 | 110 | |
195 | 110 | let frame_buffer = frame_buffer.unwrap(); |
196 | 110 | let mut image_buffer = image_buffer.unwrap(); |
197 | | |
198 | 3.10G | for (x, y, pixel) in image_buffer.enumerate_pixels_mut() { |
199 | 3.10G | let frame_x = x.wrapping_sub(frame.left); |
200 | 3.10G | let frame_y = y.wrapping_sub(frame.top); |
201 | 3.10G | |
202 | 3.10G | if frame_x < frame.width && frame_y < frame.height { |
203 | 2.62M | *pixel = *frame_buffer.get_pixel(frame_x, frame_y); |
204 | 3.09G | } else { |
205 | 3.09G | // this is only necessary in case the buffer is not zeroed |
206 | 3.09G | *pixel = Rgba([0, 0, 0, 0]); |
207 | 3.09G | } |
208 | | } |
209 | | } |
210 | | |
211 | 121 | Ok(()) |
212 | 1.51k | } Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<_> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Line | Count | Source | 108 | 1.51k | fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { | 109 | 1.51k | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); | 110 | | | 111 | 1.51k | let frame = match self | 112 | 1.51k | .reader | 113 | 1.51k | .next_frame_info() | 114 | 1.51k | .map_err(ImageError::from_decoding)? | 115 | | { | 116 | 1.38k | Some(frame) => FrameInfo::new_from_frame(frame), | 117 | | None => { | 118 | 7 | return Err(ImageError::Parameter(ParameterError::from_kind( | 119 | 7 | ParameterErrorKind::NoMoreData, | 120 | 7 | ))) | 121 | | } | 122 | | }; | 123 | | | 124 | 1.38k | let (width, height) = self.dimensions(); | 125 | 1.38k | | 126 | 1.38k | if frame.left == 0 | 127 | 455 | && frame.width == width | 128 | 147 | && (u64::from(frame.top) + u64::from(frame.height) <= u64::from(height)) | 129 | | { | 130 | | // If the frame matches the logical screen, or, as a more general case, | 131 | | // fits into it and touches its left and right borders, then | 132 | | // we can directly write it into the buffer without causing line wraparound. | 133 | 96 | let line_length = usize::try_from(width) | 134 | 96 | .unwrap() | 135 | 96 | .checked_mul(self.color_type().bytes_per_pixel() as usize) | 136 | 96 | .unwrap(); | 137 | 96 | | 138 | 96 | // isolate the portion of the buffer to read the frame data into. | 139 | 96 | // the chunks above and below it are going to be zeroed. | 140 | 96 | let (blank_top, rest) = | 141 | 96 | buf.split_at_mut(line_length.checked_mul(frame.top as usize).unwrap()); | 142 | 96 | let (buf, blank_bottom) = | 143 | 96 | rest.split_at_mut(line_length.checked_mul(frame.height as usize).unwrap()); | 144 | 96 | | 145 | 96 | debug_assert_eq!(buf.len(), self.reader.buffer_size()); | 146 | | | 147 | | // this is only necessary in case the buffer is not zeroed | 148 | 4.69G | for b in blank_top { | 149 | 4.69G | *b = 0; | 150 | 4.69G | } | 151 | | // fill the middle section with the frame data | 152 | 96 | self.reader | 153 | 96 | .read_into_buffer(buf) | 154 | 96 | .map_err(ImageError::from_decoding)?; | 155 | | // this is only necessary in case the buffer is not zeroed | 156 | 2.13G | for b in blank_bottom { | 157 | 2.13G | *b = 0; | 158 | 2.13G | } | 159 | | } else { | 160 | | // If the frame does not match the logical screen, read into an extra buffer | 161 | | // and 'insert' the frame from left/top to logical screen width/height. | 162 | 1.29k | let buffer_size = (frame.width as usize) | 163 | 1.29k | .checked_mul(frame.height as usize) | 164 | 1.29k | .and_then(|s| s.checked_mul(4)) | 165 | 1.29k | .ok_or(ImageError::Limits(LimitError::from_kind( | 166 | 1.29k | LimitErrorKind::InsufficientMemory, | 167 | 1.29k | )))?; | 168 | | | 169 | 1.29k | self.limits.reserve_usize(buffer_size)?; | 170 | 1.28k | let mut frame_buffer = vec![0; buffer_size]; | 171 | 1.28k | self.limits.free_usize(buffer_size); | 172 | 1.28k | | 173 | 1.28k | self.reader | 174 | 1.28k | .read_into_buffer(&mut frame_buffer[..]) | 175 | 1.28k | .map_err(ImageError::from_decoding)?; | 176 | | | 177 | 110 | let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer); | 178 | 110 | let image_buffer = ImageBuffer::from_raw(width, height, buf); | 179 | 110 | | 180 | 110 | // `buffer_size` uses wrapping arithmetic, thus might not report the | 181 | 110 | // correct storage requirement if the result does not fit in `usize`. | 182 | 110 | // `ImageBuffer::from_raw` detects overflow and reports by returning `None`. | 183 | 110 | if frame_buffer.is_none() || image_buffer.is_none() { | 184 | 0 | return Err(ImageError::Unsupported( | 185 | 0 | UnsupportedError::from_format_and_kind( | 186 | 0 | ImageFormat::Gif.into(), | 187 | 0 | UnsupportedErrorKind::GenericFeature(format!( | 188 | 0 | "Image dimensions ({}, {}) are too large", | 189 | 0 | frame.width, frame.height | 190 | 0 | )), | 191 | 0 | ), | 192 | 0 | )); | 193 | 110 | } | 194 | 110 | | 195 | 110 | let frame_buffer = frame_buffer.unwrap(); | 196 | 110 | let mut image_buffer = image_buffer.unwrap(); | 197 | | | 198 | 3.10G | for (x, y, pixel) in image_buffer.enumerate_pixels_mut() { | 199 | 3.10G | let frame_x = x.wrapping_sub(frame.left); | 200 | 3.10G | let frame_y = y.wrapping_sub(frame.top); | 201 | 3.10G | | 202 | 3.10G | if frame_x < frame.width && frame_y < frame.height { | 203 | 2.62M | *pixel = *frame_buffer.get_pixel(frame_x, frame_y); | 204 | 3.09G | } else { | 205 | 3.09G | // this is only necessary in case the buffer is not zeroed | 206 | 3.09G | *pixel = Rgba([0, 0, 0, 0]); | 207 | 3.09G | } | 208 | | } | 209 | | } | 210 | | | 211 | 121 | Ok(()) | 212 | 1.51k | } |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image |
213 | | |
214 | 1.51k | fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> { |
215 | 1.51k | (*self).read_image(buf) |
216 | 1.51k | } Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<_> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Line | Count | Source | 214 | 1.51k | fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> { | 215 | 1.51k | (*self).read_image(buf) | 216 | 1.51k | } |
Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed Unexecuted instantiation: <image::codecs::gif::GifDecoder<std::io::cursor::Cursor<&[u8]>> as image::io::decoder::ImageDecoder>::read_image_boxed |
217 | | } |
218 | | |
219 | | struct GifFrameIterator<R: Read> { |
220 | | reader: gif::Decoder<R>, |
221 | | |
222 | | width: u32, |
223 | | height: u32, |
224 | | |
225 | | non_disposed_frame: Option<ImageBuffer<Rgba<u8>, Vec<u8>>>, |
226 | | limits: Limits, |
227 | | } |
228 | | |
229 | | impl<R: BufRead + Seek> GifFrameIterator<R> { |
230 | 0 | fn new(decoder: GifDecoder<R>) -> GifFrameIterator<R> { |
231 | 0 | let (width, height) = decoder.dimensions(); |
232 | 0 | let limits = decoder.limits.clone(); |
233 | 0 |
|
234 | 0 | // intentionally ignore the background color for web compatibility |
235 | 0 |
|
236 | 0 | GifFrameIterator { |
237 | 0 | reader: decoder.reader, |
238 | 0 | width, |
239 | 0 | height, |
240 | 0 | non_disposed_frame: None, |
241 | 0 | limits, |
242 | 0 | } |
243 | 0 | } |
244 | | } |
245 | | |
246 | | impl<R: Read> Iterator for GifFrameIterator<R> { |
247 | | type Item = ImageResult<animation::Frame>; |
248 | | |
249 | 0 | fn next(&mut self) -> Option<ImageResult<animation::Frame>> { |
250 | | // The iterator always produces RGBA8 images |
251 | | const COLOR_TYPE: ColorType = ColorType::Rgba8; |
252 | | |
253 | | // Allocate the buffer for the previous frame. |
254 | | // This is done here and not in the constructor because |
255 | | // the constructor cannot return an error when the allocation limit is exceeded. |
256 | 0 | if self.non_disposed_frame.is_none() { |
257 | 0 | if let Err(e) = self |
258 | 0 | .limits |
259 | 0 | .reserve_buffer(self.width, self.height, COLOR_TYPE) |
260 | | { |
261 | 0 | return Some(Err(e)); |
262 | 0 | } |
263 | 0 | self.non_disposed_frame = Some(ImageBuffer::from_pixel( |
264 | 0 | self.width, |
265 | 0 | self.height, |
266 | 0 | Rgba([0, 0, 0, 0]), |
267 | 0 | )); |
268 | 0 | } |
269 | | // Bind to a variable to avoid repeated `.unwrap()` calls |
270 | 0 | let non_disposed_frame = self.non_disposed_frame.as_mut().unwrap(); |
271 | | |
272 | | // begin looping over each frame |
273 | | |
274 | 0 | let frame = match self.reader.next_frame_info() { |
275 | 0 | Ok(frame_info) => { |
276 | 0 | if let Some(frame) = frame_info { |
277 | 0 | FrameInfo::new_from_frame(frame) |
278 | | } else { |
279 | | // no more frames |
280 | 0 | return None; |
281 | | } |
282 | | } |
283 | 0 | Err(err) => return Some(Err(ImageError::from_decoding(err))), |
284 | | }; |
285 | | |
286 | | // All allocations we do from now on will be freed at the end of this function. |
287 | | // Therefore, do not count them towards the persistent limits. |
288 | | // Instead, create a local instance of `Limits` for this function alone |
289 | | // which will be dropped along with all the buffers when they go out of scope. |
290 | 0 | let mut local_limits = self.limits.clone(); |
291 | | |
292 | | // Check the allocation we're about to perform against the limits |
293 | 0 | if let Err(e) = local_limits.reserve_buffer(frame.width, frame.height, COLOR_TYPE) { |
294 | 0 | return Some(Err(e)); |
295 | 0 | } |
296 | 0 | // Allocate the buffer now that the limits allowed it |
297 | 0 | let mut vec = vec![0; self.reader.buffer_size()]; |
298 | 0 | if let Err(err) = self.reader.read_into_buffer(&mut vec) { |
299 | 0 | return Some(Err(ImageError::from_decoding(err))); |
300 | 0 | } |
301 | | |
302 | | // create the image buffer from the raw frame. |
303 | | // `buffer_size` uses wrapping arithmetic, thus might not report the |
304 | | // correct storage requirement if the result does not fit in `usize`. |
305 | | // on the other hand, `ImageBuffer::from_raw` detects overflow and |
306 | | // reports by returning `None`. |
307 | 0 | let Some(mut frame_buffer) = ImageBuffer::from_raw(frame.width, frame.height, vec) else { |
308 | 0 | return Some(Err(ImageError::Unsupported( |
309 | 0 | UnsupportedError::from_format_and_kind( |
310 | 0 | ImageFormat::Gif.into(), |
311 | 0 | UnsupportedErrorKind::GenericFeature(format!( |
312 | 0 | "Image dimensions ({}, {}) are too large", |
313 | 0 | frame.width, frame.height |
314 | 0 | )), |
315 | 0 | ), |
316 | 0 | ))); |
317 | | }; |
318 | | |
319 | | // blend the current frame with the non-disposed frame, then update |
320 | | // the non-disposed frame according to the disposal method. |
321 | 0 | fn blend_and_dispose_pixel( |
322 | 0 | dispose: DisposalMethod, |
323 | 0 | previous: &mut Rgba<u8>, |
324 | 0 | current: &mut Rgba<u8>, |
325 | 0 | ) { |
326 | 0 | let pixel_alpha = current.channels()[3]; |
327 | 0 | if pixel_alpha == 0 { |
328 | 0 | *current = *previous; |
329 | 0 | } |
330 | | |
331 | 0 | match dispose { |
332 | 0 | DisposalMethod::Any | DisposalMethod::Keep => { |
333 | 0 | // do not dispose |
334 | 0 | // (keep pixels from this frame) |
335 | 0 | // note: the `Any` disposal method is underspecified in the GIF |
336 | 0 | // spec, but most viewers treat it identically to `Keep` |
337 | 0 | *previous = *current; |
338 | 0 | } |
339 | 0 | DisposalMethod::Background => { |
340 | 0 | // restore to background color |
341 | 0 | // (background shows through transparent pixels in the next frame) |
342 | 0 | *previous = Rgba([0, 0, 0, 0]); |
343 | 0 | } |
344 | 0 | DisposalMethod::Previous => { |
345 | 0 | // restore to previous |
346 | 0 | // (dispose frames leaving the last none disposal frame) |
347 | 0 | } |
348 | | } |
349 | 0 | } |
350 | | |
351 | | // if `frame_buffer`'s frame exactly matches the entire image, then |
352 | | // use it directly, else create a new buffer to hold the composited |
353 | | // image. |
354 | 0 | let image_buffer = if (frame.left, frame.top) == (0, 0) |
355 | 0 | && (self.width, self.height) == frame_buffer.dimensions() |
356 | | { |
357 | 0 | for (x, y, pixel) in frame_buffer.enumerate_pixels_mut() { |
358 | 0 | let previous_pixel = non_disposed_frame.get_pixel_mut(x, y); |
359 | 0 | blend_and_dispose_pixel(frame.disposal_method, previous_pixel, pixel); |
360 | 0 | } |
361 | 0 | frame_buffer |
362 | | } else { |
363 | | // Check limits before allocating the buffer |
364 | 0 | if let Err(e) = local_limits.reserve_buffer(self.width, self.height, COLOR_TYPE) { |
365 | 0 | return Some(Err(e)); |
366 | 0 | } |
367 | 0 | ImageBuffer::from_fn(self.width, self.height, |x, y| { |
368 | 0 | let frame_x = x.wrapping_sub(frame.left); |
369 | 0 | let frame_y = y.wrapping_sub(frame.top); |
370 | 0 | let previous_pixel = non_disposed_frame.get_pixel_mut(x, y); |
371 | 0 |
|
372 | 0 | if frame_x < frame_buffer.width() && frame_y < frame_buffer.height() { |
373 | 0 | let mut pixel = *frame_buffer.get_pixel(frame_x, frame_y); |
374 | 0 | blend_and_dispose_pixel(frame.disposal_method, previous_pixel, &mut pixel); |
375 | 0 | pixel |
376 | | } else { |
377 | | // out of bounds, return pixel from previous frame |
378 | 0 | *previous_pixel |
379 | | } |
380 | 0 | }) |
381 | | }; |
382 | | |
383 | 0 | Some(Ok(animation::Frame::from_parts( |
384 | 0 | image_buffer, |
385 | 0 | 0, |
386 | 0 | 0, |
387 | 0 | frame.delay, |
388 | 0 | ))) |
389 | 0 | } |
390 | | } |
391 | | |
392 | | impl<'a, R: BufRead + Seek + 'a> AnimationDecoder<'a> for GifDecoder<R> { |
393 | 0 | fn into_frames(self) -> animation::Frames<'a> { |
394 | 0 | animation::Frames::new(Box::new(GifFrameIterator::new(self))) |
395 | 0 | } |
396 | | } |
397 | | |
398 | | struct FrameInfo { |
399 | | left: u32, |
400 | | top: u32, |
401 | | width: u32, |
402 | | height: u32, |
403 | | disposal_method: DisposalMethod, |
404 | | delay: animation::Delay, |
405 | | } |
406 | | |
407 | | impl FrameInfo { |
408 | 1.38k | fn new_from_frame(frame: &Frame) -> FrameInfo { |
409 | 1.38k | FrameInfo { |
410 | 1.38k | left: u32::from(frame.left), |
411 | 1.38k | top: u32::from(frame.top), |
412 | 1.38k | width: u32::from(frame.width), |
413 | 1.38k | height: u32::from(frame.height), |
414 | 1.38k | disposal_method: frame.dispose, |
415 | 1.38k | // frame.delay is in units of 10ms so frame.delay*10 is in ms |
416 | 1.38k | delay: animation::Delay::from_ratio(Ratio::new(u32::from(frame.delay) * 10, 1)), |
417 | 1.38k | } |
418 | 1.38k | } |
419 | | } |
420 | | |
421 | | /// Number of repetitions for a GIF animation |
422 | | #[derive(Clone, Copy, Debug)] |
423 | | pub enum Repeat { |
424 | | /// Finite number of repetitions |
425 | | Finite(u16), |
426 | | /// Looping GIF |
427 | | Infinite, |
428 | | } |
429 | | |
430 | | impl Repeat { |
431 | 0 | pub(crate) fn to_gif_enum(self) -> gif::Repeat { |
432 | 0 | match self { |
433 | 0 | Repeat::Finite(n) => gif::Repeat::Finite(n), |
434 | 0 | Repeat::Infinite => gif::Repeat::Infinite, |
435 | | } |
436 | 0 | } |
437 | | } |
438 | | |
439 | | /// GIF encoder. |
440 | | pub struct GifEncoder<W: Write> { |
441 | | w: Option<W>, |
442 | | gif_encoder: Option<gif::Encoder<W>>, |
443 | | speed: i32, |
444 | | repeat: Option<Repeat>, |
445 | | } |
446 | | |
447 | | impl<W: Write> GifEncoder<W> { |
448 | | /// Creates a new GIF encoder with a speed of 1. This prioritizes quality over performance at any cost. |
449 | 0 | pub fn new(w: W) -> GifEncoder<W> { |
450 | 0 | Self::new_with_speed(w, 1) |
451 | 0 | } Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::new Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new |
452 | | |
453 | | /// Create a new GIF encoder, and has the speed parameter `speed`. See |
454 | | /// [`Frame::from_rgba_speed`](https://docs.rs/gif/latest/gif/struct.Frame.html#method.from_rgba_speed) |
455 | | /// for more information. |
456 | 0 | pub fn new_with_speed(w: W, speed: i32) -> GifEncoder<W> { |
457 | 0 | assert!( |
458 | 0 | (1..=30).contains(&speed), |
459 | 0 | "speed needs to be in the range [1, 30]" |
460 | | ); |
461 | 0 | GifEncoder { |
462 | 0 | w: Some(w), |
463 | 0 | gif_encoder: None, |
464 | 0 | speed, |
465 | 0 | repeat: None, |
466 | 0 | } |
467 | 0 | } Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::new_with_speed Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new_with_speed |
468 | | |
469 | | /// Set the repeat behaviour of the encoded GIF |
470 | 0 | pub fn set_repeat(&mut self, repeat: Repeat) -> ImageResult<()> { |
471 | 0 | if let Some(ref mut encoder) = self.gif_encoder { |
472 | 0 | encoder |
473 | 0 | .set_repeat(repeat.to_gif_enum()) |
474 | 0 | .map_err(ImageError::from_encoding)?; |
475 | 0 | } |
476 | 0 | self.repeat = Some(repeat); |
477 | 0 | Ok(()) |
478 | 0 | } |
479 | | |
480 | | /// Encode a single image. |
481 | 0 | pub fn encode( |
482 | 0 | &mut self, |
483 | 0 | data: &[u8], |
484 | 0 | width: u32, |
485 | 0 | height: u32, |
486 | 0 | color: ExtendedColorType, |
487 | 0 | ) -> ImageResult<()> { |
488 | 0 | let (width, height) = self.gif_dimensions(width, height)?; |
489 | 0 | match color { |
490 | | ExtendedColorType::Rgb8 => { |
491 | 0 | self.encode_gif(Frame::from_rgb_speed(width, height, data, self.speed)) |
492 | | } |
493 | 0 | ExtendedColorType::Rgba8 => self.encode_gif(Frame::from_rgba_speed( |
494 | 0 | width, |
495 | 0 | height, |
496 | 0 | &mut data.to_owned(), |
497 | 0 | self.speed, |
498 | 0 | )), |
499 | 0 | _ => Err(ImageError::Unsupported( |
500 | 0 | UnsupportedError::from_format_and_kind( |
501 | 0 | ImageFormat::Gif.into(), |
502 | 0 | UnsupportedErrorKind::Color(color), |
503 | 0 | ), |
504 | 0 | )), |
505 | | } |
506 | 0 | } Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::encode Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode |
507 | | |
508 | | /// Encode one frame of animation. |
509 | 0 | pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> { |
510 | 0 | let frame = self.convert_frame(img_frame)?; |
511 | 0 | self.encode_gif(frame) |
512 | 0 | } |
513 | | |
514 | | /// Encodes Frames. |
515 | | /// Consider using `try_encode_frames` instead to encode an `animation::Frames` like iterator. |
516 | 0 | pub fn encode_frames<F>(&mut self, frames: F) -> ImageResult<()> |
517 | 0 | where |
518 | 0 | F: IntoIterator<Item = animation::Frame>, |
519 | 0 | { |
520 | 0 | for img_frame in frames { |
521 | 0 | self.encode_frame(img_frame)?; |
522 | | } |
523 | 0 | Ok(()) |
524 | 0 | } |
525 | | |
526 | | /// Try to encode a collection of `ImageResult<animation::Frame>` objects. |
527 | | /// Use this function to encode an `animation::Frames` like iterator. |
528 | | /// Whenever an `Err` item is encountered, that value is returned without further actions. |
529 | 0 | pub fn try_encode_frames<F>(&mut self, frames: F) -> ImageResult<()> |
530 | 0 | where |
531 | 0 | F: IntoIterator<Item = ImageResult<animation::Frame>>, |
532 | 0 | { |
533 | 0 | for img_frame in frames { |
534 | 0 | self.encode_frame(img_frame?)?; |
535 | | } |
536 | 0 | Ok(()) |
537 | 0 | } |
538 | | |
539 | 0 | pub(crate) fn convert_frame( |
540 | 0 | &mut self, |
541 | 0 | img_frame: animation::Frame, |
542 | 0 | ) -> ImageResult<Frame<'static>> { |
543 | 0 | // get the delay before converting img_frame |
544 | 0 | let frame_delay = img_frame.delay().into_ratio().to_integer(); |
545 | 0 | // convert img_frame into RgbaImage |
546 | 0 | let mut rbga_frame = img_frame.into_buffer(); |
547 | 0 | let (width, height) = self.gif_dimensions(rbga_frame.width(), rbga_frame.height())?; |
548 | | |
549 | | // Create the gif::Frame from the animation::Frame |
550 | 0 | let mut frame = Frame::from_rgba_speed(width, height, &mut rbga_frame, self.speed); |
551 | 0 | // Saturate the conversion to u16::MAX instead of returning an error as that |
552 | 0 | // would require a new special cased variant in ParameterErrorKind which most |
553 | 0 | // likely couldn't be reused for other cases. This isn't a bad trade-off given |
554 | 0 | // that the current algorithm is already lossy. |
555 | 0 | frame.delay = (frame_delay / 10).try_into().unwrap_or(u16::MAX); |
556 | 0 |
|
557 | 0 | Ok(frame) |
558 | 0 | } |
559 | | |
560 | 0 | fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> { |
561 | 0 | fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> { |
562 | 0 | let width = u16::try_from(width).ok()?; |
563 | 0 | let height = u16::try_from(height).ok()?; |
564 | 0 | Some((width, height)) |
565 | 0 | } |
566 | | |
567 | | // TODO: this is not very idiomatic yet. Should return an EncodingError. |
568 | 0 | inner_dimensions(width, height).ok_or_else(|| { |
569 | 0 | ImageError::Parameter(ParameterError::from_kind( |
570 | 0 | ParameterErrorKind::DimensionMismatch, |
571 | 0 | )) |
572 | 0 | }) Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::gif_dimensions::{closure#0} Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::gif_dimensions::{closure#0} |
573 | 0 | } Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::gif_dimensions Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::gif_dimensions |
574 | | |
575 | 0 | pub(crate) fn encode_gif(&mut self, mut frame: Frame) -> ImageResult<()> { |
576 | | let gif_encoder; |
577 | 0 | if let Some(ref mut encoder) = self.gif_encoder { |
578 | 0 | gif_encoder = encoder; |
579 | 0 | } else { |
580 | 0 | let writer = self.w.take().unwrap(); |
581 | 0 | let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[]) |
582 | 0 | .map_err(ImageError::from_encoding)?; |
583 | 0 | if let Some(ref repeat) = self.repeat { |
584 | 0 | encoder |
585 | 0 | .set_repeat(repeat.to_gif_enum()) |
586 | 0 | .map_err(ImageError::from_encoding)?; |
587 | 0 | } |
588 | 0 | self.gif_encoder = Some(encoder); |
589 | 0 | gif_encoder = self.gif_encoder.as_mut().unwrap(); |
590 | | } |
591 | | |
592 | 0 | frame.dispose = DisposalMethod::Background; |
593 | 0 |
|
594 | 0 | gif_encoder |
595 | 0 | .write_frame(&frame) |
596 | 0 | .map_err(ImageError::from_encoding) |
597 | 0 | } Unexecuted instantiation: <image::codecs::gif::GifEncoder<_>>::encode_gif Unexecuted instantiation: <image::codecs::gif::GifEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_gif |
598 | | } |
599 | | |
600 | | impl ImageError { |
601 | 1.71k | fn from_decoding(err: gif::DecodingError) -> ImageError { |
602 | | use gif::DecodingError::*; |
603 | 1.71k | match err { |
604 | 276 | err @ Format(_) => { |
605 | 276 | ImageError::Decoding(DecodingError::new(ImageFormat::Gif.into(), err)) |
606 | | } |
607 | 1.43k | Io(io_err) => ImageError::IoError(io_err), |
608 | | } |
609 | 1.71k | } |
610 | | |
611 | 0 | fn from_encoding(err: gif::EncodingError) -> ImageError { |
612 | | use gif::EncodingError::*; |
613 | 0 | match err { |
614 | 0 | err @ Format(_) => { |
615 | 0 | ImageError::Encoding(EncodingError::new(ImageFormat::Gif.into(), err)) |
616 | | } |
617 | 0 | Io(io_err) => ImageError::IoError(io_err), |
618 | | } |
619 | 0 | } |
620 | | } |
621 | | |
622 | | #[cfg(test)] |
623 | | mod test { |
624 | | use super::*; |
625 | | |
626 | | #[test] |
627 | | fn frames_exceeding_logical_screen_size() { |
628 | | // This is a gif with 10x10 logical screen, but a 16x16 frame + 6px offset inside. |
629 | | let data = vec![ |
630 | | 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0A, 0x00, 0x0A, 0x00, 0xF0, 0x00, 0x00, 0x00, |
631 | | 0x00, 0x00, 0x0E, 0xFF, 0x1F, 0x21, 0xF9, 0x04, 0x09, 0x64, 0x00, 0x00, 0x00, 0x2C, |
632 | | 0x06, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x23, 0x84, 0x8F, 0xA9, |
633 | | 0xBB, 0xE1, 0xE8, 0x42, 0x8A, 0x0F, 0x50, 0x79, 0xAE, 0xD1, 0xF9, 0x7A, 0xE8, 0x71, |
634 | | 0x5B, 0x48, 0x81, 0x64, 0xD5, 0x91, 0xCA, 0x89, 0x4D, 0x21, 0x63, 0x89, 0x4C, 0x09, |
635 | | 0x77, 0xF5, 0x6D, 0x14, 0x00, 0x3B, |
636 | | ]; |
637 | | |
638 | | let decoder = GifDecoder::new(Cursor::new(data)).unwrap(); |
639 | | let mut buf = vec![0u8; decoder.total_bytes() as usize]; |
640 | | |
641 | | assert!(decoder.read_image(&mut buf).is_ok()); |
642 | | } |
643 | | } |