diff options
-rw-r--r-- | src/block/block.rs | 5 | ||||
-rw-r--r-- | src/block/extension/graphiccontrol.rs | 59 | ||||
-rw-r--r-- | src/block/extension/mod.rs | 36 | ||||
-rw-r--r-- | src/block/mod.rs | 10 | ||||
-rw-r--r-- | src/gif.rs | 45 | ||||
-rw-r--r-- | src/writer/gifbuilder.rs | 7 |
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, |