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;
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
65/// GIF decoder for reading GIF89a files.
66///
67/// Supports:
68/// - Single images and animations
69/// - Transparency
70/// - Interlaced images
71/// - Global and local color tables
72pub struct GifDecoder {
73    state: GifDecoderState,
74}
75
76impl GifDecoder {
77    /// Create a new GIF decoder from data.
78    ///
79    /// # Arguments
80    ///
81    /// * `data` - GIF file data
82    ///
83    /// # Errors
84    ///
85    /// Returns error if data is not a valid GIF file.
86    pub fn new(data: &[u8]) -> CodecResult<Self> {
87        let state = GifDecoderState::decode(data)?;
88        Ok(Self { state })
89    }
90
91    /// Get canvas width.
92    #[must_use]
93    pub fn width(&self) -> u32 {
94        u32::from(self.state.screen_descriptor.width)
95    }
96
97    /// Get canvas height.
98    #[must_use]
99    pub fn height(&self) -> u32 {
100        u32::from(self.state.screen_descriptor.height)
101    }
102
103    /// Get number of frames.
104    #[must_use]
105    pub fn frame_count(&self) -> usize {
106        self.state.frames.len()
107    }
108
109    /// Get loop count (0 = infinite).
110    #[must_use]
111    pub fn loop_count(&self) -> u16 {
112        self.state.loop_count
113    }
114
115    /// Get the logical screen descriptor.
116    #[must_use]
117    pub fn screen_descriptor(&self) -> &LogicalScreenDescriptor {
118        &self.state.screen_descriptor
119    }
120
121    /// Get the global color table.
122    #[must_use]
123    pub fn global_color_table(&self) -> &[u8] {
124        &self.state.global_color_table
125    }
126
127    /// Get frame information.
128    ///
129    /// # Arguments
130    ///
131    /// * `index` - Frame index
132    ///
133    /// # Errors
134    ///
135    /// Returns error if index is out of range.
136    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    /// Decode a specific frame.
143    ///
144    /// # Arguments
145    ///
146    /// * `index` - Frame index to decode
147    ///
148    /// # Errors
149    ///
150    /// Returns error if decoding fails or index is out of range.
151    pub fn decode_frame(&self, index: usize) -> CodecResult<VideoFrame> {
152        self.state.frame_to_video_frame(index)
153    }
154
155    /// Decode all frames.
156    ///
157    /// # Errors
158    ///
159    /// Returns error if decoding fails.
160    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    /// Get frame delay time in milliseconds.
169    ///
170    /// # Arguments
171    ///
172    /// * `index` - Frame index
173    ///
174    /// # Errors
175    ///
176    /// Returns error if index is out of range.
177    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            // Delay time is in hundredths of a second
181            Ok(u32::from(control.delay_time) * 10)
182        } else {
183            Ok(0)
184        }
185    }
186
187    /// Check if frame has transparency.
188    ///
189    /// # Arguments
190    ///
191    /// * `index` - Frame index
192    ///
193    /// # Errors
194    ///
195    /// Returns error if index is out of range.
196    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    /// Get frame disposal method.
202    ///
203    /// # Arguments
204    ///
205    /// * `index` - Frame index
206    ///
207    /// # Errors
208    ///
209    /// Returns error if index is out of range.
210    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    /// Check if this is an animated GIF.
220    #[must_use]
221    pub fn is_animated(&self) -> bool {
222        self.frame_count() > 1
223    }
224
225    /// Get total animation duration in milliseconds.
226    #[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
238/// GIF encoder for creating GIF89a files.
239///
240/// Supports:
241/// - Single images and animations
242/// - Color quantization with configurable palette size
243/// - Dithering options
244/// - Transparency
245/// - Loop count control
246pub struct GifEncoder {
247    state: GifEncoderState,
248}
249
250impl GifEncoder {
251    /// Create a new GIF encoder.
252    ///
253    /// # Arguments
254    ///
255    /// * `width` - Canvas width
256    /// * `height` - Canvas height
257    /// * `config` - Encoder configuration
258    ///
259    /// # Errors
260    ///
261    /// Returns error if parameters are invalid.
262    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    /// Encode frames to GIF data.
268    ///
269    /// # Arguments
270    ///
271    /// * `frames` - Frames to encode
272    /// * `frame_configs` - Configuration for each frame
273    ///
274    /// # Errors
275    ///
276    /// Returns error if encoding fails.
277    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    /// Encode a single frame (non-animated GIF).
286    ///
287    /// # Arguments
288    ///
289    /// * `frame` - Frame to encode
290    ///
291    /// # Errors
292    ///
293    /// Returns error if encoding fails.
294    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    /// Encode multiple frames with the same delay time.
300    ///
301    /// # Arguments
302    ///
303    /// * `frames` - Frames to encode
304    /// * `delay_ms` - Delay between frames in milliseconds
305    ///
306    /// # Errors
307    ///
308    /// Returns error if encoding fails.
309    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; // Convert to hundredths of a second
315        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/// Frame disposal method.
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub enum DisposalMethod {
327    /// No disposal specified.
328    None,
329    /// Do not dispose (keep frame).
330    Keep,
331    /// Restore to background color.
332    Background,
333    /// Restore to previous frame.
334    Previous,
335}
336
337impl DisposalMethod {
338    /// Convert from GIF disposal method value.
339    #[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    /// Convert to GIF disposal method value.
351    #[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/// Detect if data is a GIF file.
363///
364/// # Arguments
365///
366/// * `data` - Data to check
367///
368/// # Examples
369///
370/// ```ignore
371/// let data = std::fs::read("image.gif")?;
372/// if is_gif(&data) {
373///     let decoder = GifDecoder::new(&data)?;
374///     // ...
375/// }
376/// ```
377#[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); // Should have header + data
440    }
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        // Decode and verify
464        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}