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