about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gifed/Cargo.toml1
-rw-r--r--gifed/src/reader/mod.rs368
-rw-r--r--gifprobe/src/main.rs75
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());
+	}
+}