1use 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
17const GIF_SIGNATURE: &[u8] = b"GIF";
19
20const GIF89A_VERSION: &[u8] = b"89a";
22
23const GIF87A_VERSION: &[u8] = b"87a";
25
26const EXTENSION_INTRODUCER: u8 = 0x21;
28
29const IMAGE_SEPARATOR: u8 = 0x2C;
31
32const TRAILER: u8 = 0x3B;
34
35#[allow(dead_code)]
37const BLOCK_TERMINATOR: u8 = 0x00;
38
39const GRAPHICS_CONTROL_LABEL: u8 = 0xF9;
41
42const COMMENT_LABEL: u8 = 0xFE;
44
45const PLAIN_TEXT_LABEL: u8 = 0x01;
47
48const APPLICATION_LABEL: u8 = 0xFF;
50
51#[allow(dead_code)]
53const DISPOSAL_NONE: u8 = 0;
54
55#[allow(dead_code)]
57const DISPOSAL_KEEP: u8 = 1;
58
59#[allow(dead_code)]
61const DISPOSAL_BACKGROUND: u8 = 2;
62
63#[allow(dead_code)]
65const DISPOSAL_PREVIOUS: u8 = 3;
66
67#[derive(Debug, Clone)]
69pub struct LogicalScreenDescriptor {
70 pub width: u16,
72 pub height: u16,
74 pub has_global_color_table: bool,
76 pub color_resolution: u8,
78 pub sort_flag: bool,
80 pub global_color_table_size: u8,
82 pub background_color_index: u8,
84 pub pixel_aspect_ratio: u8,
86}
87
88#[derive(Debug, Clone, Default)]
90pub struct GraphicsControlExtension {
91 pub disposal_method: u8,
93 pub user_input_flag: bool,
95 pub has_transparency: bool,
97 pub delay_time: u16,
99 pub transparent_color_index: u8,
101}
102
103#[derive(Debug, Clone)]
105pub struct ImageDescriptor {
106 pub left: u16,
108 pub top: u16,
110 pub width: u16,
112 pub height: u16,
114 pub has_local_color_table: bool,
116 pub interlaced: bool,
118 pub sort_flag: bool,
120 pub local_color_table_size: u8,
122}
123
124#[derive(Debug, Clone)]
126pub struct GifFrame {
127 pub descriptor: ImageDescriptor,
129 pub control: Option<GraphicsControlExtension>,
131 pub color_table: Vec<u8>,
133 pub indices: Vec<u8>,
135}
136
137pub struct GifDecoderState {
139 pub screen_descriptor: LogicalScreenDescriptor,
141 pub global_color_table: Vec<u8>,
143 pub frames: Vec<GifFrame>,
145 pub loop_count: u16,
147 pub background_color: [u8; 3],
149}
150
151impl GifDecoderState {
152 #[allow(clippy::too_many_lines)]
158 pub fn decode(data: &[u8]) -> CodecResult<Self> {
159 let mut cursor = Cursor::new(data);
160
161 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 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 let screen_descriptor = Self::read_screen_descriptor(&mut cursor)?;
186
187 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 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 }
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 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 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 fn color_table_size(size_field: u8) -> usize {
296 1 << (size_field + 1)
297 }
298
299 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 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 Self::read_data_sub_blocks(cursor)?;
344 Ok(Extension::Comment(Vec::new()))
345 }
346 }
347 }
348
349 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 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 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 #[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 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 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 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 let compressed_data = Self::read_data_sub_blocks(cursor)?;
462
463 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 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 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 let passes = [
489 (0, 8), (4, 8), (2, 4), (1, 2), ];
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 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 let mut rgba_data = vec![0u8; (width * height * 4) as usize];
528
529 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 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 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 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#[derive(Debug)]
600enum Extension {
601 GraphicsControl(GraphicsControlExtension),
603 Application(Vec<u8>),
605 #[allow(dead_code)]
607 Comment(Vec<u8>),
608 #[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}