about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGenny <gen@nyble.dev>2021-03-10 02:44:45 -0600
committerGenny <gen@nyble.dev>2021-03-10 02:44:45 -0600
commitfa403b904c8596d8d1731d7f8bfafa6c0145e3dd (patch)
treeb1c0b89da9f51f35b7984a30d9c249788922bb9a
parent743888ae0c1038a92f4a0b64709a313eba70c887 (diff)
downloadgifed-fa403b904c8596d8d1731d7f8bfafa6c0145e3dd.tar.gz
gifed-fa403b904c8596d8d1731d7f8bfafa6c0145e3dd.zip
Implement, roughly, extensions, animation, and looping
-rw-r--r--src/block/block.rs5
-rw-r--r--src/block/extension/graphiccontrol.rs59
-rw-r--r--src/block/extension/mod.rs36
-rw-r--r--src/block/mod.rs10
-rw-r--r--src/gif.rs45
-rw-r--r--src/writer/gifbuilder.rs7
6 files changed, 151 insertions, 11 deletions
diff --git a/src/block/block.rs b/src/block/block.rs
deleted file mode 100644
index 329361f..0000000
--- a/src/block/block.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-use super::IndexedImage;
-
-pub enum Block {
-	IndexedImage(IndexedImage)
-}
\ No newline at end of file
diff --git a/src/block/extension/graphiccontrol.rs b/src/block/extension/graphiccontrol.rs
new file mode 100644
index 0000000..46f44fa
--- /dev/null
+++ b/src/block/extension/graphiccontrol.rs
@@ -0,0 +1,59 @@
+pub struct GraphicControl {
+	pub(crate) packed: u8,
+	pub(crate) delay_time: u16,
+	pub(crate) transparency_index: u8
+}
+
+impl GraphicControl {
+	pub fn new(disposal_method: DisposalMethod, user_input_flag: bool, transparency_flag: bool, delay_time: u16, transparency_index: u8) -> Self {
+		let mut ret = Self {
+			packed: 0,
+			delay_time,
+			transparency_index
+		};
+
+		ret.disposal_method(disposal_method);
+		ret.user_input(user_input_flag);
+		ret.transparency(transparency_flag);
+
+		ret
+	}
+
+	pub fn disposal_method(&mut self, method: DisposalMethod) {
+		match method {
+			DisposalMethod::Clear => self.packed &= 0b111_000_1_1,
+			DisposalMethod::DoNotDispose => self.packed |= 0b000_100_0_0,
+			DisposalMethod::RestoreBackground => self.packed |= 0b000_010_0_0,
+			DisposalMethod::RestorePrevious => self.packed |= 0b000_110_0_0
+		};
+	}
+
+	pub fn user_input(&mut self, flag: bool) {
+		if flag {
+			self.packed |= 0b000_000_1_0;
+		} else {
+			self.packed &= 0b111_111_0_1;
+		}
+	}
+
+	pub fn transparency(&mut self, flag: bool) {
+		if flag {
+			self.packed |= 0b000_000_0_1;
+		} else {
+			self.packed &= 0b111_111_1_0;
+		}
+	}
+
+	pub fn delay_time(&mut self, hundreths: u16) {
+		self.delay_time = hundreths;
+	}
+
+	//TODO: Transparency index setter
+}
+
+pub enum DisposalMethod {
+	Clear,
+	DoNotDispose,
+	RestoreBackground,
+	RestorePrevious
+}
\ No newline at end of file
diff --git a/src/block/extension/mod.rs b/src/block/extension/mod.rs
new file mode 100644
index 0000000..4d65c09
--- /dev/null
+++ b/src/block/extension/mod.rs
@@ -0,0 +1,36 @@
+mod graphiccontrol;
+
+pub use graphiccontrol::{DisposalMethod, GraphicControl};
+
+pub enum Extension {
+	GraphicControl(GraphicControl),
+	Looping(u16)
+}
+
+impl From<&Extension> for Box<[u8]> {
+	fn from(ext: &Extension) -> Self {
+		let mut vec = vec![];
+		vec.push(0x21); // Push the extension introducer
+
+		match ext {
+			Extension::GraphicControl(gc) => {
+				vec.push(0xF9); // Graphic control label
+				vec.push(0x04); // Block size for this extension is always 4
+				vec.push(gc.packed);
+				vec.extend_from_slice(&gc.delay_time.to_le_bytes());
+				vec.push(gc.transparency_index);
+			},
+			Extension::Looping(count) => {
+				vec.push(0xFF); // Application extension label
+				vec.push(0x0B); // 11 bytes in this block
+				vec.extend_from_slice(b"NETSCAPE2.0"); // App. ident. and "auth code"
+				vec.push(0x03); // Sub-block length
+				vec.push(0x01); // Identifies netscape looping extension
+				vec.extend_from_slice(&count.to_le_bytes());
+			} 
+		}
+
+		vec.push(0x00); // Zero-length data block indicates end of extension
+		vec.into_boxed_slice()
+	}
+}
\ No newline at end of file
diff --git a/src/block/mod.rs b/src/block/mod.rs
index a0c2454..38b10ea 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -1,13 +1,17 @@
-mod block;
 mod colortable;
