we (web engine): Experimental web browser project to understand the limits of Claude
at css-loading 1879 lines 59 kB view raw
1//! GIF decoder (GIF87a / GIF89a) — pure Rust. 2//! 3//! Supports single-frame and animated GIFs, global and local color tables, 4//! LZW decompression, transparency via Graphic Control Extension, interlacing, 5//! and frame disposal methods. 6 7use crate::pixel::{self, Image, ImageError}; 8 9// --------------------------------------------------------------------------- 10// Constants 11// --------------------------------------------------------------------------- 12 13const GIF87A: &[u8; 6] = b"GIF87a"; 14const GIF89A: &[u8; 6] = b"GIF89a"; 15 16const IMAGE_DESCRIPTOR: u8 = 0x2C; 17const EXTENSION_INTRODUCER: u8 = 0x21; 18const TRAILER: u8 = 0x3B; 19 20const GRAPHIC_CONTROL_EXT: u8 = 0xF9; 21const APPLICATION_EXT: u8 = 0xFF; 22const COMMENT_EXT: u8 = 0xFE; 23const PLAIN_TEXT_EXT: u8 = 0x01; 24 25// --------------------------------------------------------------------------- 26// Disposal method 27// --------------------------------------------------------------------------- 28 29/// How the decoder should handle the frame's area before drawing the next frame. 30#[derive(Debug, Clone, Copy, PartialEq, Eq)] 31pub enum DisposalMethod { 32 /// No disposal specified — leave the frame in place. 33 None, 34 /// Do not dispose — leave the frame in place (same as None). 35 DoNotDispose, 36 /// Restore the area to the background color. 37 RestoreBackground, 38 /// Restore the area to the previous frame's content. 39 RestorePrevious, 40} 41 42impl DisposalMethod { 43 fn from_byte(b: u8) -> Self { 44 match b { 45 0 => Self::None, 46 1 => Self::DoNotDispose, 47 2 => Self::RestoreBackground, 48 3 => Self::RestorePrevious, 49 _ => Self::None, 50 } 51 } 52} 53 54// --------------------------------------------------------------------------- 55// GifFrame 56// --------------------------------------------------------------------------- 57 58/// A single frame from an animated GIF. 59#[derive(Debug, Clone, PartialEq, Eq)] 60pub struct GifFrame { 61 /// The frame image in RGBA8. 62 pub image: Image, 63 /// Delay time in hundredths of a second (centiseconds). 64 pub delay_cs: u16, 65 /// Disposal method for this frame. 66 pub disposal: DisposalMethod, 67} 68 69// --------------------------------------------------------------------------- 70// LZW decompressor 71// --------------------------------------------------------------------------- 72 73/// LZW decompressor for GIF image data. 74/// 75/// GIF uses a variable-width LZW scheme with clear and EOI codes. The minimum 76/// code size is specified per sub-image. Codes are packed LSB-first. 77struct LzwDecoder { 78 min_code_size: u8, 79 clear_code: u16, 80 eoi_code: u16, 81 /// Current entries in the code table. Each entry is stored as 82 /// (prefix_code, suffix_byte). Single-byte entries have prefix = u16::MAX. 83 table: Vec<(u16, u8)>, 84 code_size: u8, 85 next_code: u16, 86} 87 88impl LzwDecoder { 89 fn new(min_code_size: u8) -> Self { 90 let clear_code = 1u16 << min_code_size; 91 let eoi_code = clear_code + 1; 92 Self { 93 min_code_size, 94 clear_code, 95 eoi_code, 96 table: Vec::new(), 97 code_size: min_code_size + 1, 98 next_code: 0, 99 } 100 } 101 102 fn reset(&mut self) { 103 self.table.clear(); 104 let initial_entries = (1u16 << self.min_code_size) + 2; 105 self.table.reserve(initial_entries as usize); 106 for i in 0..self.clear_code { 107 self.table.push((u16::MAX, i as u8)); 108 } 109 // Clear code and EOI code entries (not used for output, but occupy slots) 110 self.table.push((u16::MAX, 0)); // clear code 111 self.table.push((u16::MAX, 0)); // eoi code 112 self.code_size = self.min_code_size + 1; 113 self.next_code = self.eoi_code + 1; 114 } 115 116 /// Emit the string for a code into `output`. Returns the first byte of the string. 117 fn emit(&self, code: u16, output: &mut Vec<u8>) -> u8 { 118 // Walk the chain to find the string length, then write in reverse. 119 let start = output.len(); 120 let mut c = code; 121 loop { 122 let (prefix, suffix) = self.table[c as usize]; 123 output.push(suffix); 124 if prefix == u16::MAX { 125 break; 126 } 127 c = prefix; 128 } 129 // Reverse the appended portion 130 output[start..].reverse(); 131 output[start] 132 } 133 134 /// Get the first byte of the string for a code. 135 fn first_byte(&self, code: u16) -> u8 { 136 let mut c = code; 137 loop { 138 let (prefix, suffix) = self.table[c as usize]; 139 if prefix == u16::MAX { 140 return suffix; 141 } 142 c = prefix; 143 } 144 } 145 146 fn add_entry(&mut self, prefix: u16, suffix: u8) { 147 if self.next_code < 4096 { 148 self.table.push((prefix, suffix)); 149 self.next_code += 1; 150 // Increase code size when all codes at the current size are used (early change) 151 if self.next_code >= (1 << self.code_size) && self.code_size < 12 { 152 self.code_size += 1; 153 } 154 } 155 } 156 157 /// Decompress LZW data from packed sub-blocks. 158 fn decompress(&mut self, data: &[u8]) -> Result<Vec<u8>, ImageError> { 159 self.reset(); 160 let mut reader = LzwBitReader::new(data); 161 let mut output = Vec::new(); 162 163 // First code must be a clear code 164 let first = reader.read_bits(self.code_size)?; 165 if first != self.clear_code { 166 return Err(ImageError::Decode( 167 "GIF LZW: expected clear code at start".into(), 168 )); 169 } 170 171 // Read the first data code after clear 172 let mut prev_code = reader.read_bits(self.code_size)?; 173 if prev_code == self.eoi_code { 174 return Ok(output); 175 } 176 if prev_code >= self.next_code { 177 return Err(ImageError::Decode("GIF LZW: invalid first code".into())); 178 } 179 self.emit(prev_code, &mut output); 180 181 while let Ok(code) = reader.read_bits(self.code_size) { 182 if code == self.eoi_code { 183 break; 184 } 185 186 if code == self.clear_code { 187 self.reset(); 188 prev_code = match reader.read_bits(self.code_size) { 189 Ok(c) => c, 190 Err(_) => break, 191 }; 192 if prev_code == self.eoi_code { 193 break; 194 } 195 if prev_code >= self.next_code { 196 return Err(ImageError::Decode( 197 "GIF LZW: invalid code after clear".into(), 198 )); 199 } 200 self.emit(prev_code, &mut output); 201 continue; 202 } 203 204 if code < self.next_code { 205 // Code is in the table 206 let first = self.first_byte(code); 207 self.add_entry(prev_code, first); 208 self.emit(code, &mut output); 209 } else if code == self.next_code { 210 // Special case: code is not yet in table 211 let first = self.first_byte(prev_code); 212 self.add_entry(prev_code, first); 213 self.emit(code, &mut output); 214 } else { 215 return Err(ImageError::Decode(format!( 216 "GIF LZW: code {code} out of range (next={next})", 217 next = self.next_code 218 ))); 219 } 220 221 prev_code = code; 222 } 223 224 Ok(output) 225 } 226} 227 228// --------------------------------------------------------------------------- 229// Bit reader for LZW (LSB-first, packed) 230// --------------------------------------------------------------------------- 231 232struct LzwBitReader<'a> { 233 data: &'a [u8], 234 pos: usize, 235 bit_buf: u32, 236 bits_in_buf: u8, 237} 238 239impl<'a> LzwBitReader<'a> { 240 fn new(data: &'a [u8]) -> Self { 241 Self { 242 data, 243 pos: 0, 244 bit_buf: 0, 245 bits_in_buf: 0, 246 } 247 } 248 249 fn read_bits(&mut self, count: u8) -> Result<u16, ImageError> { 250 while self.bits_in_buf < count { 251 if self.pos >= self.data.len() { 252 return Err(ImageError::Decode("GIF LZW: unexpected end of data".into())); 253 } 254 self.bit_buf |= (self.data[self.pos] as u32) << self.bits_in_buf; 255 self.pos += 1; 256 self.bits_in_buf += 8; 257 } 258 let mask = (1u32 << count) - 1; 259 let value = (self.bit_buf & mask) as u16; 260 self.bit_buf >>= count; 261 self.bits_in_buf -= count; 262 Ok(value) 263 } 264} 265 266// --------------------------------------------------------------------------- 267// GIF parser internals 268// --------------------------------------------------------------------------- 269 270struct GifReader<'a> { 271 data: &'a [u8], 272 pos: usize, 273} 274 275impl<'a> GifReader<'a> { 276 fn new(data: &'a [u8]) -> Self { 277 Self { data, pos: 0 } 278 } 279 280 fn remaining(&self) -> usize { 281 self.data.len().saturating_sub(self.pos) 282 } 283 284 fn read_byte(&mut self) -> Result<u8, ImageError> { 285 if self.pos >= self.data.len() { 286 return Err(ImageError::Decode("GIF: unexpected end of data".into())); 287 } 288 let b = self.data[self.pos]; 289 self.pos += 1; 290 Ok(b) 291 } 292 293 fn read_u16_le(&mut self) -> Result<u16, ImageError> { 294 if self.pos + 2 > self.data.len() { 295 return Err(ImageError::Decode("GIF: unexpected end of data".into())); 296 } 297 let lo = self.data[self.pos] as u16; 298 let hi = self.data[self.pos + 1] as u16; 299 self.pos += 2; 300 Ok(lo | (hi << 8)) 301 } 302 303 fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], ImageError> { 304 if self.pos + n > self.data.len() { 305 return Err(ImageError::Decode("GIF: unexpected end of data".into())); 306 } 307 let slice = &self.data[self.pos..self.pos + n]; 308 self.pos += n; 309 Ok(slice) 310 } 311 312 /// Read concatenated sub-blocks (size-prefixed, terminated by a zero-length block). 313 fn read_sub_blocks(&mut self) -> Result<Vec<u8>, ImageError> { 314 let mut result = Vec::new(); 315 loop { 316 let block_size = self.read_byte()? as usize; 317 if block_size == 0 { 318 break; 319 } 320 let block = self.read_bytes(block_size)?; 321 result.extend_from_slice(block); 322 } 323 Ok(result) 324 } 325 326 /// Skip sub-blocks without collecting data. 327 fn skip_sub_blocks(&mut self) -> Result<(), ImageError> { 328 loop { 329 let block_size = self.read_byte()? as usize; 330 if block_size == 0 { 331 break; 332 } 333 if self.pos + block_size > self.data.len() { 334 return Err(ImageError::Decode("GIF: unexpected end of data".into())); 335 } 336 self.pos += block_size; 337 } 338 Ok(()) 339 } 340} 341 342// --------------------------------------------------------------------------- 343// Graphic Control Extension 344// --------------------------------------------------------------------------- 345 346struct GraphicControl { 347 disposal: DisposalMethod, 348 transparent_color: Option<u8>, 349 delay_cs: u16, 350} 351 352// --------------------------------------------------------------------------- 353// Logical Screen Descriptor 354// --------------------------------------------------------------------------- 355 356struct ScreenDescriptor { 357 width: u16, 358 height: u16, 359 global_color_table: Option<Vec<u8>>, 360 background_index: u8, 361} 362 363fn parse_header_and_screen(reader: &mut GifReader<'_>) -> Result<ScreenDescriptor, ImageError> { 364 // Validate signature 365 if reader.remaining() < 6 { 366 return Err(ImageError::Decode("GIF: data too short for header".into())); 367 } 368 let sig = reader.read_bytes(6)?; 369 if sig != GIF87A.as_slice() && sig != GIF89A.as_slice() { 370 return Err(ImageError::Decode("GIF: invalid signature".into())); 371 } 372 373 // Logical screen descriptor (7 bytes) 374 let width = reader.read_u16_le()?; 375 let height = reader.read_u16_le()?; 376 let packed = reader.read_byte()?; 377 let background_index = reader.read_byte()?; 378 let _pixel_aspect_ratio = reader.read_byte()?; 379 380 let has_gct = (packed & 0x80) != 0; 381 let gct_size_field = packed & 0x07; 382 383 let global_color_table = if has_gct { 384 let num_entries = 1usize << (gct_size_field as usize + 1); 385 let table_bytes = num_entries * 3; 386 let table = reader.read_bytes(table_bytes)?; 387 Some(table.to_vec()) 388 } else { 389 None 390 }; 391 392 Ok(ScreenDescriptor { 393 width, 394 height, 395 global_color_table, 396 background_index, 397 }) 398} 399 400// --------------------------------------------------------------------------- 401// GIF interlace pass reordering 402// --------------------------------------------------------------------------- 403 404/// GIF interlaced images use 4 passes with these parameters: 405/// Pass 1: start row 0, step 8 406/// Pass 2: start row 4, step 8 407/// Pass 3: start row 2, step 4 408/// Pass 4: start row 1, step 2 409const INTERLACE_PASSES: [(usize, usize); 4] = [(0, 8), (4, 8), (2, 4), (1, 2)]; 410 411fn deinterlace(data: &[u8], width: usize, height: usize) -> Vec<u8> { 412 let row_bytes = width; 413 let mut output = vec![0u8; width * height]; 414 let mut src_row = 0usize; 415 416 for &(start, step) in &INTERLACE_PASSES { 417 let mut y = start; 418 while y < height { 419 let src_offset = src_row * row_bytes; 420 let dst_offset = y * row_bytes; 421 if src_offset + row_bytes <= data.len() { 422 output[dst_offset..dst_offset + row_bytes] 423 .copy_from_slice(&data[src_offset..src_offset + row_bytes]); 424 } 425 src_row += 1; 426 y += step; 427 } 428 } 429 430 output 431} 432 433// --------------------------------------------------------------------------- 434// Frame decoding 435// --------------------------------------------------------------------------- 436 437struct RawFrame { 438 left: u16, 439 top: u16, 440 width: u16, 441 height: u16, 442 color_table: Vec<u8>, 443 transparent_color: Option<u8>, 444 disposal: DisposalMethod, 445 delay_cs: u16, 446 /// Decompressed indexed pixel data. 447 indices: Vec<u8>, 448} 449 450fn decode_frame( 451 reader: &mut GifReader<'_>, 452 global_ct: &Option<Vec<u8>>, 453 gce: Option<GraphicControl>, 454) -> Result<RawFrame, ImageError> { 455 // Image descriptor 456 let left = reader.read_u16_le()?; 457 let top = reader.read_u16_le()?; 458 let width = reader.read_u16_le()?; 459 let height = reader.read_u16_le()?; 460 let packed = reader.read_byte()?; 461 462 let has_lct = (packed & 0x80) != 0; 463 let interlaced = (packed & 0x40) != 0; 464 let lct_size_field = packed & 0x07; 465 466 let local_color_table = if has_lct { 467 let num_entries = 1usize << (lct_size_field as usize + 1); 468 let table_bytes = num_entries * 3; 469 let table = reader.read_bytes(table_bytes)?; 470 table.to_vec() 471 } else { 472 Vec::new() 473 }; 474 475 // Determine which color table to use 476 let color_table = if !local_color_table.is_empty() { 477 local_color_table 478 } else if let Some(ref gct) = global_ct { 479 gct.clone() 480 } else { 481 return Err(ImageError::Decode("GIF: no color table available".into())); 482 }; 483 484 // LZW minimum code size 485 let min_code_size = reader.read_byte()?; 486 if min_code_size > 11 { 487 return Err(ImageError::Decode(format!( 488 "GIF: invalid LZW minimum code size {min_code_size}" 489 ))); 490 } 491 492 // Read LZW sub-blocks 493 let lzw_data = reader.read_sub_blocks()?; 494 495 // Decompress 496 let mut decoder = LzwDecoder::new(min_code_size); 497 let mut indices = decoder.decompress(&lzw_data)?; 498 499 // Deinterlace if needed 500 if interlaced && width > 0 && height > 0 { 501 indices = deinterlace(&indices, width as usize, height as usize); 502 } 503 504 let (disposal, transparent_color, delay_cs) = match gce { 505 Some(gc) => (gc.disposal, gc.transparent_color, gc.delay_cs), 506 None => (DisposalMethod::None, None, 0), 507 }; 508 509 Ok(RawFrame { 510 left, 511 top, 512 width, 513 height, 514 color_table, 515 transparent_color, 516 disposal, 517 delay_cs, 518 indices, 519 }) 520} 521 522/// Render a raw frame's indices into an RGBA8 image. 523fn render_frame(frame: &RawFrame) -> Result<Image, ImageError> { 524 let w = frame.width as u32; 525 let h = frame.height as u32; 526 if w == 0 || h == 0 { 527 return Err(ImageError::Decode("GIF: frame has zero dimension".into())); 528 } 529 530 let pixel_count = w as usize * h as usize; 531 let palette = &frame.color_table; 532 let palette_len = palette.len() / 3; 533 534 // Truncate indices to expected pixel count (some GIFs have extra data) 535 let indices = if frame.indices.len() >= pixel_count { 536 &frame.indices[..pixel_count] 537 } else { 538 // Pad with zeros if data is short 539 return render_frame_padded(frame, pixel_count, palette, palette_len); 540 }; 541 542 match frame.transparent_color { 543 Some(tc) => { 544 let mut data = Vec::with_capacity(pixel_count * 4); 545 for &idx in indices { 546 if idx == tc { 547 data.extend_from_slice(&[0, 0, 0, 0]); 548 } else { 549 let i = idx as usize; 550 if i >= palette_len { 551 return Err(ImageError::PaletteIndexOutOfBounds { 552 index: idx, 553 palette_len, 554 }); 555 } 556 let offset = i * 3; 557 data.push(palette[offset]); 558 data.push(palette[offset + 1]); 559 data.push(palette[offset + 2]); 560 data.push(255); 561 } 562 } 563 Image::new(w, h, data) 564 } 565 None => pixel::from_indexed(w, h, palette, indices), 566 } 567} 568 569fn render_frame_padded( 570 frame: &RawFrame, 571 pixel_count: usize, 572 palette: &[u8], 573 palette_len: usize, 574) -> Result<Image, ImageError> { 575 let w = frame.width as u32; 576 let h = frame.height as u32; 577 let mut data = Vec::with_capacity(pixel_count * 4); 578 for i in 0..pixel_count { 579 let idx = if i < frame.indices.len() { 580 frame.indices[i] 581 } else { 582 0 583 }; 584 if let Some(tc) = frame.transparent_color { 585 if idx == tc { 586 data.extend_from_slice(&[0, 0, 0, 0]); 587 continue; 588 } 589 } 590 let ci = idx as usize; 591 if ci >= palette_len { 592 return Err(ImageError::PaletteIndexOutOfBounds { 593 index: idx, 594 palette_len, 595 }); 596 } 597 let offset = ci * 3; 598 data.push(palette[offset]); 599 data.push(palette[offset + 1]); 600 data.push(palette[offset + 2]); 601 data.push(255); 602 } 603 Image::new(w, h, data) 604} 605 606// --------------------------------------------------------------------------- 607// Canvas compositing for animated GIFs 608// --------------------------------------------------------------------------- 609 610fn composite_frame( 611 canvas: &mut [u8], 612 canvas_width: u32, 613 canvas_height: u32, 614 frame: &RawFrame, 615 frame_image: &Image, 616) { 617 let cx = frame.left as usize; 618 let cy = frame.top as usize; 619 let fw = frame.width as usize; 620 let fh = frame.height as usize; 621 let cw = canvas_width as usize; 622 let ch = canvas_height as usize; 623 624 for row in 0..fh { 625 let dy = cy + row; 626 if dy >= ch { 627 break; 628 } 629 for col in 0..fw { 630 let dx = cx + col; 631 if dx >= cw { 632 break; 633 } 634 let src_off = (row * fw + col) * 4; 635 let dst_off = (dy * cw + dx) * 4; 636 let a = frame_image.data[src_off + 3]; 637 if a > 0 { 638 canvas[dst_off] = frame_image.data[src_off]; 639 canvas[dst_off + 1] = frame_image.data[src_off + 1]; 640 canvas[dst_off + 2] = frame_image.data[src_off + 2]; 641 canvas[dst_off + 3] = frame_image.data[src_off + 3]; 642 } 643 } 644 } 645} 646 647fn apply_disposal( 648 canvas: &mut [u8], 649 prev_canvas: &[u8], 650 frame: &RawFrame, 651 canvas_width: u32, 652 canvas_height: u32, 653 bg_index: u8, 654 color_table: &Option<Vec<u8>>, 655) { 656 match frame.disposal { 657 DisposalMethod::RestoreBackground => { 658 let cx = frame.left as usize; 659 let cy = frame.top as usize; 660 let fw = frame.width as usize; 661 let fh = frame.height as usize; 662 let cw = canvas_width as usize; 663 let ch = canvas_height as usize; 664 665 // Background color from the global color table 666 let (br, bg, bb) = if let Some(ref gct) = color_table { 667 let i = bg_index as usize; 668 let palette_len = gct.len() / 3; 669 if i < palette_len { 670 let off = i * 3; 671 (gct[off], gct[off + 1], gct[off + 2]) 672 } else { 673 (0, 0, 0) 674 } 675 } else { 676 (0, 0, 0) 677 }; 678 679 for row in 0..fh { 680 let dy = cy + row; 681 if dy >= ch { 682 break; 683 } 684 for col in 0..fw { 685 let dx = cx + col; 686 if dx >= cw { 687 break; 688 } 689 let off = (dy * cw + dx) * 4; 690 canvas[off] = br; 691 canvas[off + 1] = bg; 692 canvas[off + 2] = bb; 693 canvas[off + 3] = 0; // transparent background 694 } 695 } 696 } 697 DisposalMethod::RestorePrevious => { 698 canvas.copy_from_slice(prev_canvas); 699 } 700 DisposalMethod::None | DisposalMethod::DoNotDispose => { 701 // Leave canvas as-is 702 } 703 } 704} 705 706// --------------------------------------------------------------------------- 707// Public API 708// --------------------------------------------------------------------------- 709 710/// Decode a GIF image, returning the first frame as an RGBA8 image. 711/// 712/// For single-frame GIFs this returns the only frame. For animated GIFs this 713/// returns the composited first frame at the logical screen dimensions. 714pub fn decode_gif(data: &[u8]) -> Result<Image, ImageError> { 715 let frames = decode_gif_frames(data)?; 716 if frames.is_empty() { 717 return Err(ImageError::Decode("GIF: no frames found".into())); 718 } 719 Ok(frames.into_iter().next().unwrap().image) 720} 721 722/// Decode all frames from a GIF image. 723/// 724/// Each frame is composited onto the logical screen canvas according to the 725/// frame's position, disposal method, and transparency settings. The returned 726/// `Image` in each `GifFrame` has the logical screen dimensions. 727pub fn decode_gif_frames(data: &[u8]) -> Result<Vec<GifFrame>, ImageError> { 728 let mut reader = GifReader::new(data); 729 let screen = parse_header_and_screen(&mut reader)?; 730 731 let sw = screen.width as u32; 732 let sh = screen.height as u32; 733 if sw == 0 || sh == 0 { 734 return Err(ImageError::Decode( 735 "GIF: logical screen has zero dimension".into(), 736 )); 737 } 738 739 let canvas_size = sw as usize * sh as usize * 4; 740 let mut canvas = vec![0u8; canvas_size]; 741 let mut prev_canvas = vec![0u8; canvas_size]; 742 let mut frames = Vec::new(); 743 let mut current_gce: Option<GraphicControl> = None; 744 745 loop { 746 if reader.remaining() == 0 { 747 break; 748 } 749 let block_type = reader.read_byte()?; 750 751 match block_type { 752 TRAILER => break, 753 754 EXTENSION_INTRODUCER => { 755 let label = reader.read_byte()?; 756 match label { 757 GRAPHIC_CONTROL_EXT => { 758 let block_size = reader.read_byte()?; 759 if block_size != 4 { 760 return Err(ImageError::Decode( 761 "GIF: invalid graphic control extension size".into(), 762 )); 763 } 764 let packed = reader.read_byte()?; 765 let delay_cs = reader.read_u16_le()?; 766 let transparent_index = reader.read_byte()?; 767 let _terminator = reader.read_byte()?; 768 769 let disposal = DisposalMethod::from_byte((packed >> 2) & 0x07); 770 let has_transparency = (packed & 0x01) != 0; 771 772 current_gce = Some(GraphicControl { 773 disposal, 774 transparent_color: if has_transparency { 775 Some(transparent_index) 776 } else { 777 None 778 }, 779 delay_cs, 780 }); 781 } 782 APPLICATION_EXT | COMMENT_EXT | PLAIN_TEXT_EXT => { 783 // Skip the block size and sub-blocks 784 reader.skip_sub_blocks()?; 785 } 786 _ => { 787 // Unknown extension — skip sub-blocks 788 reader.skip_sub_blocks()?; 789 } 790 } 791 } 792 793 IMAGE_DESCRIPTOR => { 794 let gce = current_gce.take(); 795 let raw = decode_frame(&mut reader, &screen.global_color_table, gce)?; 796 let frame_image = render_frame(&raw)?; 797 798 // Save canvas state before compositing (for RestorePrevious) 799 prev_canvas.copy_from_slice(&canvas); 800 801 // Composite frame onto canvas 802 composite_frame(&mut canvas, sw, sh, &raw, &frame_image); 803 804 // Output the composited canvas as this frame 805 let output_image = Image::new(sw, sh, canvas.clone())?; 806 frames.push(GifFrame { 807 image: output_image, 808 delay_cs: raw.delay_cs, 809 disposal: raw.disposal, 810 }); 811 812 // Apply disposal for next frame 813 apply_disposal( 814 &mut canvas, 815 &prev_canvas, 816 &raw, 817 sw, 818 sh, 819 screen.background_index, 820 &screen.global_color_table, 821 ); 822 } 823 824 _ => { 825 // Unknown block type — try to skip. In practice this shouldn't happen 826 // in well-formed GIFs but we don't want to fail hard. 827 break; 828 } 829 } 830 } 831 832 Ok(frames) 833} 834 835// --------------------------------------------------------------------------- 836// Tests 837// --------------------------------------------------------------------------- 838 839#[cfg(test)] 840mod tests { 841 use super::*; 842 843 // ----------------------------------------------------------------------- 844 // LZW decompressor tests 845 // ----------------------------------------------------------------------- 846 847 #[test] 848 fn lzw_simple_sequence() { 849 // LZW with min code size 2 (codes 0-3 are literal, 4=clear, 5=eoi) 850 // Encode a simple sequence: clear, 0, 1, 0, 1, eoi 851 // initial code_size=3, bumps to 4 after next_code reaches 8 852 let mut bits: u64 = 0; 853 let mut bit_pos = 0u32; 854 855 let codes: &[(u16, u8)] = &[ 856 (4, 3), // clear code 857 (0, 3), // literal 0 858 (1, 3), // literal 1 859 (0, 3), // literal 0 → add_entry makes next_code=8, code_size→4 860 (1, 4), // literal 1 (now 4 bits) 861 (5, 4), // eoi (4 bits) 862 ]; 863 864 for &(code, nbits) in codes { 865 bits |= (code as u64) << bit_pos; 866 bit_pos += nbits as u32; 867 } 868 869 let byte_count = (bit_pos + 7) / 8; 870 let data: Vec<u8> = (0..byte_count) 871 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 872 .collect(); 873 874 let mut decoder = LzwDecoder::new(2); 875 let result = decoder.decompress(&data).unwrap(); 876 assert_eq!(result, vec![0, 1, 0, 1]); 877 } 878 879 #[test] 880 fn lzw_repeated_pattern() { 881 // Encode: clear, 1, 1, 1, 1, eoi with min_code_size=2 882 // After clear: table has 0,1,2,3, clear=4, eoi=5, next=6 883 // Code size = 3 884 // Read 1 -> output [1], prev=1 885 // Read 1 -> in table, first_byte(1)=1, add (1,1) as code 6 -> output [1,1] 886 // next=7 887 // Read 6 -> in table, first_byte(6)=1, add (1,1) as code 7 -> output [1,1,1,1] 888 // next=8 -> code_size becomes 4 889 // Read 5 (eoi, now 4 bits) -> done 890 891 let mut bits: u64 = 0; 892 let mut bit_pos = 0u32; 893 894 let codes: &[(u16, u8)] = &[ 895 (4, 3), // clear 896 (1, 3), // literal 1 897 (1, 3), // literal 1 898 (6, 3), // code 6 = {1,1} 899 (5, 4), // eoi (now 4 bits since next_code=8 bumped code_size) 900 ]; 901 902 for &(code, nbits) in codes { 903 bits |= (code as u64) << bit_pos; 904 bit_pos += nbits as u32; 905 } 906 907 let byte_count = (bit_pos + 7) / 8; 908 let data: Vec<u8> = (0..byte_count) 909 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 910 .collect(); 911 912 let mut decoder = LzwDecoder::new(2); 913 let result = decoder.decompress(&data).unwrap(); 914 assert_eq!(result, vec![1, 1, 1, 1]); 915 } 916 917 #[test] 918 fn lzw_code_not_in_table() { 919 // Test the special case where code == next_code 920 // min_code_size=2, clear=4, eoi=5, next=6 921 // Sequence: clear, 0, 6(=next), eoi 922 // After 0: prev=0 923 // Read 6: code == next_code, first_byte(prev=0)=0, add(0,0) as 6 924 // emit(6) = [0,0] 925 // Total output: [0, 0, 0] 926 927 let mut bits: u64 = 0; 928 let mut bit_pos = 0u32; 929 930 let codes: &[(u16, u8)] = &[ 931 (4, 3), // clear 932 (0, 3), // literal 0 933 (6, 3), // code 6 = next_code (special case) 934 (5, 3), // eoi 935 ]; 936 937 for &(code, nbits) in codes { 938 bits |= (code as u64) << bit_pos; 939 bit_pos += nbits as u32; 940 } 941 942 let byte_count = (bit_pos + 7) / 8; 943 let data: Vec<u8> = (0..byte_count) 944 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 945 .collect(); 946 947 let mut decoder = LzwDecoder::new(2); 948 let result = decoder.decompress(&data).unwrap(); 949 assert_eq!(result, vec![0, 0, 0]); 950 } 951 952 #[test] 953 fn lzw_clear_code_reset() { 954 // Test that clear code resets the table 955 // min_code_size=2, clear=4, eoi=5 956 // Sequence: clear, 0, 1, clear, 2, 3, eoi 957 958 let mut bits: u64 = 0; 959 let mut bit_pos = 0u32; 960 961 let codes: &[(u16, u8)] = &[ 962 (4, 3), // clear 963 (0, 3), // literal 0 964 (1, 3), // literal 1 965 (4, 3), // clear (reset) 966 (2, 3), // literal 2 967 (3, 3), // literal 3 968 (5, 3), // eoi 969 ]; 970 971 for &(code, nbits) in codes { 972 bits |= (code as u64) << bit_pos; 973 bit_pos += nbits as u32; 974 } 975 976 let byte_count = (bit_pos + 7) / 8; 977 let data: Vec<u8> = (0..byte_count) 978 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 979 .collect(); 980 981 let mut decoder = LzwDecoder::new(2); 982 let result = decoder.decompress(&data).unwrap(); 983 assert_eq!(result, vec![0, 1, 2, 3]); 984 } 985 986 #[test] 987 fn lzw_empty_after_clear_eoi() { 988 // clear then immediate eoi 989 let mut bits: u64 = 0; 990 let mut bit_pos = 0u32; 991 992 let codes: &[(u16, u8)] = &[ 993 (4, 3), // clear 994 (5, 3), // eoi 995 ]; 996 997 for &(code, nbits) in codes { 998 bits |= (code as u64) << bit_pos; 999 bit_pos += nbits as u32; 1000 } 1001 1002 let byte_count = (bit_pos + 7) / 8; 1003 let data: Vec<u8> = (0..byte_count) 1004 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 1005 .collect(); 1006 1007 let mut decoder = LzwDecoder::new(2); 1008 let result = decoder.decompress(&data).unwrap(); 1009 assert!(result.is_empty()); 1010 } 1011 1012 #[test] 1013 fn lzw_min_code_size_1() { 1014 // min_code_size=1: literals 0,1; clear=2, eoi=3 1015 // code_size starts at 2 1016 // After reading 0, 1: add_entry(0,1)=code4, next=5 > 4=1<<2 → code_size=3 1017 let mut bits: u64 = 0; 1018 let mut bit_pos = 0u32; 1019 1020 let codes: &[(u16, u8)] = &[ 1021 (2, 2), // clear (code_size=2) 1022 (0, 2), // literal 0 1023 (1, 2), // literal 1 → after this, code_size bumps to 3 1024 (0, 3), // literal 0 (now 3 bits) 1025 (3, 3), // eoi (3 bits) 1026 ]; 1027 1028 for &(code, nbits) in codes { 1029 bits |= (code as u64) << bit_pos; 1030 bit_pos += nbits as u32; 1031 } 1032 1033 let byte_count = (bit_pos + 7) / 8; 1034 let data: Vec<u8> = (0..byte_count) 1035 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 1036 .collect(); 1037 1038 let mut decoder = LzwDecoder::new(1); 1039 let result = decoder.decompress(&data).unwrap(); 1040 assert_eq!(result, vec![0, 1, 0]); 1041 } 1042 1043 #[test] 1044 fn lzw_min_code_size_8() { 1045 // min_code_size=8: literals 0-255; clear=256, eoi=257 1046 // code_size starts at 9 1047 let mut bits: u128 = 0; 1048 let mut bit_pos = 0u32; 1049 1050 let codes: &[(u16, u8)] = &[ 1051 (256, 9), // clear 1052 (0, 9), // literal 0 1053 (255, 9), // literal 255 1054 (128, 9), // literal 128 1055 (257, 9), // eoi 1056 ]; 1057 1058 for &(code, nbits) in codes { 1059 bits |= (code as u128) << bit_pos; 1060 bit_pos += nbits as u32; 1061 } 1062 1063 let byte_count = (bit_pos + 7) / 8; 1064 let data: Vec<u8> = (0..byte_count) 1065 .map(|i| ((bits >> (i * 8)) & 0xFF) as u8) 1066 .collect(); 1067 1068 let mut decoder = LzwDecoder::new(8); 1069 let result = decoder.decompress(&data).unwrap(); 1070 assert_eq!(result, vec![0, 255, 128]); 1071 } 1072 1073 // ----------------------------------------------------------------------- 1074 // Deinterlace tests 1075 // ----------------------------------------------------------------------- 1076 1077 #[test] 1078 fn deinterlace_8_rows() { 1079 // With 8 rows and width=1: 1080 // Pass 1 (start=0, step=8): row 0 1081 // Pass 2 (start=4, step=8): row 4 1082 // Pass 3 (start=2, step=4): rows 2, 6 1083 // Pass 4 (start=1, step=2): rows 1, 3, 5, 7 1084 // Interlaced order: [0, 4, 2, 6, 1, 3, 5, 7] 1085 let interlaced: Vec<u8> = vec![0, 4, 2, 6, 1, 3, 5, 7]; 1086 let result = deinterlace(&interlaced, 1, 8); 1087 assert_eq!(result, vec![0, 1, 2, 3, 4, 5, 6, 7]); 1088 } 1089 1090 #[test] 1091 fn deinterlace_4_rows() { 1092 // 4 rows: 1093 // Pass 1 (start=0, step=8): row 0 1094 // Pass 2 (start=4, step=8): (none) 1095 // Pass 3 (start=2, step=4): row 2 1096 // Pass 4 (start=1, step=2): rows 1, 3 1097 // Interlaced order: [0, 2, 1, 3] 1098 let interlaced: Vec<u8> = vec![0, 2, 1, 3]; 1099 let result = deinterlace(&interlaced, 1, 4); 1100 assert_eq!(result, vec![0, 1, 2, 3]); 1101 } 1102 1103 #[test] 1104 fn deinterlace_1_row() { 1105 let interlaced: Vec<u8> = vec![42]; 1106 let result = deinterlace(&interlaced, 1, 1); 1107 assert_eq!(result, vec![42]); 1108 } 1109 1110 #[test] 1111 fn deinterlace_wider() { 1112 // 4 rows, width=2 1113 // Interlaced row order: 0, 2, 1, 3 1114 let interlaced: Vec<u8> = vec![ 1115 10, 11, // row 0 1116 30, 31, // row 2 1117 20, 21, // row 1 1118 40, 41, // row 3 1119 ]; 1120 let result = deinterlace(&interlaced, 2, 4); 1121 assert_eq!( 1122 result, 1123 vec![ 1124 10, 11, // row 0 1125 20, 21, // row 1 1126 30, 31, // row 2 1127 40, 41, // row 3 1128 ] 1129 ); 1130 } 1131 1132 // ----------------------------------------------------------------------- 1133 // Disposal method tests 1134 // ----------------------------------------------------------------------- 1135 1136 #[test] 1137 fn disposal_from_byte() { 1138 assert_eq!(DisposalMethod::from_byte(0), DisposalMethod::None); 1139 assert_eq!(DisposalMethod::from_byte(1), DisposalMethod::DoNotDispose); 1140 assert_eq!( 1141 DisposalMethod::from_byte(2), 1142 DisposalMethod::RestoreBackground 1143 ); 1144 assert_eq!( 1145 DisposalMethod::from_byte(3), 1146 DisposalMethod::RestorePrevious 1147 ); 1148 assert_eq!(DisposalMethod::from_byte(4), DisposalMethod::None); 1149 assert_eq!(DisposalMethod::from_byte(255), DisposalMethod::None); 1150 } 1151 1152 // ----------------------------------------------------------------------- 1153 // Full GIF decode tests (hand-crafted minimal GIFs) 1154 // ----------------------------------------------------------------------- 1155 1156 /// Build a minimal valid GIF87a with a single 1x1 red pixel. 1157 fn build_1x1_red_gif() -> Vec<u8> { 1158 let mut gif = Vec::new(); 1159 1160 // Header 1161 gif.extend_from_slice(b"GIF87a"); 1162 1163 // Logical screen descriptor: 1x1, GCT flag set, 1 bit color resolution 1164 // packed: 0x80 (has GCT) | 0x00 (color res = 0+1=1) | 0x00 (sort=0) | 0x00 (GCT size=0 -> 2 entries) 1165 gif.push(1); 1166 gif.push(0); // width=1 1167 gif.push(1); 1168 gif.push(0); // height=1 1169 gif.push(0x80); // packed 1170 gif.push(0); // background index 1171 gif.push(0); // pixel aspect ratio 1172 1173 // GCT: 2 entries (2^(0+1) = 2) 1174 gif.extend_from_slice(&[255, 0, 0]); // entry 0: red 1175 gif.extend_from_slice(&[0, 0, 0]); // entry 1: black 1176 1177 // Image descriptor 1178 gif.push(0x2C); 1179 gif.extend_from_slice(&[0, 0]); // left 1180 gif.extend_from_slice(&[0, 0]); // top 1181 gif.push(1); 1182 gif.push(0); // width=1 1183 gif.push(1); 1184 gif.push(0); // height=1 1185 gif.push(0); // packed (no LCT, no interlace) 1186 1187 // LZW min code size = 2 (must be >= 2 for GIF) 1188 gif.push(2); 1189 1190 // LZW data: clear(4), 0, eoi(5) — all in 3-bit codes 1191 // Bits: 100 000 101 = 0b101_000_100 packed LSB first 1192 // Byte 0: bits 0-7 = 00100 000 = 0b00000100 (reversed reading) 1193 // Let me construct this properly: 1194 // code 4 (clear) = 100 (3 bits) 1195 // code 0 = 000 (3 bits) 1196 // code 5 (eoi) = 101 (3 bits) 1197 // Total: 9 bits packed LSB first 1198 // bits[0..3] = 100 = 4 (clear) 1199 // bits[3..6] = 000 = 0 (literal 0) 1200 // bits[6..9] = 101 = 5 (eoi) 1201 // Byte 0 (bits 0-7): bit0=0, bit1=0, bit2=1, bit3=0, bit4=0, bit5=0, bit6=1, bit7=0 1202 // = 0b01000100 = 0x44 1203 // Byte 1 (bits 8): bit8=1 1204 // = 0b00000001 = 0x01 1205 gif.push(2); // sub-block size = 2 bytes 1206 gif.push(0x44); 1207 gif.push(0x01); 1208 gif.push(0); // sub-block terminator 1209 1210 // Trailer 1211 gif.push(0x3B); 1212 1213 gif 1214 } 1215 1216 #[test] 1217 fn decode_1x1_red() { 1218 let data = build_1x1_red_gif(); 1219 let img = decode_gif(&data).unwrap(); 1220 assert_eq!(img.width, 1); 1221 assert_eq!(img.height, 1); 1222 assert_eq!(img.data, vec![255, 0, 0, 255]); 1223 } 1224 1225 /// Build a 2x2 GIF with 4 colors. 1226 fn build_2x2_gif() -> Vec<u8> { 1227 let mut gif = Vec::new(); 1228 1229 // Header 1230 gif.extend_from_slice(b"GIF89a"); 1231 1232 // Screen: 2x2, GCT with 4 entries (size field = 1 -> 2^(1+1) = 4) 1233 gif.push(2); 1234 gif.push(0); 1235 gif.push(2); 1236 gif.push(0); 1237 gif.push(0x81); // has GCT, GCT size=1 (4 entries) 1238 gif.push(0); // bg index 1239 gif.push(0); // aspect 1240 1241 // GCT: 4 entries 1242 gif.extend_from_slice(&[255, 0, 0]); // 0: red 1243 gif.extend_from_slice(&[0, 255, 0]); // 1: green 1244 gif.extend_from_slice(&[0, 0, 255]); // 2: blue 1245 gif.extend_from_slice(&[255, 255, 0]); // 3: yellow 1246 1247 // Image descriptor 1248 gif.push(0x2C); 1249 gif.extend_from_slice(&[0, 0, 0, 0]); // left=0, top=0 1250 gif.push(2); 1251 gif.push(0); 1252 gif.push(2); 1253 gif.push(0); // 2x2 1254 gif.push(0); // no LCT 1255 1256 // LZW min code size = 2 (4 literal codes: 0,1,2,3; clear=4, eoi=5) 1257 gif.push(2); 1258 1259 // Encode: clear(4), 0, 1, 2, 3, eoi(5) 1260 // code_size starts at 3, bumps to 4 after code 2 (next_code reaches 8) 1261 let mut bits: u64 = 0; 1262 let mut bp = 0u32; 1263 for &(code, nbits) in &[(4u16, 3u8), (0, 3), (1, 3), (2, 3), (3, 4), (5, 4)] { 1264 bits |= (code as u64) << bp; 1265 bp += nbits as u32; 1266 } 1267 let byte_count = ((bp + 7) / 8) as usize; 1268 let lzw_bytes: Vec<u8> = (0..byte_count) 1269 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1270 .collect(); 1271 1272 gif.push(lzw_bytes.len() as u8); 1273 gif.extend_from_slice(&lzw_bytes); 1274 gif.push(0); // terminator 1275 1276 gif.push(0x3B); // trailer 1277 gif 1278 } 1279 1280 #[test] 1281 fn decode_2x2_four_colors() { 1282 let data = build_2x2_gif(); 1283 let img = decode_gif(&data).unwrap(); 1284 assert_eq!(img.width, 2); 1285 assert_eq!(img.height, 2); 1286 assert_eq!( 1287 img.data, 1288 vec![ 1289 255, 0, 0, 255, // red 1290 0, 255, 0, 255, // green 1291 0, 0, 255, 255, // blue 1292 255, 255, 0, 255, // yellow 1293 ] 1294 ); 1295 } 1296 1297 #[test] 1298 fn decode_frames_single() { 1299 let data = build_2x2_gif(); 1300 let frames = decode_gif_frames(&data).unwrap(); 1301 assert_eq!(frames.len(), 1); 1302 assert_eq!(frames[0].delay_cs, 0); 1303 assert_eq!(frames[0].disposal, DisposalMethod::None); 1304 } 1305 1306 /// Build a GIF89a with transparency. 1307 fn build_transparent_gif() -> Vec<u8> { 1308 let mut gif = Vec::new(); 1309 1310 gif.extend_from_slice(b"GIF89a"); 1311 1312 // Screen: 2x1 1313 gif.push(2); 1314 gif.push(0); 1315 gif.push(1); 1316 gif.push(0); 1317 gif.push(0x80); // has GCT, size=0 (2 entries) 1318 gif.push(0); 1319 gif.push(0); 1320 1321 // GCT: 2 entries 1322 gif.extend_from_slice(&[255, 0, 0]); // 0: red 1323 gif.extend_from_slice(&[0, 255, 0]); // 1: green 1324 1325 // Graphic Control Extension 1326 gif.push(0x21); // extension introducer 1327 gif.push(0xF9); // GCE label 1328 gif.push(4); // block size 1329 gif.push(0x01); // packed: disposal=0, transparent=true 1330 gif.push(10); 1331 gif.push(0); // delay = 10cs 1332 gif.push(1); // transparent color index = 1 1333 gif.push(0); // block terminator 1334 1335 // Image descriptor 1336 gif.push(0x2C); 1337 gif.extend_from_slice(&[0, 0, 0, 0]); 1338 gif.push(2); 1339 gif.push(0); 1340 gif.push(1); 1341 gif.push(0); 1342 gif.push(0); 1343 1344 // LZW min code size = 2 1345 gif.push(2); 1346 1347 // Encode: clear(4), 0, 1, eoi(5) 1348 let mut bits: u64 = 0; 1349 let mut bp = 0u32; 1350 for &(code, nbits) in &[(4u16, 3u8), (0, 3), (1, 3), (5, 3)] { 1351 bits |= (code as u64) << bp; 1352 bp += nbits as u32; 1353 } 1354 let byte_count = ((bp + 7) / 8) as usize; 1355 let lzw_bytes: Vec<u8> = (0..byte_count) 1356 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1357 .collect(); 1358 1359 gif.push(lzw_bytes.len() as u8); 1360 gif.extend_from_slice(&lzw_bytes); 1361 gif.push(0); 1362 1363 gif.push(0x3B); 1364 gif 1365 } 1366 1367 #[test] 1368 fn decode_with_transparency() { 1369 let data = build_transparent_gif(); 1370 let img = decode_gif(&data).unwrap(); 1371 assert_eq!(img.width, 2); 1372 assert_eq!(img.height, 1); 1373 // Pixel 0: index 0 = red (opaque) 1374 // Pixel 1: index 1 = transparent 1375 assert_eq!( 1376 img.data, 1377 vec![ 1378 255, 0, 0, 255, // red, opaque 1379 0, 0, 0, 0, // transparent 1380 ] 1381 ); 1382 } 1383 1384 #[test] 1385 fn decode_transparency_delay() { 1386 let data = build_transparent_gif(); 1387 let frames = decode_gif_frames(&data).unwrap(); 1388 assert_eq!(frames.len(), 1); 1389 assert_eq!(frames[0].delay_cs, 10); 1390 } 1391 1392 /// Build a 2-frame animated GIF. 1393 fn build_animated_gif() -> Vec<u8> { 1394 let mut gif = Vec::new(); 1395 1396 gif.extend_from_slice(b"GIF89a"); 1397 1398 // Screen: 2x1 1399 gif.push(2); 1400 gif.push(0); 1401 gif.push(1); 1402 gif.push(0); 1403 gif.push(0x80); // GCT, size=0 (2 entries) 1404 gif.push(0); 1405 gif.push(0); 1406 1407 // GCT 1408 gif.extend_from_slice(&[255, 0, 0]); // 0: red 1409 gif.extend_from_slice(&[0, 0, 255]); // 1: blue 1410 1411 // --- Frame 1 --- 1412 // GCE: disposal=none, delay=5 1413 gif.push(0x21); 1414 gif.push(0xF9); 1415 gif.push(4); 1416 gif.push(0x00); // disposal=0(none), no transparency 1417 gif.push(5); 1418 gif.push(0); // delay=5 1419 gif.push(0); // no transparent 1420 gif.push(0); 1421 1422 // Image descriptor: full 2x1 1423 gif.push(0x2C); 1424 gif.extend_from_slice(&[0, 0, 0, 0]); 1425 gif.push(2); 1426 gif.push(0); 1427 gif.push(1); 1428 gif.push(0); 1429 gif.push(0); 1430 1431 gif.push(2); // LZW min code size 1432 1433 // Encode: clear(4), 0, 1, eoi(5) 1434 { 1435 let mut bits: u64 = 0; 1436 let mut bp = 0u32; 1437 for &(code, nbits) in &[(4u16, 3u8), (0, 3), (1, 3), (5, 3)] { 1438 bits |= (code as u64) << bp; 1439 bp += nbits as u32; 1440 } 1441 let byte_count = ((bp + 7) / 8) as usize; 1442 let lzw_bytes: Vec<u8> = (0..byte_count) 1443 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1444 .collect(); 1445 gif.push(lzw_bytes.len() as u8); 1446 gif.extend_from_slice(&lzw_bytes); 1447 gif.push(0); 1448 } 1449 1450 // --- Frame 2 --- 1451 // GCE: disposal=restore_bg, delay=10 1452 gif.push(0x21); 1453 gif.push(0xF9); 1454 gif.push(4); 1455 gif.push(0x08); // disposal=2(restore bg), no transparency 1456 gif.push(10); 1457 gif.push(0); // delay=10 1458 gif.push(0); 1459 gif.push(0); 1460 1461 // Image descriptor: full 2x1 1462 gif.push(0x2C); 1463 gif.extend_from_slice(&[0, 0, 0, 0]); 1464 gif.push(2); 1465 gif.push(0); 1466 gif.push(1); 1467 gif.push(0); 1468 gif.push(0); 1469 1470 gif.push(2); 1471 1472 // Encode: clear(4), 1, 0, eoi(5) 1473 { 1474 let mut bits: u64 = 0; 1475 let mut bp = 0u32; 1476 for &(code, nbits) in &[(4u16, 3u8), (1, 3), (0, 3), (5, 3)] { 1477 bits |= (code as u64) << bp; 1478 bp += nbits as u32; 1479 } 1480 let byte_count = ((bp + 7) / 8) as usize; 1481 let lzw_bytes: Vec<u8> = (0..byte_count) 1482 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1483 .collect(); 1484 gif.push(lzw_bytes.len() as u8); 1485 gif.extend_from_slice(&lzw_bytes); 1486 gif.push(0); 1487 } 1488 1489 gif.push(0x3B); 1490 gif 1491 } 1492 1493 #[test] 1494 fn decode_animated_two_frames() { 1495 let data = build_animated_gif(); 1496 let frames = decode_gif_frames(&data).unwrap(); 1497 assert_eq!(frames.len(), 2); 1498 1499 // Frame 1: [red, blue] 1500 assert_eq!(frames[0].delay_cs, 5); 1501 assert_eq!(frames[0].disposal, DisposalMethod::None); 1502 assert_eq!(frames[0].image.data, vec![255, 0, 0, 255, 0, 0, 255, 255]); 1503 1504 // Frame 2: [blue, red] (composited on canvas) 1505 assert_eq!(frames[1].delay_cs, 10); 1506 assert_eq!(frames[1].disposal, DisposalMethod::RestoreBackground); 1507 assert_eq!(frames[1].image.data, vec![0, 0, 255, 255, 255, 0, 0, 255]); 1508 } 1509 1510 #[test] 1511 fn decode_animated_first_frame_only() { 1512 let data = build_animated_gif(); 1513 let img = decode_gif(&data).unwrap(); 1514 // decode_gif returns the first frame 1515 assert_eq!(img.data, vec![255, 0, 0, 255, 0, 0, 255, 255]); 1516 } 1517 1518 /// Build a GIF with a local color table. 1519 fn build_local_ct_gif() -> Vec<u8> { 1520 let mut gif = Vec::new(); 1521 1522 gif.extend_from_slice(b"GIF87a"); 1523 1524 // Screen: 1x1, NO GCT 1525 gif.push(1); 1526 gif.push(0); 1527 gif.push(1); 1528 gif.push(0); 1529 gif.push(0x00); // no GCT 1530 gif.push(0); 1531 gif.push(0); 1532 1533 // Image descriptor with LCT 1534 gif.push(0x2C); 1535 gif.extend_from_slice(&[0, 0, 0, 0]); 1536 gif.push(1); 1537 gif.push(0); 1538 gif.push(1); 1539 gif.push(0); 1540 gif.push(0x80); // has LCT, size=0 (2 entries) 1541 1542 // LCT 1543 gif.extend_from_slice(&[0, 128, 255]); // 0: teal-ish 1544 gif.extend_from_slice(&[0, 0, 0]); // 1: black 1545 1546 gif.push(2); // LZW min code size 1547 1548 // Encode: clear(4), 0, eoi(5) 1549 let mut bits: u64 = 0; 1550 let mut bp = 0u32; 1551 for &(code, nbits) in &[(4u16, 3u8), (0, 3), (5, 3)] { 1552 bits |= (code as u64) << bp; 1553 bp += nbits as u32; 1554 } 1555 let byte_count = ((bp + 7) / 8) as usize; 1556 let lzw_bytes: Vec<u8> = (0..byte_count) 1557 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1558 .collect(); 1559 gif.push(lzw_bytes.len() as u8); 1560 gif.extend_from_slice(&lzw_bytes); 1561 gif.push(0); 1562 1563 gif.push(0x3B); 1564 gif 1565 } 1566 1567 #[test] 1568 fn decode_local_color_table() { 1569 let data = build_local_ct_gif(); 1570 let img = decode_gif(&data).unwrap(); 1571 assert_eq!(img.width, 1); 1572 assert_eq!(img.height, 1); 1573 assert_eq!(img.data, vec![0, 128, 255, 255]); 1574 } 1575 1576 // ----------------------------------------------------------------------- 1577 // Error case tests 1578 // ----------------------------------------------------------------------- 1579 1580 #[test] 1581 fn invalid_signature() { 1582 let data = b"NOT_GIF"; 1583 assert!(matches!( 1584 decode_gif(data), 1585 Err(ImageError::Decode(ref msg)) if msg.contains("invalid signature") 1586 )); 1587 } 1588 1589 #[test] 1590 fn too_short() { 1591 let data = b"GIF8"; 1592 assert!(matches!(decode_gif(data), Err(ImageError::Decode(_)))); 1593 } 1594 1595 #[test] 1596 fn no_frames() { 1597 let mut gif = Vec::new(); 1598 gif.extend_from_slice(b"GIF87a"); 1599 gif.push(1); 1600 gif.push(0); 1601 gif.push(1); 1602 gif.push(0); 1603 gif.push(0x00); 1604 gif.push(0); 1605 gif.push(0); 1606 gif.push(0x3B); 1607 assert!(matches!( 1608 decode_gif(&gif), 1609 Err(ImageError::Decode(ref msg)) if msg.contains("no frames") 1610 )); 1611 } 1612 1613 #[test] 1614 fn zero_dimension_screen() { 1615 let mut gif = Vec::new(); 1616 gif.extend_from_slice(b"GIF87a"); 1617 gif.push(0); 1618 gif.push(0); // width=0 1619 gif.push(1); 1620 gif.push(0); // height=1 1621 gif.push(0x00); 1622 gif.push(0); 1623 gif.push(0); 1624 assert!(matches!( 1625 decode_gif(&gif), 1626 Err(ImageError::Decode(ref msg)) if msg.contains("zero dimension") 1627 )); 1628 } 1629 1630 #[test] 1631 fn invalid_lzw_min_code_size() { 1632 let mut gif = Vec::new(); 1633 gif.extend_from_slice(b"GIF87a"); 1634 gif.push(1); 1635 gif.push(0); 1636 gif.push(1); 1637 gif.push(0); 1638 gif.push(0x80); 1639 gif.push(0); 1640 gif.push(0); 1641 gif.extend_from_slice(&[0; 6]); // GCT (2 entries) 1642 1643 gif.push(0x2C); // image descriptor 1644 gif.extend_from_slice(&[0, 0, 0, 0, 1, 0, 1, 0, 0]); 1645 gif.push(12); // invalid LZW min code size (>11) 1646 1647 assert!(matches!( 1648 decode_gif(&gif), 1649 Err(ImageError::Decode(ref msg)) if msg.contains("LZW minimum code size") 1650 )); 1651 } 1652 1653 // ----------------------------------------------------------------------- 1654 // Sub-frame positioning test 1655 // ----------------------------------------------------------------------- 1656 1657 /// Build a GIF where the frame is smaller than the logical screen. 1658 fn build_subframe_gif() -> Vec<u8> { 1659 let mut gif = Vec::new(); 1660 1661 gif.extend_from_slice(b"GIF89a"); 1662 1663 // Screen: 3x3 1664 gif.push(3); 1665 gif.push(0); 1666 gif.push(3); 1667 gif.push(0); 1668 gif.push(0x80); // GCT, size=0 (2 entries) 1669 gif.push(0); 1670 gif.push(0); 1671 1672 // GCT 1673 gif.extend_from_slice(&[0, 0, 0]); // 0: black 1674 gif.extend_from_slice(&[255, 255, 255]); // 1: white 1675 1676 // Image descriptor: 1x1 at position (1,1) 1677 gif.push(0x2C); 1678 gif.push(1); 1679 gif.push(0); // left=1 1680 gif.push(1); 1681 gif.push(0); // top=1 1682 gif.push(1); 1683 gif.push(0); // width=1 1684 gif.push(1); 1685 gif.push(0); // height=1 1686 gif.push(0); 1687 1688 gif.push(2); // LZW min 1689 1690 // Encode: clear(4), 1, eoi(5) 1691 let mut bits: u64 = 0; 1692 let mut bp = 0u32; 1693 for &(code, nbits) in &[(4u16, 3u8), (1, 3), (5, 3)] { 1694 bits |= (code as u64) << bp; 1695 bp += nbits as u32; 1696 } 1697 let byte_count = ((bp + 7) / 8) as usize; 1698 let lzw_bytes: Vec<u8> = (0..byte_count) 1699 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1700 .collect(); 1701 gif.push(lzw_bytes.len() as u8); 1702 gif.extend_from_slice(&lzw_bytes); 1703 gif.push(0); 1704 1705 gif.push(0x3B); 1706 gif 1707 } 1708 1709 #[test] 1710 fn decode_subframe_positioning() { 1711 let data = build_subframe_gif(); 1712 let img = decode_gif(&data).unwrap(); 1713 assert_eq!(img.width, 3); 1714 assert_eq!(img.height, 3); 1715 // Canvas starts all black (0,0,0,0 = transparent) 1716 // The 1x1 white pixel is composited at position (1,1) 1717 let pixel = |x: usize, y: usize| -> &[u8] { 1718 let off = (y * 3 + x) * 4; 1719 &img.data[off..off + 4] 1720 }; 1721 // All pixels except (1,1) should be transparent black 1722 assert_eq!(pixel(0, 0), &[0, 0, 0, 0]); 1723 assert_eq!(pixel(1, 0), &[0, 0, 0, 0]); 1724 assert_eq!(pixel(2, 0), &[0, 0, 0, 0]); 1725 assert_eq!(pixel(0, 1), &[0, 0, 0, 0]); 1726 assert_eq!(pixel(1, 1), &[255, 255, 255, 255]); // white 1727 assert_eq!(pixel(2, 1), &[0, 0, 0, 0]); 1728 assert_eq!(pixel(0, 2), &[0, 0, 0, 0]); 1729 assert_eq!(pixel(1, 2), &[0, 0, 0, 0]); 1730 assert_eq!(pixel(2, 2), &[0, 0, 0, 0]); 1731 } 1732 1733 // ----------------------------------------------------------------------- 1734 // Application extension skip test 1735 // ----------------------------------------------------------------------- 1736 1737 #[test] 1738 fn skip_application_extension() { 1739 let mut gif = Vec::new(); 1740 1741 gif.extend_from_slice(b"GIF89a"); 1742 gif.push(1); 1743 gif.push(0); 1744 gif.push(1); 1745 gif.push(0); 1746 gif.push(0x80); 1747 gif.push(0); 1748 gif.push(0); 1749 gif.extend_from_slice(&[255, 0, 0, 0, 0, 0]); // GCT 1750 1751 // Application extension (NETSCAPE2.0 for looping) 1752 gif.push(0x21); 1753 gif.push(0xFF); 1754 // Sub-blocks for the extension 1755 gif.push(11); // sub-block size 1756 gif.extend_from_slice(b"NETSCAPE2.0"); 1757 gif.push(3); // sub-block size 1758 gif.push(1); // sub-block id 1759 gif.push(0); 1760 gif.push(0); // loop count 1761 gif.push(0); // terminator 1762 1763 // Image 1764 gif.push(0x2C); 1765 gif.extend_from_slice(&[0, 0, 0, 0, 1, 0, 1, 0, 0]); 1766 gif.push(2); 1767 1768 let mut bits: u64 = 0; 1769 let mut bp = 0u32; 1770 for &(code, nbits) in &[(4u16, 3u8), (0, 3), (5, 3)] { 1771 bits |= (code as u64) << bp; 1772 bp += nbits as u32; 1773 } 1774 let byte_count = ((bp + 7) / 8) as usize; 1775 let lzw_bytes: Vec<u8> = (0..byte_count) 1776 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1777 .collect(); 1778 gif.push(lzw_bytes.len() as u8); 1779 gif.extend_from_slice(&lzw_bytes); 1780 gif.push(0); 1781 1782 gif.push(0x3B); 1783 1784 let img = decode_gif(&gif).unwrap(); 1785 assert_eq!(img.width, 1); 1786 assert_eq!(img.height, 1); 1787 assert_eq!(img.data, vec![255, 0, 0, 255]); 1788 } 1789 1790 // ----------------------------------------------------------------------- 1791 // Comment extension skip test 1792 // ----------------------------------------------------------------------- 1793 1794 #[test] 1795 fn skip_comment_extension() { 1796 let mut gif = Vec::new(); 1797 1798 gif.extend_from_slice(b"GIF89a"); 1799 gif.push(1); 1800 gif.push(0); 1801 gif.push(1); 1802 gif.push(0); 1803 gif.push(0x80); 1804 gif.push(0); 1805 gif.push(0); 1806 gif.extend_from_slice(&[0, 255, 0, 0, 0, 0]); // GCT 1807 1808 // Comment extension 1809 gif.push(0x21); 1810 gif.push(0xFE); 1811 gif.push(5); 1812 gif.extend_from_slice(b"hello"); 1813 gif.push(0); 1814 1815 // Image 1816 gif.push(0x2C); 1817 gif.extend_from_slice(&[0, 0, 0, 0, 1, 0, 1, 0, 0]); 1818 gif.push(2); 1819 1820 let mut bits: u64 = 0; 1821 let mut bp = 0u32; 1822 for &(code, nbits) in &[(4u16, 3u8), (0, 3), (5, 3)] { 1823 bits |= (code as u64) << bp; 1824 bp += nbits as u32; 1825 } 1826 let byte_count = ((bp + 7) / 8) as usize; 1827 let lzw_bytes: Vec<u8> = (0..byte_count) 1828 .map(|i| ((bits >> (i as u32 * 8)) & 0xFF) as u8) 1829 .collect(); 1830 gif.push(lzw_bytes.len() as u8); 1831 gif.extend_from_slice(&lzw_bytes); 1832 gif.push(0); 1833 1834 gif.push(0x3B); 1835 1836 let img = decode_gif(&gif).unwrap(); 1837 assert_eq!(img.data, vec![0, 255, 0, 255]); 1838 } 1839 1840 // ----------------------------------------------------------------------- 1841 // GIF87a compatibility test 1842 // ----------------------------------------------------------------------- 1843 1844 #[test] 1845 fn gif87a_is_accepted() { 1846 let data = build_1x1_red_gif(); 1847 // Already uses GIF87a 1848 assert!(data.starts_with(b"GIF87a")); 1849 let img = decode_gif(&data).unwrap(); 1850 assert_eq!(img.data, vec![255, 0, 0, 255]); 1851 } 1852 1853 // ----------------------------------------------------------------------- 1854 // Bit reader tests 1855 // ----------------------------------------------------------------------- 1856 1857 #[test] 1858 fn bit_reader_basic() { 1859 // Byte 0: 0b10110100 — bits[0..8] = 0,0,1,0,1,1,0,1 1860 // Byte 1: 0b00000001 — bits[8..16] = 1,0,0,0,0,0,0,0 1861 let data = [0b10110100u8, 0b00000001]; 1862 let mut reader = LzwBitReader::new(&data); 1863 1864 // Read 3 bits [0,1,2]: 0*1+0*2+1*4 = 4 1865 assert_eq!(reader.read_bits(3).unwrap(), 4); 1866 // Read 3 bits [3,4,5]: 0*1+1*2+1*4 = 6 1867 assert_eq!(reader.read_bits(3).unwrap(), 6); 1868 // Read 3 bits [6,7,8]: 0*1+1*2+1*4 = 6 1869 assert_eq!(reader.read_bits(3).unwrap(), 6); 1870 } 1871 1872 #[test] 1873 fn bit_reader_eof() { 1874 let data = [0xFF]; 1875 let mut reader = LzwBitReader::new(&data); 1876 assert!(reader.read_bits(8).is_ok()); 1877 assert!(reader.read_bits(1).is_err()); 1878 } 1879}