Skip to main content

oximedia_codec/gif/
decoder.rs

1//! GIF decoder implementation.
2//!
3//! Supports GIF89a format with:
4//! - Interlaced and non-interlaced images
5//! - Local and global color tables
6//! - Transparency
7//! - Animation with multiple frames
8//! - Disposal methods
9
10use super::lzw::LzwDecoder;
11use crate::error::{CodecError, CodecResult};
12use crate::frame::{Plane, VideoFrame};
13use bytes::Bytes;
14use oximedia_core::PixelFormat;
15use std::io::{Cursor, Read};
16
17/// GIF signature.
18const GIF_SIGNATURE: &[u8] = b"GIF";
19
20/// GIF89a version.
21const GIF89A_VERSION: &[u8] = b"89a";
22
23/// GIF87a version.
24const GIF87A_VERSION: &[u8] = b"87a";
25
26/// Extension introducer.
27const EXTENSION_INTRODUCER: u8 = 0x21;
28
29/// Image separator.
30const IMAGE_SEPARATOR: u8 = 0x2C;
31
32/// Trailer (end of GIF).
33const TRAILER: u8 = 0x3B;
34
35/// Block terminator.
36#[allow(dead_code)]
37const BLOCK_TERMINATOR: u8 = 0x00;
38
39/// Graphics Control Extension label.
40const GRAPHICS_CONTROL_LABEL: u8 = 0xF9;
41
42/// Comment Extension label.
43const COMMENT_LABEL: u8 = 0xFE;
44
45/// Plain Text Extension label.
46const PLAIN_TEXT_LABEL: u8 = 0x01;
47
48/// Application Extension label.
49const APPLICATION_LABEL: u8 = 0xFF;
50
51/// Disposal method: No disposal specified.
52#[allow(dead_code)]
53const DISPOSAL_NONE: u8 = 0;
54
55/// Disposal method: Do not dispose (keep frame).
56#[allow(dead_code)]
57const DISPOSAL_KEEP: u8 = 1;
58
59/// Disposal method: Restore to background color.
60#[allow(dead_code)]
61const DISPOSAL_BACKGROUND: u8 = 2;
62
63/// Disposal method: Restore to previous frame.
64#[allow(dead_code)]
65const DISPOSAL_PREVIOUS: u8 = 3;
66
67/// Logical Screen Descriptor.
68#[derive(Debug, Clone)]
69pub struct LogicalScreenDescriptor {
70    /// Canvas width in pixels.
71    pub width: u16,
72    /// Canvas height in pixels.
73    pub height: u16,
74    /// Global color table flag.
75    pub has_global_color_table: bool,
76    /// Color resolution (bits per color minus 1).
77    pub color_resolution: u8,
78    /// Sort flag.
79    pub sort_flag: bool,
80    /// Size of global color table (log2(size) - 1).
81    pub global_color_table_size: u8,
82    /// Background color index.
83    pub background_color_index: u8,
84    /// Pixel aspect ratio.
85    pub pixel_aspect_ratio: u8,
86}
87
88/// Graphics Control Extension data.
89#[derive(Debug, Clone, Default)]
90pub struct GraphicsControlExtension {
91    /// Disposal method.
92    pub disposal_method: u8,
93    /// User input flag.
94    pub user_input_flag: bool,
95    /// Transparency flag.
96    pub has_transparency: bool,
97    /// Delay time in hundredths of a second.
98    pub delay_time: u16,
99    /// Transparent color index.
100    pub transparent_color_index: u8,
101}
102
103/// Image descriptor.
104#[derive(Debug, Clone)]
105pub struct ImageDescriptor {
106    /// Left position on canvas.
107    pub left: u16,
108    /// Top position on canvas.
109    pub top: u16,
110    /// Image width.
111    pub width: u16,
112    /// Image height.
113    pub height: u16,
114    /// Local color table flag.
115    pub has_local_color_table: bool,
116    /// Interlace flag.
117    pub interlaced: bool,
118    /// Sort flag.
119    pub sort_flag: bool,
120    /// Size of local color table (log2(size) - 1).
121    pub local_color_table_size: u8,
122}
123
124/// A single GIF frame.
125#[derive(Debug, Clone)]
126pub struct GifFrame {
127    /// Image descriptor.
128    pub descriptor: ImageDescriptor,
129    /// Graphics control extension (if present).
130    pub control: Option<GraphicsControlExtension>,
131    /// Color table (local or global).
132    pub color_table: Vec<u8>,
133    /// Decompressed pixel indices.
134    pub indices: Vec<u8>,
135}
136
137/// GIF decoder state.
138pub struct GifDecoderState {
139    /// Logical screen descriptor.
140    pub screen_descriptor: LogicalScreenDescriptor,
141    /// Global color table.
142    pub global_color_table: Vec<u8>,
143    /// Decoded frames.
144    pub frames: Vec<GifFrame>,
145    /// Loop count (0 = infinite).
146    pub loop_count: u16,
147    /// Background color (RGB).
148    pub background_color: [u8; 3],
149}
150
151impl GifDecoderState {
152    /// Decode a GIF file.
153    ///
154    /// # Errors
155    ///
156    /// Returns error if decoding fails or data is invalid.
157    #[allow(clippy::too_many_lines)]
158    pub fn decode(data: &[u8]) -> CodecResult<Self> {
159        let mut cursor = Cursor::new(data);
160
161        // Read and verify signature
162        let mut signature = [0u8; 3];
163        cursor
164            .read_exact(&mut signature)
165            .map_err(|_| CodecError::InvalidData("Failed to read GIF signature".into()))?;
166
167        if &signature != GIF_SIGNATURE {
168            return Err(CodecError::InvalidData("Invalid GIF signature".into()));
169        }
170
171        // Read and verify version
172        let mut version = [0u8; 3];
173        cursor
174            .read_exact(&mut version)
175            .map_err(|_| CodecError::InvalidData("Failed to read GIF version".into()))?;
176
177        if &version != GIF89A_VERSION && &version != GIF87A_VERSION {
178            return Err(CodecError::InvalidData(format!(
179                "Unsupported GIF version: {:?}",
180                version
181            )));
182        }
183
184        // Read Logical Screen Descriptor
185        let screen_descriptor = Self::read_screen_descriptor(&mut cursor)?;
186
187        // Read Global Color Table if present
188        let global_color_table = if screen_descriptor.has_global_color_table {
189            let size = Self::color_table_size(screen_descriptor.global_color_table_size);
190            Self::read_color_table(&mut cursor, size)?
191        } else {
192            Vec::new()
193        };
194
195        let mut state = Self {
196            screen_descriptor: screen_descriptor.clone(),
197            global_color_table: global_color_table.clone(),
198            frames: Vec::new(),
199            loop_count: 1,
200            background_color: Self::get_background_color(&screen_descriptor, &global_color_table),
201        };
202
203        // Parse data stream
204        let mut current_gce: Option<GraphicsControlExtension> = None;
205
206        loop {
207            let mut block_type = [0u8; 1];
208            if cursor.read_exact(&mut block_type).is_err() {
209                break;
210            }
211
212            match block_type[0] {
213                IMAGE_SEPARATOR => {
214                    let frame = Self::read_image(
215                        &mut cursor,
216                        &screen_descriptor,
217                        &global_color_table,
218                        current_gce.take(),
219                    )?;
220                    state.frames.push(frame);
221                }
222                EXTENSION_INTRODUCER => {
223                    let ext = Self::read_extension(&mut cursor)?;
224                    match ext {
225                        Extension::GraphicsControl(gce) => {
226                            current_gce = Some(gce);
227                        }
228                        Extension::Application(app) => {
229                            if let Some(loop_count) = Self::parse_netscape_extension(&app) {
230                                state.loop_count = loop_count;
231                            }
232                        }
233                        Extension::Comment(_) | Extension::PlainText(_) => {
234                            // Ignore comments and plain text
235                        }
236                    }
237                }
238                TRAILER => break,
239                _ => {
240                    return Err(CodecError::InvalidData(format!(
241                        "Unknown block type: 0x{:02X}",
242                        block_type[0]
243                    )))
244                }
245            }
246        }
247
248        if state.frames.is_empty() {
249            return Err(CodecError::InvalidData("No frames found in GIF".into()));
250        }
251
252        Ok(state)
253    }
254
255    /// Read Logical Screen Descriptor.
256    fn read_screen_descriptor(cursor: &mut Cursor<&[u8]>) -> CodecResult<LogicalScreenDescriptor> {
257        let mut buf = [0u8; 7];
258        cursor
259            .read_exact(&mut buf)
260            .map_err(|_| CodecError::InvalidData("Failed to read screen descriptor".into()))?;
261
262        let width = u16::from_le_bytes([buf[0], buf[1]]);
263        let height = u16::from_le_bytes([buf[2], buf[3]]);
264        let packed = buf[4];
265        let background_color_index = buf[5];
266        let pixel_aspect_ratio = buf[6];
267
268        let has_global_color_table = (packed & 0x80) != 0;
269        let color_resolution = ((packed & 0x70) >> 4) + 1;
270        let sort_flag = (packed & 0x08) != 0;
271        let global_color_table_size = packed & 0x07;
272
273        Ok(LogicalScreenDescriptor {
274            width,
275            height,
276            has_global_color_table,
277            color_resolution,
278            sort_flag,
279            global_color_table_size,
280            background_color_index,
281            pixel_aspect_ratio,
282        })
283    }
284
285    /// Read color table.
286    fn read_color_table(cursor: &mut Cursor<&[u8]>, size: usize) -> CodecResult<Vec<u8>> {
287        let mut table = vec![0u8; size * 3];
288        cursor
289            .read_exact(&mut table)
290            .map_err(|_| CodecError::InvalidData("Failed to read color table".into()))?;
291        Ok(table)
292    }
293
294    /// Calculate color table size from size field.
295    fn color_table_size(size_field: u8) -> usize {
296        1 << (size_field + 1)
297    }
298
299    /// Get background color from screen descriptor and global color table.
300    fn get_background_color(
301        descriptor: &LogicalScreenDescriptor,
302        global_color_table: &[u8],
303    ) -> [u8; 3] {
304        if descriptor.has_global_color_table {
305            let idx = descriptor.background_color_index as usize * 3;
306            if idx + 2 < global_color_table.len() {
307                return [
308                    global_color_table[idx],
309                    global_color_table[idx + 1],
310                    global_color_table[idx + 2],
311                ];
312            }
313        }
314        [0, 0, 0]
315    }
316
317    /// Read extension block.
318    fn read_extension(cursor: &mut Cursor<&[u8]>) -> CodecResult<Extension> {
319        let mut label = [0u8; 1];
320        cursor
321            .read_exact(&mut label)
322            .map_err(|_| CodecError::InvalidData("Failed to read extension label".into()))?;
323
324        match label[0] {
325            GRAPHICS_CONTROL_LABEL => {
326                let gce = Self::read_graphics_control_extension(cursor)?;
327                Ok(Extension::GraphicsControl(gce))
328            }
329            APPLICATION_LABEL => {
330                let data = Self::read_data_sub_blocks(cursor)?;
331                Ok(Extension::Application(data))
332            }
333            COMMENT_LABEL => {
334                let data = Self::read_data_sub_blocks(cursor)?;
335                Ok(Extension::Comment(data))
336            }
337            PLAIN_TEXT_LABEL => {
338                let data = Self::read_data_sub_blocks(cursor)?;
339                Ok(Extension::PlainText(data))
340            }
341            _ => {
342                // Skip unknown extension
343                Self::read_data_sub_blocks(cursor)?;
344                Ok(Extension::Comment(Vec::new()))
345            }
346        }
347    }
348
349    /// Read Graphics Control Extension.
350    fn read_graphics_control_extension(
351        cursor: &mut Cursor<&[u8]>,
352    ) -> CodecResult<GraphicsControlExtension> {
353        let mut buf = [0u8; 6];
354        cursor
355            .read_exact(&mut buf)
356            .map_err(|_| CodecError::InvalidData("Failed to read GCE".into()))?;
357
358        let _block_size = buf[0];
359        let packed = buf[1];
360        let delay_time = u16::from_le_bytes([buf[2], buf[3]]);
361        let transparent_color_index = buf[4];
362        let _terminator = buf[5];
363
364        let disposal_method = (packed & 0x1C) >> 2;
365        let user_input_flag = (packed & 0x02) != 0;
366        let has_transparency = (packed & 0x01) != 0;
367
368        Ok(GraphicsControlExtension {
369            disposal_method,
370            user_input_flag,
371            has_transparency,
372            delay_time,
373            transparent_color_index,
374        })
375    }
376
377    /// Read data sub-blocks.
378    fn read_data_sub_blocks(cursor: &mut Cursor<&[u8]>) -> CodecResult<Vec<u8>> {
379        let mut data = Vec::new();
380        loop {
381            let mut block_size = [0u8; 1];
382            cursor
383                .read_exact(&mut block_size)
384                .map_err(|_| CodecError::InvalidData("Failed to read block size".into()))?;
385
386            if block_size[0] == 0 {
387                break;
388            }
389
390            let size = block_size[0] as usize;
391            let start_len = data.len();
392            data.resize(start_len + size, 0);
393            cursor
394                .read_exact(&mut data[start_len..])
395                .map_err(|_| CodecError::InvalidData("Failed to read data block".into()))?;
396        }
397        Ok(data)
398    }
399
400    /// Parse Netscape extension (NETSCAPE2.0) for loop count.
401    fn parse_netscape_extension(data: &[u8]) -> Option<u16> {
402        if data.len() >= 11 && &data[0..11] == b"NETSCAPE2.0" {
403            if data.len() >= 16 && data[11] == 3 && data[12] == 1 {
404                return Some(u16::from_le_bytes([data[13], data[14]]));
405            }
406        }
407        None
408    }
409
410    /// Read image data.
411    #[allow(clippy::too_many_lines)]
412    fn read_image(
413        cursor: &mut Cursor<&[u8]>,
414        screen_descriptor: &LogicalScreenDescriptor,
415        global_color_table: &[u8],
416        control: Option<GraphicsControlExtension>,
417    ) -> CodecResult<GifFrame> {
418        // Read Image Descriptor
419        let mut buf = [0u8; 9];
420        cursor
421            .read_exact(&mut buf)
422            .map_err(|_| CodecError::InvalidData("Failed to read image descriptor".into()))?;
423
424        let left = u16::from_le_bytes([buf[0], buf[1]]);
425        let top = u16::from_le_bytes([buf[2], buf[3]]);
426        let width = u16::from_le_bytes([buf[4], buf[5]]);
427        let height = u16::from_le_bytes([buf[6], buf[7]]);
428        let packed = buf[8];
429
430        let has_local_color_table = (packed & 0x80) != 0;
431        let interlaced = (packed & 0x40) != 0;
432        let sort_flag = (packed & 0x20) != 0;
433        let local_color_table_size = packed & 0x07;
434
435        let descriptor = ImageDescriptor {
436            left,
437            top,
438            width,
439            height,
440            has_local_color_table,
441            interlaced,
442            sort_flag,
443            local_color_table_size,
444        };
445
446        // Read Local Color Table if present
447        let color_table = if has_local_color_table {
448            let size = Self::color_table_size(local_color_table_size);
449            Self::read_color_table(cursor, size)?
450        } else {
451            global_color_table.to_vec()
452        };
453
454        // Read LZW minimum code size
455        let mut lzw_min_code_size = [0u8; 1];
456        cursor
457            .read_exact(&mut lzw_min_code_size)
458            .map_err(|_| CodecError::InvalidData("Failed to read LZW code size".into()))?;
459
460        // Read image data sub-blocks
461        let compressed_data = Self::read_data_sub_blocks(cursor)?;
462
463        // Decompress image data
464        let mut decoder = LzwDecoder::new(lzw_min_code_size[0])?;
465        let expected_size = (width as usize) * (height as usize);
466        let mut indices = decoder.decompress(&compressed_data, expected_size)?;
467
468        // Deinterlace if needed
469        if interlaced {
470            indices = Self::deinterlace(&indices, width, height)?;
471        }
472
473        Ok(GifFrame {
474            descriptor,
475            control,
476            color_table,
477            indices,
478        })
479    }
480
481    /// Deinterlace image data.
482    fn deinterlace(indices: &[u8], width: u16, height: u16) -> CodecResult<Vec<u8>> {
483        let width = width as usize;
484        let height = height as usize;
485        let mut deinterlaced = vec![0u8; width * height];
486
487        // GIF interlacing uses 4 passes
488        let passes = [
489            (0, 8), // Pass 1: every 8th row, starting with row 0
490            (4, 8), // Pass 2: every 8th row, starting with row 4
491            (2, 4), // Pass 3: every 4th row, starting with row 2
492            (1, 2), // Pass 4: every 2nd row, starting with row 1
493        ];
494
495        let mut src_idx = 0;
496        for (start, step) in &passes {
497            let mut y = *start;
498            while y < height {
499                if src_idx + width <= indices.len() {
500                    let dst_idx = y * width;
501                    deinterlaced[dst_idx..dst_idx + width]
502                        .copy_from_slice(&indices[src_idx..src_idx + width]);
503                    src_idx += width;
504                }
505                y += step;
506            }
507        }
508
509        Ok(deinterlaced)
510    }
511
512    /// Convert a GIF frame to a VideoFrame.
513    pub fn frame_to_video_frame(&self, frame_index: usize) -> CodecResult<VideoFrame> {
514        if frame_index >= self.frames.len() {
515            return Err(CodecError::InvalidParameter(format!(
516                "Frame index {} out of range (total: {})",
517                frame_index,
518                self.frames.len()
519            )));
520        }
521
522        let frame = &self.frames[frame_index];
523        let width = self.screen_descriptor.width as u32;
524        let height = self.screen_descriptor.height as u32;
525
526        // Convert indexed colors to RGBA
527        let mut rgba_data = vec![0u8; (width * height * 4) as usize];
528
529        // Fill with background color initially
530        for y in 0..height as usize {
531            for x in 0..width as usize {
532                let idx = (y * width as usize + x) * 4;
533                rgba_data[idx] = self.background_color[0];
534                rgba_data[idx + 1] = self.background_color[1];
535                rgba_data[idx + 2] = self.background_color[2];
536                rgba_data[idx + 3] = 255;
537            }
538        }
539
540        // Draw the frame
541        let frame_width = frame.descriptor.width as usize;
542        let frame_height = frame.descriptor.height as usize;
543        let left = frame.descriptor.left as usize;
544        let top = frame.descriptor.top as usize;
545
546        for y in 0..frame_height {
547            for x in 0..frame_width {
548                let canvas_x = left + x;
549                let canvas_y = top + y;
550
551                if canvas_x >= width as usize || canvas_y >= height as usize {
552                    continue;
553                }
554
555                let src_idx = y * frame_width + x;
556                if src_idx >= frame.indices.len() {
557                    continue;
558                }
559
560                let color_index = frame.indices[src_idx] as usize;
561
562                // Check for transparency
563                if let Some(ref control) = frame.control {
564                    if control.has_transparency
565                        && color_index == control.transparent_color_index as usize
566                    {
567                        continue;
568                    }
569                }
570
571                // Get color from palette
572                let color_offset = color_index * 3;
573                if color_offset + 2 < frame.color_table.len() {
574                    let dst_idx = (canvas_y * width as usize + canvas_x) * 4;
575                    rgba_data[dst_idx] = frame.color_table[color_offset];
576                    rgba_data[dst_idx + 1] = frame.color_table[color_offset + 1];
577                    rgba_data[dst_idx + 2] = frame.color_table[color_offset + 2];
578                    rgba_data[dst_idx + 3] = 255;
579                }
580            }
581        }
582
583        let stride = (width * 4) as usize;
584        let plane = Plane {
585            data: rgba_data,
586            stride,
587            width,
588            height,
589        };
590
591        let mut video_frame = VideoFrame::new(PixelFormat::Rgba32, width, height);
592        video_frame.planes = vec![plane];
593
594        Ok(video_frame)
595    }
596}
597
598/// Extension types.
599#[derive(Debug)]
600enum Extension {
601    /// Graphics Control Extension.
602    GraphicsControl(GraphicsControlExtension),
603    /// Application Extension.
604    Application(Vec<u8>),
605    /// Comment Extension.
606    #[allow(dead_code)]
607    Comment(Vec<u8>),
608    /// Plain Text Extension.
609    #[allow(dead_code)]
610    PlainText(Vec<u8>),
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    #[test]
618    fn test_color_table_size() {
619        assert_eq!(GifDecoderState::color_table_size(0), 2);
620        assert_eq!(GifDecoderState::color_table_size(1), 4);
621        assert_eq!(GifDecoderState::color_table_size(7), 256);
622    }
623
624    #[test]
625    fn test_deinterlace() {
626        let width = 4;
627        let height = 4;
628        let indices: Vec<u8> = (0..16).collect();
629        let result = GifDecoderState::deinterlace(&indices, width, height).expect("should succeed");
630        assert_eq!(result.len(), 16);
631    }
632}