diff options
-rw-r--r-- | gifed/Cargo.toml | 1 | ||||
-rw-r--r-- | gifed/src/reader/mod.rs | 368 | ||||
-rw-r--r-- | gifprobe/src/main.rs | 75 |
3 files changed, 267 insertions, 177 deletions
diff --git a/gifed/Cargo.toml b/gifed/Cargo.toml index bf02547..7c92cd3 100644 --- a/gifed/Cargo.toml +++ b/gifed/Cargo.toml @@ -9,7 +9,6 @@ repository = "https://github.com/genuinebyte/gifed" [dev-dependencies] criterion = "0.3" -png = "0.17.2" [dependencies] weezl = "0.1.5" diff --git a/gifed/src/reader/mod.rs b/gifed/src/reader/mod.rs index 2620811..b4d892a 100644 --- a/gifed/src/reader/mod.rs +++ b/gifed/src/reader/mod.rs @@ -1,230 +1,262 @@ use std::{ - borrow::Cow, - convert::{TryFrom, TryInto}, + convert::TryFrom, error::Error, fmt, fs::File, - io::Read, + io::{BufReader, ErrorKind, Read}, + ops::Range, path::Path, }; -use crate::{ - block::{ - extension::{Application, GraphicControl}, - Block, CompressedImage, ImageDescriptor, Palette, ScreenDescriptor, Version, - }, - Gif, +use crate::block::{ + extension::{Application, GraphicControl}, + Block, CompressedImage, ImageDescriptor, Palette, ScreenDescriptor, Version, }; -pub struct GifReader {} - -impl GifReader { - pub fn file<P: AsRef<Path>>(path: P) -> Result<Gif, DecodeError> { - let mut file = File::open(path)?; - let mut reader = SmartReader { - inner: vec![], - position: 0, - }; - file.read_to_end(&mut reader.inner)?; +pub struct Decoder<R: Read> { + reader: SmartReader<R>, +} - let mut gif = Self::read_required(&mut reader)?; +impl Decoder<BufReader<File>> { + pub fn file<P: AsRef<Path>>(path: P) -> Result<Self, DecodeError> { + let file = File::open(path).map_err(|e| DecodeError::IoError(e))?; + let buffreader = BufReader::new(file); + Ok(Decoder::new(buffreader)) + } +} - if gif.screen_descriptor.has_color_table() { - let gct_size = gif.screen_descriptor.color_table_len() * 3; - gif.global_color_table = Some(Self::read_color_table(&mut reader, gct_size)?); - } - - loop { - match Self::read_block(&mut reader)? { - Some(block) => gif.blocks.push(block), - None => return Ok(gif), - } +impl<R: Read> Decoder<R> { + pub fn new(reader: R) -> Self { + Self { + reader: SmartReader::new(reader), } } - fn read_required(reader: &mut SmartReader) -> Result<Gif, DecodeError> { - let version = match reader.take_lossy_utf8(6).as_deref() { - Some("GIF87a") => Version::Gif87a, - Some("GIF89a") => Version::Gif89a, - _ => return Err(DecodeError::UnknownVersionString), - }; - - let mut lsd_buffer: [u8; 7] = [0; 7]; - reader - .read_exact(&mut lsd_buffer) - .ok_or(DecodeError::UnexpectedEof)?; + pub fn read(mut self) -> Result<Reader<R>, DecodeError> { + let version = self.read_version()?; + let screen_descriptor = self.read_screen_descriptor()?; - let lsd = ScreenDescriptor::from(lsd_buffer); + let palette = if screen_descriptor.has_color_table() { + Some( + self.reader + .read_palette(screen_descriptor.color_table_len())?, + ) + } else { + None + }; - Ok(Gif { - header: version, - screen_descriptor: lsd, - global_color_table: None, - blocks: vec![], + Ok(Reader { + version, + screen_descriptor, + palette, + reader: self.reader, + saw_trailer: false, }) } - fn read_color_table(reader: &mut SmartReader, size: usize) -> Result<Palette, DecodeError> { - let buffer = reader - .take(size as usize) - .ok_or(DecodeError::UnexpectedEof)?; + fn read_version(&mut self) -> Result<Version, DecodeError> { + let mut buf = [0; 6]; + self.reader.read_exact(&mut buf)?; - // We get the size from the screen descriptor. This should never return Err - Ok(Palette::try_from(&buffer[..]).unwrap()) + match buf.as_slice() { + b"GIF87a" => Ok(Version::Gif87a), + b"GIF89a" => Ok(Version::Gif89a), + _ => Err(DecodeError::InvalidVersion), + } } - fn read_block(reader: &mut SmartReader) -> Result<Option<Block>, DecodeError> { - let block_id = reader.u8().ok_or(DecodeError::UnexpectedEof)?; - - //TODO: remove panic - match block_id { - 0x21 => Self::read_extension(reader).map(|block| Some(block)), - 0x2C => Self::read_image(reader).map(|block| Some(block)), - 0x3B => Ok(None), - _ => panic!( - "Unknown block identifier {:X} {:X}", - block_id, reader.position - ), - } + fn read_screen_descriptor(&mut self) -> Result<ScreenDescriptor, DecodeError> { + let mut buf = [0; 7]; + self.reader.read_exact(&mut buf)?; + Ok(buf.into()) } +} - fn read_extension(reader: &mut SmartReader) -> Result<Block, DecodeError> { - let extension_id = reader.u8().expect("File ended early"); +pub struct ReadBlock { + pub offset: Range<usize>, + pub block: Block, +} - match extension_id { - 0xF9 => { - reader.skip(1); // Skip block length, we know it - let mut data = [0u8; 4]; - reader - .read_exact(&mut data) - .ok_or(DecodeError::UnexpectedEof)?; - reader.skip(1); // Skip block terminator - - Ok(Block::GraphicControlExtension(GraphicControl::from(data))) +pub struct Reader<R: Read> { + pub version: Version, + pub screen_descriptor: ScreenDescriptor, + pub palette: Option<Palette>, + + reader: SmartReader<R>, + saw_trailer: bool, +} + +impl<R: Read> Reader<R> { + pub fn block(&mut self) -> Result<Option<ReadBlock>, DecodeError> { + if self.saw_trailer { + return Ok(None); + } + + let before = self.reader.bytes_read; + let introducer = self.reader.u8()?; + + match introducer { + 0x2C => { + let mut buf = [0; 9]; + self.reader.read_exact(&mut buf)?; + let descriptor: ImageDescriptor = buf.into(); + + let palette = if descriptor.has_color_table() { + Some(self.reader.read_palette(descriptor.color_table_size())?) + } else { + None + }; + + let lzw_code_size = self.reader.u8()?; + let data = self.reader.take_data_subblocks()?; + let after = self.reader.bytes_read; + + Ok(Some(ReadBlock { + offset: before..after, + block: Block::CompressedImage(CompressedImage { + image_descriptor: descriptor, + local_color_table: palette, + lzw_code_size, + blocks: data, + }), + })) } - 0xFE => Ok(Block::CommentExtension( - reader.take_and_collapse_subblocks(), - )), - 0x01 => todo!(), //TODO: do; plain text extension - 0xFF => { - //TODO: error instead of unwraps - assert_eq!(Some(11), reader.u8()); - let identifier = TryInto::try_into(reader.take(8).unwrap()).unwrap(); - let authentication_code: [u8; 3] = - TryInto::try_into(reader.take(3).unwrap()).unwrap(); - let data = reader.take_and_collapse_subblocks(); - - Ok(Block::ApplicationExtension(Application { - identifier, - authentication_code, - data, + 0x21 => { + let block = self.read_extension()?; + + Ok(Some(ReadBlock { + offset: before..self.reader.bytes_read, + block, })) } - _ => panic!("Unknown Extension Identifier!"), + 0x3B => { + self.saw_trailer = true; + + Ok(None) + } + _ => Err(DecodeError::UnknownBlock { byte: introducer }), } } - fn read_image(mut reader: &mut SmartReader) -> Result<Block, DecodeError> { - let mut buffer = [0u8; 9]; - reader - .read_exact(&mut buffer) - .ok_or(DecodeError::UnexpectedEof)?; - let descriptor = ImageDescriptor::from(buffer); + fn read_extension(&mut self) -> Result<Block, DecodeError> { + let label = self.reader.u8()?; - let color_table = if descriptor.has_color_table() { - let size = descriptor.color_table_size() * 3; - Some(Self::read_color_table(&mut reader, size)?) - } else { - None - }; - - let lzw_csize = reader.u8().ok_or(DecodeError::UnexpectedEof)?; - let compressed_data = reader.take_data_subblocks(); + match label { + 0xF9 => { + // Graphics Control Extension + let _len = self.reader.u8()?; + let mut buf = [0; 4]; + self.reader.read_exact(&mut buf)?; + let _ = self.reader.u8()?; + let gce = GraphicControl::from(buf); + + Ok(Block::GraphicControlExtension(gce)) + } + 0xFE => { + // Comment Extension + let data = self.reader.take_and_collapse_subblocks()?; + Ok(Block::CommentExtension(data)) + } + 0xFF => { + //TODO: Should we check this is 11? + let _len = self.reader.u8()?; + let mut app_id = [0; 8]; + let mut auth = [0; 3]; + self.reader.read_exact(&mut app_id)?; + self.reader.read_exact(&mut auth)?; + let data = self.reader.take_and_collapse_subblocks()?; + let app = Application { + identifier: app_id, + authentication_code: auth, + data, + }; - Ok(Block::CompressedImage(CompressedImage { - image_descriptor: descriptor, - local_color_table: color_table, - lzw_code_size: lzw_csize, - blocks: compressed_data, - })) + Ok(Block::ApplicationExtension(app)) + } + _ => Err(DecodeError::UnknownExtension), + } } } -struct SmartReader { - inner: Vec<u8>, - position: usize, +struct SmartReader<R: Read> { + inner: R, + bytes_read: usize, } -impl SmartReader { - pub fn u8(&mut self) -> Option<u8> { - self.position += 1; - self.inner.get(self.position - 1).map(|b| *b) +impl<R: Read> SmartReader<R> { + pub fn new(reader: R) -> Self { + Self { + inner: reader, + bytes_read: 0, + } } - pub fn u16(&mut self) -> Option<u16> { - self.position += 2; - self.inner - .get(self.position - 2..self.position) - .map(|bytes| u16::from_le_bytes(bytes.try_into().unwrap())) - } + pub fn u8(&mut self) -> Result<u8, DecodeError> { + let mut buffer = [0]; - pub fn skip(&mut self, size: usize) { - self.position += size; + match self.inner.read(&mut buffer) { + Ok(read) => { + self.bytes_read += read; + Ok(buffer[0]) + } + Err(e) => Err(DecodeError::IoError(e)), + } } - pub fn take(&mut self, size: usize) -> Option<&[u8]> { - self.position += size; - self.inner.get(self.position - size..self.position) + #[allow(dead_code)] + pub fn u16(&mut self) -> Result<u16, DecodeError> { + let mut buffer = [0, 0]; + + self.read_exact(&mut buffer).map(|_| { + self.bytes_read += 2; + u16::from_le_bytes(buffer) + }) } //TODO: Result not Option when buffer len - pub fn read_exact(&mut self, buf: &mut [u8]) -> Option<()> { - if self.position + buf.len() > self.inner.len() { - None - } else { - self.position += buf.len(); - buf.copy_from_slice(&self.inner[self.position - buf.len()..self.position]); - Some(()) + pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), DecodeError> { + match self.inner.read_exact(buf) { + Ok(_) => { + self.bytes_read += buf.len(); + Ok(()) + } + Err(e) if e.kind() == ErrorKind::UnexpectedEof => Err(DecodeError::UnexpectedEof), + Err(e) => Err(DecodeError::IoError(e)), } } - pub fn take_vec(&mut self, size: usize) -> Option<Vec<u8>> { - self.position += size; - self.inner - .get(self.position - size..self.position) - .map(|bytes| bytes.to_vec()) - } - - pub fn take_lossy_utf8(&mut self, size: usize) -> Option<Cow<'_, str>> { - self.take(size).map(|bytes| String::from_utf8_lossy(bytes)) - } - - pub fn take_data_subblocks(&mut self) -> Vec<Vec<u8>> { + pub fn take_data_subblocks(&mut self) -> Result<Vec<Vec<u8>>, DecodeError> { let mut blocks = vec![]; loop { - let block_size = self.u8().expect("Failed to read length of sublock"); + let block_size = self.u8()?; if block_size == 0 { - return blocks; + return Ok(blocks); } - let block = self - .take_vec(block_size as usize) - .expect("Failed to read sublock"); + let mut block = vec![0; block_size as usize]; + self.read_exact(&mut block)?; blocks.push(block); } } - pub fn take_and_collapse_subblocks(&mut self) -> Vec<u8> { - let blocks = self.take_data_subblocks(); + pub fn take_and_collapse_subblocks(&mut self) -> Result<Vec<u8>, DecodeError> { + let blocks = self.take_data_subblocks()?; let mut ret = vec![]; for block in blocks { ret.extend_from_slice(&block) } - ret + Ok(ret) + } + + pub fn read_palette(&mut self, count: usize) -> Result<Palette, DecodeError> { + let mut buf = vec![0; count as usize * 3]; + self.read_exact(&mut buf)?; + + Ok(Palette::try_from(buf.as_slice()).unwrap()) } } @@ -234,6 +266,9 @@ pub enum DecodeError { UnknownVersionString, UnexpectedEof, ColorIndexOutOfBounds, + InvalidVersion, + UnknownBlock { byte: u8 }, + UnknownExtension, } impl Error for DecodeError {} @@ -253,6 +288,17 @@ impl fmt::Display for DecodeError { "The image contained an index not found in the color table" ) } + DecodeError::InvalidVersion => { + write!(f, "GIF header was incorrect") + } + DecodeError::UnknownBlock { byte } => { + //TODO: gen- Better error message + write!(f, "No block with introducer {byte:02X}") + } + DecodeError::UnknownExtension => { + //TODO: gen- Better error message + write!(f, "Unknown extension") + } } } } diff --git a/gifprobe/src/main.rs b/gifprobe/src/main.rs index 241c421..2f5c025 100644 --- a/gifprobe/src/main.rs +++ b/gifprobe/src/main.rs @@ -1,9 +1,11 @@ +use std::ops::Range; + use gifed::{ block::{ Block::{self}, - IndexedImage, + CompressedImage, }, - reader::GifReader, + reader::Decoder, }; use owo_colors::OwoColorize; @@ -15,36 +17,49 @@ fn main() { return; }; - let gif = GifReader::file(&file).unwrap(); + let decoder = Decoder::file(&file).unwrap(); + let mut reader = decoder.read().unwrap(); - println!("Version {}", gif.header.yellow()); + println!("Version {}", reader.version.yellow()); println!( "Logical Screen Descriptor\n\tDimensions {}x{}", - gif.screen_descriptor.width.yellow(), - gif.screen_descriptor.height.yellow() + reader.screen_descriptor.width.yellow(), + reader.screen_descriptor.height.yellow() ); - if gif.screen_descriptor.has_color_table() { + if reader.screen_descriptor.has_color_table() { println!( "\tGlobal Color Table Present {}\n\tGlobal Color Table Size {}", "Yes".green(), - gif.screen_descriptor.color_table_len().green() + reader.screen_descriptor.color_table_len().green() ); } else { println!( "\tGlobal Color Table Present {}\n\tGlobal Color Table Size {}", "No".red(), - gif.screen_descriptor.color_table_len().red() + reader.screen_descriptor.color_table_len().red() ); } let mut img_count = 0; let mut hundreths: usize = 0; - for block in gif.blocks { + loop { + let block = match reader.block() { + Ok(Some(block)) => block, + Ok(None) => break, + Err(e) => { + eprintln!("error reading file: {e}"); + std::process::exit(-1); + } + }; + + let offset = block.offset; + let block = block.block; + match block { - Block::IndexedImage(img) => { - describe_image(&img); + Block::CompressedImage(img) => { + describe_image(&img, offset); img_count += 1; } Block::GraphicControlExtension(gce) => { @@ -57,8 +72,11 @@ fn main() { format!("Reserved: {:b}", gce.packed().disposal_method()) }; + print!("Graphic Control Extension"); + print_offset(offset); + println!( - "Graphic Control Extension\n\tDelay Time {}\n\tDispose {}", + "\tDelay Time {}\n\tDispose {}", format!("{}s", gce.delay() as f32 / 100.0).yellow(), dispose_string.yellow() ) @@ -120,9 +138,12 @@ fn main() { ); } -fn describe_image(bli: &IndexedImage) { +fn describe_image(bli: &CompressedImage, offset: Range<usize>) { + print!("Image"); + print_offset(offset); + println!( - "Image\n\tOffset {}x{}\n\tDimensions {}x{}", + "\tOffset {}x{}\n\tDimensions {}x{}", bli.image_descriptor.left.yellow(), bli.image_descriptor.top.yellow(), bli.image_descriptor.width.yellow(), @@ -143,3 +164,27 @@ fn describe_image(bli: &IndexedImage) { ); } } + +fn print_offset(offset: Range<usize>) { + print!(" ["); + print_usize(offset.start); + print!(" … "); + print_usize(offset.end); + println!("]"); +} + +fn print_usize(offset: usize) { + let bytes = offset.to_le_bytes(); + let mut seen_nonzero = false; + for byte in bytes { + if byte == 0 { + if seen_nonzero { + break; + } + } else { + seen_nonzero = true; + } + + print!("{:02X}", byte.cyan()); + } +} |