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