figif_core/decoders/
buffered.rs1use crate::error::{FigifError, Result};
4use crate::traits::{BufferedGifDecoder, GifDecoder};
5use crate::types::{DecodedFrame, DisposalMethod, GifMetadata, LoopCount};
6use gif::DecodeOptions;
7use image::{Rgba, RgbaImage};
8use std::fs::File;
9use std::io::{BufReader, Cursor, Read};
10use std::path::Path;
11
12#[derive(Debug, Clone, Default)]
27pub struct BufferedDecoder {
28 memory_limit: usize,
30}
31
32impl BufferedDecoder {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn with_memory_limit(mut self, limit: usize) -> Self {
43 self.memory_limit = limit;
44 self
45 }
46
47 fn decode_all_frames<R: Read>(&self, reader: R) -> Result<Vec<DecodedFrame>> {
49 let mut options = DecodeOptions::new();
50 options.set_color_output(gif::ColorOutput::RGBA);
51
52 let mut decoder = options.read_info(reader)?;
53
54 let width = decoder.width() as u32;
55 let height = decoder.height() as u32;
56
57 if self.memory_limit > 0 {
59 let frame_size = (width * height * 4) as usize;
60 if frame_size * 2 > self.memory_limit {
62 return Err(FigifError::InvalidConfig {
63 message: format!("GIF dimensions {}x{} exceed memory limit", width, height),
64 });
65 }
66 }
67
68 let mut canvas = RgbaImage::from_pixel(width, height, Rgba([0, 0, 0, 0]));
70 let mut frames = Vec::new();
71 let mut index = 0;
72
73 let mut previous_canvas: Option<RgbaImage> = None;
75
76 while let Some(frame) = decoder.read_next_frame()? {
77 let delay = frame.delay;
78 let disposal = DisposalMethod::from(frame.dispose);
79 let left = frame.left;
80 let top = frame.top;
81 let frame_width = frame.width as u32;
82 let frame_height = frame.height as u32;
83
84 if matches!(disposal, DisposalMethod::Previous) {
86 previous_canvas = Some(canvas.clone());
87 }
88
89 let frame_buffer = &frame.buffer;
91 for y in 0..frame_height {
92 for x in 0..frame_width {
93 let src_idx = ((y * frame_width + x) * 4) as usize;
94 if src_idx + 3 < frame_buffer.len() {
95 let pixel = Rgba([
96 frame_buffer[src_idx],
97 frame_buffer[src_idx + 1],
98 frame_buffer[src_idx + 2],
99 frame_buffer[src_idx + 3],
100 ]);
101
102 let canvas_x = left as u32 + x;
103 let canvas_y = top as u32 + y;
104
105 if canvas_x < width && canvas_y < height {
106 if pixel[3] > 0 {
108 canvas.put_pixel(canvas_x, canvas_y, pixel);
109 }
110 }
111 }
112 }
113 }
114
115 frames.push(DecodedFrame {
117 index,
118 image: canvas.clone(),
119 delay_centiseconds: delay,
120 disposal,
121 left,
122 top,
123 });
124
125 match disposal {
127 DisposalMethod::Background => {
128 for y in 0..frame_height {
130 for x in 0..frame_width {
131 let canvas_x = left as u32 + x;
132 let canvas_y = top as u32 + y;
133 if canvas_x < width && canvas_y < height {
134 canvas.put_pixel(canvas_x, canvas_y, Rgba([0, 0, 0, 0]));
135 }
136 }
137 }
138 }
139 DisposalMethod::Previous => {
140 if let Some(ref prev) = previous_canvas {
142 canvas = prev.clone();
143 }
144 }
145 DisposalMethod::Keep | DisposalMethod::None => {
146 }
148 }
149
150 index += 1;
151 }
152
153 if frames.is_empty() {
154 return Err(FigifError::NoFrames);
155 }
156
157 Ok(frames)
158 }
159
160 fn extract_metadata<R: Read>(&self, reader: R) -> Result<GifMetadata> {
162 let mut options = DecodeOptions::new();
163 options.set_color_output(gif::ColorOutput::RGBA);
164
165 let mut decoder = options.read_info(reader)?;
166
167 let width = decoder.width();
168 let height = decoder.height();
169 let global_palette = decoder.global_palette().map(|p| p.to_vec());
170 let has_transparency = global_palette
171 .as_ref()
172 .is_some_and(|_| decoder.bg_color().is_some());
173
174 let mut frame_count = 0;
176 let mut total_duration_cs: u64 = 0;
177
178 while let Some(frame) = decoder.read_next_frame()? {
179 frame_count += 1;
180 total_duration_cs += frame.delay as u64;
181 }
182
183 let loop_count = LoopCount::Infinite; Ok(GifMetadata {
187 width,
188 height,
189 frame_count,
190 total_duration_ms: total_duration_cs * 10,
191 has_transparency,
192 loop_count,
193 global_palette,
194 })
195 }
196}
197
198pub struct BufferedFrameIter {
200 frames: std::vec::IntoIter<DecodedFrame>,
201}
202
203impl Iterator for BufferedFrameIter {
204 type Item = Result<DecodedFrame>;
205
206 fn next(&mut self) -> Option<Self::Item> {
207 self.frames.next().map(Ok)
208 }
209
210 fn size_hint(&self) -> (usize, Option<usize>) {
211 self.frames.size_hint()
212 }
213}
214
215impl ExactSizeIterator for BufferedFrameIter {}
216
217impl GifDecoder for BufferedDecoder {
218 type FrameIter = BufferedFrameIter;
219
220 fn decode_file(&self, path: impl AsRef<Path>) -> Result<Self::FrameIter> {
221 let path = path.as_ref();
222 let file = File::open(path).map_err(|e| FigifError::FileRead {
223 path: path.to_path_buf(),
224 source: e,
225 })?;
226 let reader = BufReader::new(file);
227 let frames = self.decode_all_frames(reader)?;
228 Ok(BufferedFrameIter {
229 frames: frames.into_iter(),
230 })
231 }
232
233 fn decode_bytes(&self, data: &[u8]) -> Result<Self::FrameIter> {
234 if data.is_empty() {
235 return Err(FigifError::EmptyData);
236 }
237 let reader = Cursor::new(data);
238 let frames = self.decode_all_frames(reader)?;
239 Ok(BufferedFrameIter {
240 frames: frames.into_iter(),
241 })
242 }
243
244 fn decode_reader<R: Read + Send>(&self, reader: R) -> Result<Self::FrameIter> {
245 let frames = self.decode_all_frames(reader)?;
246 Ok(BufferedFrameIter {
247 frames: frames.into_iter(),
248 })
249 }
250
251 fn metadata_from_bytes(&self, data: &[u8]) -> Result<GifMetadata> {
252 if data.is_empty() {
253 return Err(FigifError::EmptyData);
254 }
255 let reader = Cursor::new(data);
256 self.extract_metadata(reader)
257 }
258
259 fn metadata_from_file(&self, path: impl AsRef<Path>) -> Result<GifMetadata> {
260 let path = path.as_ref();
261 let file = File::open(path).map_err(|e| FigifError::FileRead {
262 path: path.to_path_buf(),
263 source: e,
264 })?;
265 let reader = BufReader::new(file);
266 self.extract_metadata(reader)
267 }
268
269 fn name(&self) -> &'static str {
270 "buffered"
271 }
272}
273
274impl BufferedGifDecoder for BufferedDecoder {}