1mod decoder;
53mod encoder;
54mod lzw;
55pub mod quality;
56
57use crate::error::{CodecError, CodecResult};
58use crate::frame::VideoFrame;
59
60pub use decoder::{GifFrame, GraphicsControlExtension, ImageDescriptor, LogicalScreenDescriptor};
61pub use encoder::{DitheringMethod, GifEncoderConfig, GifFrameConfig, QuantizationMethod};
62
63use decoder::GifDecoderState;
64use encoder::GifEncoderState;
65
66pub struct GifDecoder {
74 state: GifDecoderState,
75}
76
77impl GifDecoder {
78 pub fn new(data: &[u8]) -> CodecResult<Self> {
88 let state = GifDecoderState::decode(data)?;
89 Ok(Self { state })
90 }
91
92 #[must_use]
94 pub fn width(&self) -> u32 {
95 u32::from(self.state.screen_descriptor.width)
96 }
97
98 #[must_use]
100 pub fn height(&self) -> u32 {
101 u32::from(self.state.screen_descriptor.height)
102 }
103
104 #[must_use]
106 pub fn frame_count(&self) -> usize {
107 self.state.frames.len()
108 }
109
110 #[must_use]
112 pub fn loop_count(&self) -> u16 {
113 self.state.loop_count
114 }
115
116 #[must_use]
118 pub fn screen_descriptor(&self) -> &LogicalScreenDescriptor {
119 &self.state.screen_descriptor
120 }
121
122 #[must_use]
124 pub fn global_color_table(&self) -> &[u8] {
125 &self.state.global_color_table
126 }
127
128 pub fn frame_info(&self, index: usize) -> CodecResult<&GifFrame> {
138 self.state.frames.get(index).ok_or_else(|| {
139 CodecError::InvalidParameter(format!("Frame index {} out of range", index))
140 })
141 }
142
143 pub fn decode_frame(&self, index: usize) -> CodecResult<VideoFrame> {
153 self.state.frame_to_video_frame(index)
154 }
155
156 pub fn decode_all_frames(&self) -> CodecResult<Vec<VideoFrame>> {
162 let mut frames = Vec::with_capacity(self.frame_count());
163 for i in 0..self.frame_count() {
164 frames.push(self.decode_frame(i)?);
165 }
166 Ok(frames)
167 }
168
169 pub fn frame_delay_ms(&self, index: usize) -> CodecResult<u32> {
179 let frame = self.frame_info(index)?;
180 if let Some(ref control) = frame.control {
181 Ok(u32::from(control.delay_time) * 10)
183 } else {
184 Ok(0)
185 }
186 }
187
188 pub fn frame_has_transparency(&self, index: usize) -> CodecResult<bool> {
198 let frame = self.frame_info(index)?;
199 Ok(frame.control.as_ref().map_or(false, |c| c.has_transparency))
200 }
201
202 pub fn frame_disposal_method(&self, index: usize) -> CodecResult<DisposalMethod> {
212 let frame = self.frame_info(index)?;
213 if let Some(ref control) = frame.control {
214 Ok(DisposalMethod::from_value(control.disposal_method))
215 } else {
216 Ok(DisposalMethod::None)
217 }
218 }
219
220 #[must_use]
222 pub fn is_animated(&self) -> bool {
223 self.frame_count() > 1
224 }
225
226 #[must_use]
228 pub fn total_duration_ms(&self) -> u32 {
229 let mut total = 0;
230 for i in 0..self.frame_count() {
231 if let Ok(delay) = self.frame_delay_ms(i) {
232 total += delay;
233 }
234 }
235 total
236 }
237}
238
239pub struct GifEncoder {
248 state: GifEncoderState,
249}
250
251impl GifEncoder {
252 pub fn new(width: u32, height: u32, config: GifEncoderConfig) -> CodecResult<Self> {
264 let state = GifEncoderState::new(width, height, config)?;
265 Ok(Self { state })
266 }
267
268 pub fn encode(
279 &mut self,
280 frames: &[VideoFrame],
281 frame_configs: &[GifFrameConfig],
282 ) -> CodecResult<Vec<u8>> {
283 self.state.encode(frames, frame_configs)
284 }
285
286 pub fn encode_single(&mut self, frame: &VideoFrame) -> CodecResult<Vec<u8>> {
296 let config = GifFrameConfig::default();
297 self.encode(&[frame.clone()], &[config])
298 }
299
300 pub fn encode_animation(
311 &mut self,
312 frames: &[VideoFrame],
313 delay_ms: u32,
314 ) -> CodecResult<Vec<u8>> {
315 let delay_time = (delay_ms / 10) as u16; let config = GifFrameConfig {
317 delay_time,
318 ..Default::default()
319 };
320 let configs = vec![config; frames.len()];
321 self.encode(frames, &configs)
322 }
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub enum DisposalMethod {
328 None,
330 Keep,
332 Background,
334 Previous,
336}
337
338impl DisposalMethod {
339 #[must_use]
341 pub fn from_value(value: u8) -> Self {
342 match value {
343 0 => Self::None,
344 1 => Self::Keep,
345 2 => Self::Background,
346 3 => Self::Previous,
347 _ => Self::None,
348 }
349 }
350
351 #[must_use]
353 pub fn to_value(self) -> u8 {
354 match self {
355 Self::None => 0,
356 Self::Keep => 1,
357 Self::Background => 2,
358 Self::Previous => 3,
359 }
360 }
361}
362
363#[must_use]
379pub fn is_gif(data: &[u8]) -> bool {
380 data.len() >= 6 && data.starts_with(b"GIF") && (&data[3..6] == b"89a" || &data[3..6] == b"87a")
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use crate::frame::{Plane, VideoFrame};
387 use bytes::Bytes;
388 use oximedia_core::PixelFormat;
389
390 fn create_test_frame(width: u32, height: u32, color: [u8; 3]) -> VideoFrame {
391 let size = (width * height * 4) as usize;
392 let mut data = Vec::with_capacity(size);
393
394 for _ in 0..(width * height) {
395 data.extend_from_slice(&[color[0], color[1], color[2], 255]);
396 }
397
398 let plane = Plane {
399 data,
400 stride: (width * 4) as usize,
401 width,
402 height,
403 };
404
405 let mut frame = VideoFrame::new(PixelFormat::Rgba32, width, height);
406 frame.planes = vec![plane];
407 frame
408 }
409
410 #[test]
411 fn test_is_gif() {
412 assert!(is_gif(b"GIF89a\x00\x00"));
413 assert!(is_gif(b"GIF87a\x00\x00"));
414 assert!(!is_gif(b"PNG\x00\x00\x00"));
415 assert!(!is_gif(b"GIF"));
416 }
417
418 #[test]
419 fn test_disposal_method() {
420 assert_eq!(DisposalMethod::from_value(0), DisposalMethod::None);
421 assert_eq!(DisposalMethod::from_value(1), DisposalMethod::Keep);
422 assert_eq!(DisposalMethod::from_value(2), DisposalMethod::Background);
423 assert_eq!(DisposalMethod::from_value(3), DisposalMethod::Previous);
424
425 assert_eq!(DisposalMethod::None.to_value(), 0);
426 assert_eq!(DisposalMethod::Keep.to_value(), 1);
427 assert_eq!(DisposalMethod::Background.to_value(), 2);
428 assert_eq!(DisposalMethod::Previous.to_value(), 3);
429 }
430
431 #[test]
432 fn test_encoder_single_frame() {
433 let config = GifEncoderConfig::default();
434 let mut encoder = GifEncoder::new(16, 16, config).expect("should succeed");
435
436 let frame = create_test_frame(16, 16, [255, 0, 0]);
437 let data = encoder.encode_single(&frame).expect("should succeed");
438
439 assert!(is_gif(&data));
440 assert!(data.len() > 100); }
442
443 #[test]
444 fn test_encoder_animation() {
445 let config = GifEncoderConfig {
446 colors: 16,
447 loop_count: 0,
448 ..Default::default()
449 };
450 let mut encoder = GifEncoder::new(16, 16, config).expect("should succeed");
451
452 let frames = vec![
453 create_test_frame(16, 16, [255, 0, 0]),
454 create_test_frame(16, 16, [0, 255, 0]),
455 create_test_frame(16, 16, [0, 0, 255]),
456 ];
457
458 let data = encoder
459 .encode_animation(&frames, 100)
460 .expect("should succeed");
461
462 assert!(is_gif(&data));
463
464 let decoder = GifDecoder::new(&data).expect("should succeed");
466 assert_eq!(decoder.frame_count(), 3);
467 assert_eq!(decoder.width(), 16);
468 assert_eq!(decoder.height(), 16);
469 assert!(decoder.is_animated());
470 }
471
472 #[test]
473 fn test_roundtrip() {
474 let config = GifEncoderConfig {
475 colors: 256,
476 ..Default::default()
477 };
478 let mut encoder = GifEncoder::new(8, 8, config).expect("should succeed");
479
480 let original_frame = create_test_frame(8, 8, [128, 64, 192]);
481 let data = encoder
482 .encode_single(&original_frame)
483 .expect("should succeed");
484
485 let decoder = GifDecoder::new(&data).expect("should succeed");
486 assert_eq!(decoder.frame_count(), 1);
487 assert_eq!(decoder.width(), 8);
488 assert_eq!(decoder.height(), 8);
489
490 let decoded_frame = decoder.decode_frame(0).expect("should succeed");
491 assert_eq!(decoded_frame.format, PixelFormat::Rgba32);
492 assert_eq!(decoded_frame.width, 8);
493 assert_eq!(decoded_frame.height, 8);
494 }
495}