diff --git a/src/reader/decoder.rs b/src/reader/decoder.rs index 41c9698..554e8d5 100644 --- a/src/reader/decoder.rs +++ b/src/reader/decoder.rs @@ -157,10 +157,9 @@ pub enum Decoded { /// Internal state of the GIF decoder #[derive(Debug, Copy, Clone)] enum State { - Magic(u8, [u8; 6]), - U16Byte1(U16Value, u8), - U16(U16Value), - Byte(ByteValue), + Magic, + ScreenDescriptor, + ImageBlockStart, GlobalPalette(usize), BlockStart(u8), BlockEnd, @@ -181,32 +180,6 @@ use self::State::*; use super::converter::PixelConverter; -/// U16 values that may occur in a GIF image -#[derive(Debug, Copy, Clone)] -enum U16Value { - /// Logical screen descriptor width - ScreenWidth, - /// Logical screen descriptor height - ScreenHeight, - /// Left frame offset - ImageLeft, - /// Top frame offset - ImageTop, - /// Frame width - ImageWidth, - /// Frame height - ImageHeight, -} - -/// Single byte screen descriptor values -#[derive(Debug, Copy, Clone)] -enum ByteValue { - GlobalFlags, - Background { global_flags: u8 }, - AspectRatio { global_flags: u8 }, - ImageFlags, -} - /// Decoder for `Frame::make_lzw_pre_encoded` pub struct FrameDecoder { lzw_reader: LzwReader, @@ -343,6 +316,9 @@ impl LzwReader { /// To just get GIF frames, use [`crate::Decoder`] instead. pub struct StreamingDecoder { state: State, + /// Input bytes are collected here if `update` got `buf` smaller than the minimum required + internal_buffer: [u8; 9], + unused_internal_buffer_len: u8, lzw_reader: LzwReader, skip_frame_decoding: bool, check_frame_consistency: bool, @@ -421,7 +397,9 @@ impl StreamingDecoder { pub(crate) fn with_options(options: &DecodeOptions) -> Self { Self { - state: Magic(0, [0; 6]), + internal_buffer: [0; 9], + unused_internal_buffer_len: 0, + state: Magic, lzw_reader: LzwReader::new(options.check_for_end_code), skip_frame_decoding: options.skip_frame_decoding, check_frame_consistency: options.check_frame_consistency, @@ -532,75 +510,99 @@ impl StreamingDecoder { }) ); + macro_rules! ensure_min_length_buffer ( + ($required:expr) => ({ + let required: usize = $required; + let new_buf = if buf.len() >= required && self.unused_internal_buffer_len == 0 { + buf + } else { + let has = usize::from(self.unused_internal_buffer_len); + if has < required { + let to_copy = buf.len().min(has - required); + let new_len = has + to_copy; + self.internal_buffer[has .. new_len].copy_from_slice(&buf[..to_copy]); + if new_len < required { + self.unused_internal_buffer_len = new_len as u8; + return Ok((to_copy, Decoded::Nothing)); + } else { + self.unused_internal_buffer_len = 0; + } + } + &self.internal_buffer + }; + &new_buf[..required] + }) + ); + let b = *buf.first().ok_or(io::ErrorKind::UnexpectedEof)?; match self.state { - Magic(i, mut version) => if i < 6 { - version[i as usize] = b; - goto!(Magic(i+1, version)) - } else if &version[..3] == b"GIF" { - self.version = match &version[3..] { - b"87a" => Version::V87a, - b"89a" => Version::V89a, - _ => return Err(DecodingError::format("malformed GIF header")) + Magic => { + let version = ensure_min_length_buffer!(6); + + self.version = match version { + b"GIF87a" => Version::V87a, + b"GIF89a" => Version::V89a, + _ => return Err(DecodingError::format("malformed GIF header")), }; - goto!(U16Byte1(U16Value::ScreenWidth, b)) - } else { - Err(DecodingError::format("malformed GIF header")) + + goto!(version.len(), ScreenDescriptor) }, - Byte(value) => { - use self::ByteValue::*; - match value { - GlobalFlags => { - goto!(Byte(Background { global_flags: b })) - }, - Background { global_flags } => { - goto!( - Byte(AspectRatio { global_flags }), - emit Decoded::BackgroundColor(b) - ) - }, - AspectRatio { global_flags } => { - let global_table = global_flags & 0x80 != 0; - let table_size = if global_table { - let table_size = PLTE_CHANNELS * (1 << ((global_flags & 0b111) + 1) as usize); - self.global_color_table.try_reserve_exact(table_size).map_err(|_| io::ErrorKind::OutOfMemory)?; - table_size - } else { - 0usize - }; - goto!(GlobalPalette(table_size)) - }, - ImageFlags => { - let local_table = (b & 0b1000_0000) != 0; - let interlaced = (b & 0b0100_0000) != 0; - let table_size = b & 0b0000_0111; - let check_frame_consistency = self.check_frame_consistency; - let (width, height) = (self.width, self.height); - - let frame = self.try_current_frame()?; - - frame.interlaced = interlaced; - if check_frame_consistency { - // Consistency checks. - if width.checked_sub(frame.width) < Some(frame.left) - || height.checked_sub(frame.height) < Some(frame.top) - { - return Err(DecodingError::format("frame descriptor is out-of-bounds")) - } - } + ScreenDescriptor => { + let desc = ensure_min_length_buffer!(7); + + self.width = u16::from_le_bytes(desc[..2].try_into().unwrap()); + self.height = u16::from_le_bytes(desc[2..4].try_into().unwrap()); + let global_flags = desc[4]; + let background_color = desc[5]; + + let global_table = global_flags & 0x80 != 0; + let table_size = if global_table { + let table_size = PLTE_CHANNELS * (1 << ((global_flags & 0b111) + 1) as usize); + self.global_color_table.try_reserve_exact(table_size).map_err(|_| io::ErrorKind::OutOfMemory)?; + table_size + } else { + 0usize + }; - if local_table { - let pal_len = PLTE_CHANNELS * (1 << (table_size + 1)); - frame.palette.get_or_insert_with(Vec::new) - .try_reserve_exact(pal_len).map_err(|_| io::ErrorKind::OutOfMemory)?; - goto!(LocalPalette(pal_len)) - } else { - goto!(LocalPalette(0)) - } + goto!( + desc.len(), + GlobalPalette(table_size), + emit Decoded::BackgroundColor(background_color) + ) + }, + ImageBlockStart => { + let header = ensure_min_length_buffer!(9); + + let frame = self.current.as_mut().ok_or_else(|| DecodingError::format("bad state"))?; + frame.left = u16::from_le_bytes(header[..2].try_into().unwrap()); + frame.top = u16::from_le_bytes(header[2..4].try_into().unwrap()); + frame.width = u16::from_le_bytes(header[4..6].try_into().unwrap()); + frame.height = u16::from_le_bytes(header[6..8].try_into().unwrap()); + + let flags = header[8]; + frame.interlaced = (flags & 0b0100_0000) != 0; + + if self.check_frame_consistency { + // Consistency checks. + if self.width.checked_sub(frame.width) < Some(frame.left) + || self.height.checked_sub(frame.height) < Some(frame.top) + { + return Err(DecodingError::format("frame descriptor is out-of-bounds")); } } - } + + let local_table = (flags & 0b1000_0000) != 0; + if local_table { + let table_size = flags & 0b0000_0111; + let pal_len = PLTE_CHANNELS * (1 << (table_size + 1)); + frame.palette.get_or_insert_with(Vec::new) + .try_reserve_exact(pal_len).map_err(|_| io::ErrorKind::OutOfMemory)?; + goto!(header.len(), LocalPalette(pal_len)) + } else { + goto!(header.len(), LocalPalette(0)) + } + }, GlobalPalette(left) => { // the global_color_table is guaranteed to have the exact capacity required if left > 0 { @@ -624,7 +626,7 @@ impl StreamingDecoder { match Block::from_u8(type_) { Some(Block::Image) => { self.add_frame(); - goto!(U16Byte1(U16Value::ImageLeft, b), emit Decoded::BlockStart(Block::Image)) + goto!(0, ImageBlockStart, emit Decoded::BlockStart(Block::Image)) } Some(Block::Extension) => { self.ext.data.clear(); @@ -698,11 +700,11 @@ impl StreamingDecoder { } } LocalPalette(left) => { - let n = cmp::min(left, buf.len()); if left > 0 { + let n = cmp::min(left, buf.len()); let src = &buf[..n]; if let Some(pal) = self.try_current_frame()?.palette.as_mut() { - // capacity has already been reserved in ImageFlags + // capacity has already been reserved in ImageBlockStart if pal.capacity() - pal.len() >= src.len() { pal.extend_from_slice(src); } @@ -768,10 +770,6 @@ impl StreamingDecoder { } } } - U16(next) => goto!(U16Byte1(next, b)), - U16Byte1(next, value) => { - goto!(self.read_second_byte(next, value, b)?) - } FrameDecoded => { // end of image data reached self.current = None; @@ -782,37 +780,6 @@ impl StreamingDecoder { } } - fn read_second_byte(&mut self, next: U16Value, value: u8, b: u8) -> Result { - use self::U16Value::*; - let value = (u16::from(b) << 8) | u16::from(value); - Ok(match (next, value) { - (ScreenWidth, width) => { - self.width = width; - U16(U16Value::ScreenHeight) - }, - (ScreenHeight, height) => { - self.height = height; - Byte(ByteValue::GlobalFlags) - }, - (ImageLeft, left) => { - self.try_current_frame()?.left = left; - U16(U16Value::ImageTop) - }, - (ImageTop, top) => { - self.try_current_frame()?.top = top; - U16(U16Value::ImageWidth) - }, - (ImageWidth, width) => { - self.try_current_frame()?.width = width; - U16(U16Value::ImageHeight) - }, - (ImageHeight, height) => { - self.try_current_frame()?.height = height; - Byte(ByteValue::ImageFlags) - } - }) - } - fn read_control_extension(&mut self) -> Result<(), DecodingError> { if self.ext.data.len() != 5 { return Err(DecodingError::format("control extension has wrong length"));