+pub mod extension;
 mod indexedimage;
 mod imagedescriptor;
 mod screendescriptor;
 mod version;
 
-pub use block::Block;
 pub use colortable::ColorTable;
 pub use indexedimage::IndexedImage;
 pub use imagedescriptor::ImageDescriptor;
 pub use screendescriptor::ScreenDescriptor;
-pub use version::Version;
\ No newline at end of file
+pub use version::Version;
+
+pub enum Block {
+	IndexedImage(IndexedImage),
+	Extension(extension::Extension)
+}
\ No newline at end of file
diff --git a/src/gif.rs b/src/gif.rs
index f6ff345..e6ad70f 100644
--- a/src/gif.rs
+++ b/src/gif.rs
@@ -1,4 +1,4 @@
-use crate::block::{Block, ColorTable, ScreenDescriptor, Version};
+use crate::block::{Block, ColorTable, extension::Extension, ScreenDescriptor, Version};
 pub struct Gif {
 	pub header: Version,
 	pub screen_descriptor: ScreenDescriptor,
@@ -32,6 +32,10 @@ impl Gif {
 				Block::IndexedImage(image) => {
 					boxed = image.as_boxed_slice(mcs);
 					out.extend_from_slice(&*boxed);
+				},
+				Block::Extension(ext) => {
+					boxed = ext.into();
+					out.extend_from_slice(&*boxed);
 				}
 			}
 		}
@@ -47,10 +51,11 @@ impl Gif {
 pub mod gif {
 	use crate::Color;
 	use crate::writer::{GifBuilder, ImageBuilder};
+	use crate::block::extension::{DisposalMethod, GraphicControl};
 	use super::*;
 
 	#[test]
-	fn to_vec() {
+	fn to_vec_gif87a() {
 		let gct = vec![
 			Color::new(1, 2, 3), Color::new(253, 254, 255)
 		];
@@ -82,4 +87,40 @@ pub mod gif {
 
 		assert_eq!(actual_out, expected_out);
 	}
+
+	#[test]
+	fn to_vec_gif89a() {
+		let gct = vec![
+			Color::new(1, 2, 3), Color::new(253, 254, 255)
+		];
+		let colortable = vec![
+			Color::new(0, 0, 0), Color::new(128, 0, 255)
+		];
+		let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0];
+
+		let expected_out = vec![
+			0x47, 0x49, 0x46, 0x38, 0x37, 0x61, // Version - GIF87a
+			0x04, 0x00, 0x04, 0x00, 0b1000_0000, 0x00, 0x00, // Logical Screen Descriptor
+			1, 2, 3, 253, 254, 255, // Global Color Table
+			0x2C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0b1000_0000, // Image Descriptor 1
+			0, 0, 0, 128, 0, 255, // Color Table
+			0x02, 0x05, 0x84, 0x1D, 0x81, 0x7A, 0x50, 0x00, // Image Data 1
+			0x21, 0xF9, 0x04, 0b000_010_0_0, 0x40, 0x00, 0x00, 0x00, // Graphic Control Extension
+			0x2C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0b0000_0000, // Image Descriptor 2
+			0x02, 0x05, 0x84, 0x1D, 0x81, 0x7A, 0x50, 0x00, // Image Data 2
+			0x3B // Trailer
+		];
+
+		let actual_out = GifBuilder::new(Version::Gif87a, 4, 4)
+			.global_color_table(gct.into())
+			.image(ImageBuilder::new(4, 4)
+				.color_table(colortable.into())
+				.indicies(indicies.clone())
+			).extension(Extension::GraphicControl(GraphicControl::new(DisposalMethod::RestoreBackground, false, false, 64, 0)))
+			.image(ImageBuilder::new(4, 4)
+				.indicies(indicies)
+			).build().to_vec();
+
+		assert_eq!(actual_out, expected_out);
+	}
 }
\ No newline at end of file
diff --git a/src/writer/gifbuilder.rs b/src/writer/gifbuilder.rs
index 2cdc52c..7e5138a 100644
--- a/src/writer/gifbuilder.rs
+++ b/src/writer/gifbuilder.rs
@@ -1,4 +1,4 @@
-use crate::block::{Block, ColorTable, ScreenDescriptor, Version};
+use crate::block::{Block, ColorTable, ScreenDescriptor, Version, extension::Extension};
 use crate::writer::ImageBuilder;
 use crate::Gif;
 
@@ -44,6 +44,11 @@ impl GifBuilder {
 		self
 	}
 
+	pub fn extension(mut self, ext: Extension) -> Self {
+		self.blocks.push(Block::Extension(ext));
+		self
+	}
+
 	pub fn build(self) -> Gif {
 		let mut lsd = ScreenDescriptor {
 			width: self.width,