Skip to main content

oximedia_codec/gif/
mod.rs

1//! GIF (Graphics Interchange Format) codec implementation.
2//!
3//! Supports GIF89a specification:
4//! - Decoding and encoding
5//! - Animation with multiple frames
6//! - Transparency support
7//! - Color quantization (median cut, octree)
8//! - Dithering (Floyd-Steinberg, ordered)
9//! - Loop count control
10//!
11//! # Examples
12//!
13//! ## Decoding
14//!
15//! ```ignore
16//! use oximedia_codec::gif::GifDecoder;
17//!
18//! let data = std::fs::read("animation.gif")?;
19//! let decoder = GifDecoder::new(&data)?;
20//!
21//! println!("Frames: {}", decoder.frame_count());
22//! println!("Size: {}x{}", decoder.width(), decoder.height());
23//!
24//! for i in 0..decoder.frame_count() {
25//!     let frame = decoder.decode_frame(i)?;
26//!     // Process frame...
27//! }
28//! ```
29//!
30//! ## Encoding
31//!
32//! ```ignore
33//! use oximedia_codec::gif::{GifEncoder, GifEncoderConfig, GifFrameConfig};
34//!
35//! let config = GifEncoderConfig {
36//!     colors: 256,
37//!     loop_count: 0, // Infinite loop
38//!     ..Default::default()
39//! };
40//!
41//! let mut encoder = GifEncoder::new(640, 480, config)?;
42//!
43//! let frame_config = GifFrameConfig {
44//!     delay_time: 10, // 100ms
45//!     ..Default::default()
46//! };
47//!
48//! let data = encoder.encode(&frames, &[frame_config; frames.len()])?;
49//! std::fs::write("output.gif", &data)?;
50//! ```
51
52mod 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
66/// GIF decoder for reading GIF89a files.
67///
68/// Supports:
69/// - Single images and animations
70/// - Transparency
71/// - Interlaced images
72/// - Global and local color tables
73pub struct GifDecoder {
74    state: GifDecoderState,
75}
76
77impl GifDecoder {
78    /// Create a new GIF decoder from data.
79    ///
80    /// # Arguments
81    ///
82    /// * `data` - GIF file data
83    ///
84    /// # Errors
85    ///
86    /// Returns error if data is not a valid GIF file.
87    pub fn new(data: &[u8]) -> CodecResult<Self> {
88        let state = GifDecoderState::decode(data)?;
89        Ok(Self { state })
90    }
91
92    /// Get canvas width.
93    #[must_use]
94    pub fn width(&self) -> u32 {
95        u32::from(self.state.screen_descriptor.width)
96    }
97
98    /// Get canvas height.
99    #[must_use]
100    pub fn height(&self) -> u32 {
101        u32::from(self.state.screen_descriptor.height)
102    }
103
104    /// Get number of frames.
105    #[must_use]
106    pub fn frame_count(&self) -> usize {
107        self.state.frames.len()
108    }
109
110    /// Get loop count (0 = infinite).
111    #[must_use]
112    pub fn loop_count(&self) -> u16 {
113        self.state.loop_count
114    }
115
116    /// Get the logical screen descriptor.
117    #[must_use]
118    pub fn screen_descriptor(&self) -> &LogicalScreenDescriptor {
119        &self.state.screen_descriptor
120    }
121
122    /// Get the global color table.
123    #[must_use]
124    pub fn global_color_table(&self) -> &[u8] {
125        &self.state.global_color_table
126    }
127
128    /// Get frame information.
129    ///
130    /// # Arguments
131    ///
132    /// * `index` - Frame index
133    ///
134    /// # Errors
135    ///
136    /// Returns error if index is out of range.
137    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    /// Decode a specific frame.
144    ///
145    /// # Arguments
146    ///
147    /// * `index` - Frame index to decode
148    ///
149    /// # Errors
150    ///
151    /// Returns error if decoding fails or index is out of range.
152    pub fn decode_frame(&self, index: usize) -> CodecResult<VideoFrame> {
153        self.state.frame_to_video_frame(index)
154    }
155
156    /// Decode all frames.
157    ///
158    /// # Errors
159    ///
160    /// Returns error if decoding fails.
161    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    /// Get frame delay time in milliseconds.
170    ///
171    /// # Arguments
172    ///
173    /// * `index` - Frame index
174    ///
175    /// # Errors
176    ///
177    /// Returns error if index is out of range.
178    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            // Delay time is in hundredths of a second
182            Ok(u32::from(control.delay_time) * 10)
183        } else {
184            Ok(0)
185        }
186    }
187
188    /// Check if frame has transparency.
189    ///
190    /// # Arguments
191    ///
192    /// * `index` - Frame index
193    ///
194    /// # Errors
195    ///
196    /// Returns error if index is out of range.
197    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    /// Get frame disposal method.
203    ///
204    /// # Arguments
205    ///
206    /// * `index` - Frame index
207    ///
208    /// # Errors
209    ///
210    /// Returns error if index is out of range.
211    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    /// Check if this is an animated GIF.
221    #[must_use]
222    pub fn is_animated(&self) -> bool {
223        self.frame_count() > 1
224    }
225
226    /// Get total animation duration in milliseconds.
227    #[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
239/// GIF encoder for creating GIF89a files.
240///
241/// Supports:
242/// - Single images and animations
243/// - Color quantization with configurable palette size
244/// - Dithering options
245/// - Transparency
246/// - Loop count control
247pub struct GifEncoder {
248    state: GifEncoderState,
249}
250
251impl GifEncoder {
252    /// Create a new GIF encoder.
253    ///
254    /// # Arguments
255    ///
256    /// * `width` - Canvas width
257    /// * `height` - Canvas height
258    /// * `config` - Encoder configuration
259    ///
260    /// # Errors
261    ///
262    /// Returns error if parameters are invalid.
263    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    /// Encode frames to GIF data.
269    ///
270    /// # Arguments
271    ///
272    /// * `frames` - Frames to encode
273    /// * `frame_configs` - Configuration for each frame
274    ///
275    /// # Errors
276    ///
277    /// Returns error if encoding fails.
278    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    /// Encode a single frame (non-animated GIF).
287    ///
288    /// # Arguments
289    ///
290    /// * `frame` - Frame to encode
291    ///
292    /// # Errors
293    ///
294    /// Returns error if encoding fails.
295    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    /// Encode multiple frames with the same delay time.
301    ///
302    /// # Arguments
303    ///
304    /// * `frames` - Frames to encode
305    /// * `delay_ms` - Delay between frames in milliseconds
306    ///
307    /// # Errors
308    ///
309    /// Returns error if encoding fails.
310    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; // Convert to hundredths of a second
316        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/// Frame disposal method.
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub enum DisposalMethod {
328    /// No disposal specified.
329    None,
330    /// Do not dispose (keep frame).
331    Keep,
332    /// Restore to background color.
333    Background,
334    /// Restore to previous frame.
335    Previous,
336}
337
338impl DisposalMethod {
339    /// Convert from GIF disposal method value.
340    #[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    /// Convert to GIF disposal method value.
352    #[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/// Detect if data is a GIF file.
364///
365/// # Arguments
366///
367/// * `data` - Data to check
368///
369/// # Examples
370///
371/// ```ignore
372/// let data = std::fs::read("image.gif")?;
373/// if is_gif(&data) {
374///     let decoder = GifDecoder::new(&data)?;
375///     // ...
376/// }
377/// ```
378#[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); // Should have header + data
441    }
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        // Decode and verify
465        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}