diff options
author | Genny <gen@nyble.dev> | 2021-09-15 22:16:30 -0500 |
---|---|---|
committer | Genny <gen@nyble.dev> | 2021-09-15 22:16:30 -0500 |
commit | 7b8081a79fb3db4a76f9e4cca8f8a88e6e7f873c (patch) | |
tree | 5eab8cbf47698b031c12f8eadc4c55f674f70c01 | |
parent | cdedae673268c372beb27c6d2f123cdf21f630f1 (diff) | |
download | gifed-7b8081a79fb3db4a76f9e4cca8f8a88e6e7f873c.tar.gz gifed-7b8081a79fb3db4a76f9e4cca8f8a88e6e7f873c.zip |
Reading, fix writing, monocommit
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/block/colortable.rs | 101 | ||||
-rw-r--r-- | src/block/extension/application.rs | 15 | ||||
-rw-r--r-- | src/block/extension/graphiccontrol.rs | 131 | ||||
-rw-r--r-- | src/block/extension/mod.rs | 62 | ||||
-rw-r--r-- | src/block/imagedescriptor.rs | 100 | ||||
-rw-r--r-- | src/block/indexedimage.rs | 87 | ||||
-rw-r--r-- | src/block/mod.rs | 12 | ||||
-rw-r--r-- | src/block/screendescriptor.rs | 95 | ||||
-rw-r--r-- | src/block/version.rs | 29 | ||||
-rw-r--r-- | src/color.rs | 25 | ||||
-rw-r--r-- | src/gif.rs | 351 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/lzw.rs | 306 | ||||
-rw-r--r-- | src/main.rs | 195 | ||||
-rw-r--r-- | src/reader/mod.rs | 239 | ||||
-rw-r--r-- | src/writer/gifbuilder.rs | 115 | ||||
-rw-r--r-- | src/writer/imagebuilder.rs | 112 |
18 files changed, 1347 insertions, 639 deletions
diff --git a/Cargo.toml b/Cargo.toml index f4ffb22..ae6962b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ criterion = "0.3" rand = "0.7.3" [dependencies] +weezl = "0.1.5" +owo-colors = "2.0.0" [[bench]] name = "lzw_encode" diff --git a/src/block/colortable.rs b/src/block/colortable.rs index 97b428f..da9458c 100644 --- a/src/block/colortable.rs +++ b/src/block/colortable.rs @@ -1,67 +1,82 @@ -use std::ops::Deref; pub use crate::Color; +use std::{ + convert::{TryFrom, TryInto}, + ops::Deref, +}; pub struct ColorTable { - table: Vec<Color> + table: Vec<Color>, } impl ColorTable { - pub fn new() -> Self { - Self { - table: vec![] - } - } + pub fn new() -> Self { + Self { table: vec![] } + } - /// Returns the number of colors in the color table as used by the packed - /// fields in the Logical Screen Descriptor and Image Descriptor. You can - /// get the actual size with the [`len`](struct.ColorTable.html#method.len) method. - pub fn packed_len(&self) -> u8 { - ((self.table.len() as f32).log2().ceil() - 1f32) as u8 - } + /// Returns the number of colors in the color table as used by the packed + /// fields in the Logical Screen Descriptor and Image Descriptor. You can + /// get the actual size with the [`len`](struct.ColorTable.html#method.len) method. + pub fn packed_len(&self) -> u8 { + ((self.table.len() as f32).log2().ceil() - 1f32) as u8 + } - /// Returns the number of items in the table - pub fn len(&self) -> usize { - self.table.len() - } + /// Returns the number of items in the table + pub fn len(&self) -> usize { + self.table.len() + } - /// Pushes a color on to the end of the table - pub fn push(&mut self, color: Color) { - self.table.push(color); - } + /// Pushes a color on to the end of the table + pub fn push(&mut self, color: Color) { + self.table.push(color); + } } impl Deref for ColorTable { - type Target = [Color]; + type Target = [Color]; - fn deref(&self) -> &Self::Target { - &self.table - } + fn deref(&self) -> &Self::Target { + &self.table + } } impl From<Vec<Color>> for ColorTable { - fn from(table: Vec<Color>) -> Self{ - ColorTable { - table - } - } + fn from(table: Vec<Color>) -> Self { + ColorTable { table } + } } impl From<&ColorTable> for Box<[u8]> { - fn from(table: &ColorTable) -> Self { - let mut vec = vec![]; + fn from(table: &ColorTable) -> Self { + let mut vec = vec![]; - for color in table.iter() { - vec.extend_from_slice(&[color.r, color.g, color.b]); - } + for color in table.iter() { + vec.extend_from_slice(&[color.r, color.g, color.b]); + } - let packed_len = 2u8.pow(table.packed_len() as u32 + 1); - let padding = (packed_len as usize - table.len()) * 3; - if padding > 0 { - vec.extend_from_slice(&vec![0; padding]); - } + let packed_len = 2usize.pow(table.packed_len() as u32 + 1); + let padding = (packed_len as usize - table.len()) * 3; + if padding > 0 { + vec.extend_from_slice(&vec![0; padding]); + } - vec.into_boxed_slice() - } + vec.into_boxed_slice() + } } -//TODO: TryFrom Vec<u8> (must be multiple of 3 len) and From Vec<Color> \ No newline at end of file +//TODO: TryFrom Vec<u8> (must be multiple of 3 len) and From Vec<Color> +impl TryFrom<&[u8]> for ColorTable { + type Error = (); + + fn try_from(value: &[u8]) -> Result<Self, Self::Error> { + if value.len() % 3 != 0 { + return Err(()); + } else { + Ok(Self { + table: value + .chunks(3) + .map(|slice| Color::from(TryInto::<[u8; 3]>::try_into(slice).unwrap())) + .collect::<Vec<Color>>(), + }) + } + } +} diff --git a/src/block/extension/application.rs b/src/block/extension/application.rs new file mode 100644 index 0000000..b3516d8 --- /dev/null +++ b/src/block/extension/application.rs @@ -0,0 +1,15 @@ +pub struct Application { + pub(crate) identifier: String, // max len 8 + pub(crate) authentication_code: [u8; 3], + pub(crate) data: Vec<u8>, +} + +impl Application { + pub fn identifier(&self) -> &str { + &self.identifier + } + + pub fn authentication_code(&self) -> &[u8] { + &self.authentication_code + } +} diff --git a/src/block/extension/graphiccontrol.rs b/src/block/extension/graphiccontrol.rs index 46f44fa..0d4edd1 100644 --- a/src/block/extension/graphiccontrol.rs +++ b/src/block/extension/graphiccontrol.rs @@ -1,59 +1,86 @@ +use std::convert::TryInto; + +#[derive(Clone, Debug)] pub struct GraphicControl { - pub(crate) packed: u8, - pub(crate) delay_time: u16, - pub(crate) transparency_index: u8 + 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 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(&self) -> u16 { + self.delay_time + } + + pub fn delay_time_mut(&mut self) -> &mut u16 { + &mut self.delay_time + } + + //TODO: Transparency index setter +} + +impl From<[u8; 4]> for GraphicControl { + fn from(arr: [u8; 4]) -> Self { + let packed = arr[0]; + let delay_time = u16::from_le_bytes(arr[1..3].try_into().unwrap()); + let transparency_index = arr[3]; + + Self { + packed, + delay_time, + transparency_index, + } + } } pub enum DisposalMethod { - Clear, - DoNotDispose, - RestoreBackground, - RestorePrevious -} \ No newline at end of file + Clear, + DoNotDispose, + RestoreBackground, + RestorePrevious, +} diff --git a/src/block/extension/mod.rs b/src/block/extension/mod.rs index 66a39b0..d0e57c6 100644 --- a/src/block/extension/mod.rs +++ b/src/block/extension/mod.rs @@ -1,39 +1,43 @@ +mod application; mod graphiccontrol; pub use graphiccontrol::{DisposalMethod, GraphicControl}; +pub use self::application::Application; + pub enum Extension { - GraphicControl(GraphicControl), - Looping(u16) - // Comment - // Plain Text - // Generic Application + GraphicControl(GraphicControl), + Looping(u16), + Comment(Vec<u8>), // Plain Text + Application(Application), } impl From<&Extension> for Box<[u8]> { - fn from(ext: &Extension) -> Self { - let mut vec = vec![]; - vec.push(0x21); // Push the extension introducer + 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()); - } - } + 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()); + } + Extension::Comment(_) => todo!(), + Extension::Application(_) => todo!(), + } - vec.push(0x00); // Zero-length data block indicates end of extension - vec.into_boxed_slice() - } -} \ No newline at end of file + vec.push(0x00); // Zero-length data block indicates end of extension + vec.into_boxed_slice() + } +} diff --git a/src/block/imagedescriptor.rs b/src/block/imagedescriptor.rs index c911baa..415bee7 100644 --- a/src/block/imagedescriptor.rs +++ b/src/block/imagedescriptor.rs @@ -1,45 +1,73 @@ +use std::convert::TryInto; + pub struct ImageDescriptor { - // Image Seperator 0x2C is the first byte // - pub left: u16, - pub top: u16, - pub width: u16, - pub height: u16, - pub packed: u8 + // Image Seperator 0x2C is the first byte // + pub left: u16, + pub top: u16, + pub width: u16, + pub height: u16, + pub packed: u8, } impl ImageDescriptor { - pub fn color_table_present(&mut self, is_present: bool) { - if is_present { - self.packed |= 0b1000_0000; - } else { - self.packed &= 0b0111_1111; - } - } - - pub fn color_table_size(&mut self, size: u8) { - // GCT size is calulated by raising two to this number plus one, - // so we have to work backwards. - let size = (size as f32).log2().ceil() - 1f32; - self.packed |= size as u8; - } - - //TODO: Setter for sort flag in packed field - //TODO: Setter for interlace flag in packed field + pub fn set_color_table_present(&mut self, is_present: bool) { + if is_present { + self.packed |= 0b1000_0000; + } else { + self.packed &= 0b0111_1111; + } + } + + pub fn set_color_table_size(&mut self, size: u8) { + // GCT size is calulated by raising two to this number plus one, + // so we have to work backwards. + let size = (size as f32).log2().ceil() - 1f32; + self.packed |= size as u8; + } + + //TODO: Setter for sort flag in packed field + //TODO: Setter for interlace flag in packed field + + pub fn color_table_present(&self) -> bool { + self.packed & 0b1000_0000 != 0 + } + + pub fn color_table_size(&self) -> usize { + crate::packed_to_color_table_length(self.packed & 0b0000_0111) + } } impl From<&ImageDescriptor> for Box<[u8]> { - fn from(desc: &ImageDescriptor) -> Self { - let mut vec = vec![]; - - vec.push(0x2C); // Image Seperator - vec.extend_from_slice(&desc.left.to_le_bytes()); - vec.extend_from_slice(&desc.top.to_le_bytes()); - vec.extend_from_slice(&desc.width.to_le_bytes()); - vec.extend_from_slice(&desc.height.to_le_bytes()); - vec.push(desc.packed); - - vec.into_boxed_slice() - } + fn from(desc: &ImageDescriptor) -> Self { + let mut vec = vec![]; + + vec.push(0x2C); // Image Seperator + vec.extend_from_slice(&desc.left.to_le_bytes()); + vec.extend_from_slice(&desc.top.to_le_bytes()); + vec.extend_from_slice(&desc.width.to_le_bytes()); + vec.extend_from_slice(&desc.height.to_le_bytes()); + vec.push(desc.packed); + + vec.into_boxed_slice() + } +} + +impl From<[u8; 9]> for ImageDescriptor { + fn from(arr: [u8; 9]) -> Self { + let left = u16::from_le_bytes(arr[0..2].try_into().unwrap()); + let top = u16::from_le_bytes(arr[2..4].try_into().unwrap()); + let width = u16::from_le_bytes(arr[4..6].try_into().unwrap()); + let height = u16::from_le_bytes(arr[6..8].try_into().unwrap()); + let packed = arr[8]; + + Self { + left, + top, + width, + height, + packed, + } + } } -//TODO: Impl to allow changing the packed field easier \ No newline at end of file +//TODO: Impl to allow changing the packed field easier diff --git a/src/block/indexedimage.rs b/src/block/indexedimage.rs index ae2da06..52be3d5 100644 --- a/src/block/indexedimage.rs +++ b/src/block/indexedimage.rs @@ -1,45 +1,54 @@ -use crate::LZW; +use std::convert::TryFrom; + use super::{ColorTable, ImageDescriptor}; +use crate::LZW; pub struct IndexedImage { - pub image_descriptor: ImageDescriptor, - pub local_color_table: Option<ColorTable>, - pub indicies: Vec<u8> + pub image_descriptor: ImageDescriptor, + pub local_color_table: Option<ColorTable>, + pub indicies: Vec<u8>, } impl IndexedImage { - pub fn as_boxed_slice(&self, minimum_code_size: u8) -> Box<[u8]> { - let mut out = vec![]; - - let mut boxed: Box<[u8]> = (&self.image_descriptor).into(); - out.extend_from_slice(&*boxed); - - // Get the mcs while we write out the color table - let mut mcs = if let Some(lct) = &self.local_color_table { - boxed = lct.into(); - out.extend_from_slice(&*boxed); - - lct.packed_len() - } else { - minimum_code_size - }; - - if mcs < 2 { - mcs = 2; // Must be true: 0 <= mcs <= 8 - } - - // First write out the MCS - out.push(mcs); - - let compressed = LZW::encode(mcs, &self.indicies); - - for chunk in compressed.chunks(255) { - out.push(chunk.len() as u8); - out.extend_from_slice(chunk); - } - // Data block length 0 to indicate an end - out.push(0x00); - - out.into_boxed_slice() - } -} \ No newline at end of file + pub fn as_boxed_slice(&self, minimum_code_size: u8) -> Box<[u8]> { + let mut out = vec![]; + + let mut boxed: Box<[u8]> = (&self.image_descriptor).into(); + out.extend_from_slice(&*boxed); + + // Get the mcs while we write out the color table + let mut mcs = if let Some(lct) = &self.local_color_table { + boxed = lct.into(); + out.extend_from_slice(&*boxed); + + lct.packed_len() + } else { + minimum_code_size + 1 + }; + + if mcs < 2 { + mcs = 2; // Must be true: 0 <= mcs <= 8 + } + + // First write out the MCS + out.push(mcs); + + let compressed = LZW::encode(mcs, &self.indicies); + + for chunk in compressed.chunks(255) { + out.push(chunk.len() as u8); + out.extend_from_slice(chunk); + } + // Data block length 0 to indicate an end + out.push(0x00); + + out.into_boxed_slice() + } +} + +pub struct BlockedImage { + pub image_descriptor: ImageDescriptor, + pub local_color_table: Option<ColorTable>, + pub lzw_minimum_code_size: u8, + pub blocks: Vec<Vec<u8>>, +} diff --git a/src/block/mod.rs b/src/block/mod.rs index 38b10ea..e3136bf 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -1,17 +1,19 @@ mod colortable; pub mod extension; -mod indexedimage; mod imagedescriptor; +mod indexedimage; mod screendescriptor; mod version; pub use colortable::ColorTable; -pub use indexedimage::IndexedImage; pub use imagedescriptor::ImageDescriptor; +pub use indexedimage::BlockedImage; +pub use indexedimage::IndexedImage; pub use screendescriptor::ScreenDescriptor; pub use version::Version; pub enum Block { - IndexedImage(IndexedImage), - Extension(extension::Extension) -} \ No newline at end of file + IndexedImage(IndexedImage), + BlockedImage(BlockedImage), + Extension(extension::Extension), +} diff --git a/src/block/screendescriptor.rs b/src/block/screendescriptor.rs index d53d252..ff70896 100644 --- a/src/block/screendescriptor.rs +++ b/src/block/screendescriptor.rs @@ -1,40 +1,69 @@ +use std::convert::TryInto; + pub struct ScreenDescriptor { - pub width: u16, - pub height: u16, - pub packed: u8, - pub background_color_index: u8, - pub pixel_aspect_ratio: u8 + pub width: u16, + pub height: u16, + pub packed: u8, + pub background_color_index: u8, + pub pixel_aspect_ratio: u8, } impl ScreenDescriptor { - pub fn color_table_present(&mut self, is_present: bool) { - if is_present { - self.packed |= 0b1000_0000; - } else { - self.packed &= 0b0111_1111; - } - } - - pub fn color_table_size(&mut self, size: u8) { - // GCT size is calulated by raising two to this number plus one, - // so we have to work backwards. - let size = (size as f32).log2().ceil() - 1f32; - self.packed |= size as u8; - } - - //TODO: Setter for sort flag in packed field - //TODO: Setter for color resolution in packed field + pub fn set_color_table_present(&mut self, is_present: bool) { + if is_present { + self.packed |= 0b1000_0000; + } else { + self.packed &= 0b0111_1111; + } + } + + pub fn set_color_table_size(&mut self, size: u8) { + println!("scts: {}", size); + // GCT size is calulated by raising two to this number plus one, + // so we have to work backwards. + let size = (size as f32).log2().ceil() - 1f32; + self.packed |= size as u8; + } + + //TODO: Setter for sort flag in packed field + //TODO: Setter for color resolution in packed field + + pub fn color_table_present(&self) -> bool { + self.packed & 0b1000_0000 != 0 + } + + pub fn color_table_len(&self) -> usize { + crate::packed_to_color_table_length(self.packed & 0b0000_0111) + } } impl From<&ScreenDescriptor> for Box<[u8]> { - fn from(lsd: &ScreenDescriptor) -> Self { - let mut vec = vec![]; - vec.extend_from_slice(&lsd.width.to_le_bytes()); - vec.extend_from_slice(&lsd.height.to_le_bytes()); - vec.push(lsd.packed); - vec.push(lsd.background_color_index); - vec.push(lsd.pixel_aspect_ratio); - - vec.into_boxed_slice() - } -} \ No newline at end of file + fn from(lsd: &ScreenDescriptor) -> Self { + let mut vec = vec![]; + vec.extend_from_slice(&lsd.width.to_le_bytes()); + vec.extend_from_slice(&lsd.height.to_le_bytes()); + vec.push(lsd.packed); + vec.push(lsd.background_color_index); + vec.push(lsd.pixel_aspect_ratio); + + vec.into_boxed_slice() + } +} + +impl From<[u8; 7]> for ScreenDescriptor { + fn from(arr: [u8; 7]) -> Self { + let width = u16::from_le_bytes(arr[0..2].try_into().unwrap()); + let height = u16::from_le_bytes(arr[2..4].try_into().unwrap()); + let packed = arr[4]; + let background_color_index = arr[5]; + let pixel_aspect_ratio = arr[6]; + + Self { + width, + height, + packed, + background_color_index, + pixel_aspect_ratio, + } + } +} diff --git a/src/block/version.rs b/src/block/version.rs index a5d688d..b785f27 100644 --- a/src/block/version.rs +++ b/src/block/version.rs @@ -1,13 +1,24 @@ +use std::fmt; + pub enum Version { - Gif87a, - Gif89a + Gif87a, + Gif89a, } impl From<&Version> for &[u8] { - fn from(version: &Version) -> Self { - match version { - Version::Gif87a => b"GIF87a", - Version::Gif89a => b"GIF89a" - } - } -} \ No newline at end of file + fn from(version: &Version) -> Self { + match version { + Version::Gif87a => b"GIF87a", + Version::Gif89a => b"GIF89a", + } + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Version::Gif87a => write!(f, "GIF87a"), + Version::Gif89a => write!(f, "GIF89a"), + } + } +} diff --git a/src/color.rs b/src/color.rs index dc134ef..764acaf 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,11 +1,22 @@ +#[derive(Copy, Clone, Debug)] pub struct Color { - pub r: u8, - pub g: u8, - pub b: u8 + pub r: u8, + pub g: u8, + pub b: u8, } impl Color { - pub fn new(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b } - } -} \ No newline at end of file + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b } + } +} + +impl From<[u8; 3]> for Color { + fn from(arr: [u8; 3]) -> Self { + Self { + r: arr[0], + g: arr[1], + b: arr[2], + } + } +} diff --git a/src/gif.rs b/src/gif.rs index e6ad70f..90564fc 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -1,126 +1,241 @@ -use crate::block::{Block, ColorTable, extension::Extension, ScreenDescriptor, Version}; +use crate::block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}; pub struct Gif { - pub header: Version, - pub screen_descriptor: ScreenDescriptor, - pub global_color_table: Option<ColorTable>, - pub blocks: Vec<Block> - // Trailer at the end of this struct is 0x3B // + pub header: Version, + pub screen_descriptor: ScreenDescriptor, + pub global_color_table: Option<ColorTable>, + pub blocks: Vec<Block>, // Trailer at the end of this struct is 0x3B // } impl Gif { - pub fn to_vec(&self) -> Vec<u8> { - let mut out = vec![]; - - out.extend_from_slice((&self.header).into()); - - let mut boxed: Box<[u8]> = (&self.screen_descriptor).into(); - out.extend_from_slice(&*boxed); - - // While we output the color table, grab it's length to use when - // outputting the image, or 0 if we don't have a GCT - let mcs = if let Some(gct) = &self.global_color_table { - boxed = gct.into(); - out.extend_from_slice(&*boxed); - - gct.packed_len() - } else { - 0 - }; - - for block in self.blocks.iter() { - match block { - 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); - } - } - } - - // Write Trailer - out.push(0x3B); - - out - } + pub fn to_vec(&self) -> Vec<u8> { + let mut out = vec![]; + + out.extend_from_slice((&self.header).into()); + + let mut boxed: Box<[u8]> = (&self.screen_descriptor).into(); + out.extend_from_slice(&*boxed); + + // While we output the color table, grab it's length to use when + // outputting the image, or 0 if we don't have a GCT + let mcs = if let Some(gct) = &self.global_color_table { + boxed = gct.into(); + out.extend_from_slice(&*boxed); + + gct.packed_len() + } else { + 0 + }; + + for block in self.blocks.iter() { + match block { + Block::IndexedImage(image) => { + boxed = image.as_boxed_slice(mcs); + out.extend_from_slice(&*boxed); + } + Block::BlockedImage(_) => unreachable!(), + Block::Extension(ext) => { + boxed = ext.into(); + out.extend_from_slice(&*boxed); + } + } + } + + // Write Trailer + out.push(0x3B); + + out + } } #[cfg(test)] pub mod gif { - use crate::Color; - use crate::writer::{GifBuilder, ImageBuilder}; - use crate::block::extension::{DisposalMethod, GraphicControl}; - use super::*; - - #[test] - fn to_vec_gif87a() { - 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 - 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()) - ).image(ImageBuilder::new(4, 4) - .indicies(indicies) - ).build().to_vec(); - - 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 + use super::*; + use crate::block::extension::{DisposalMethod, GraphicControl}; + use crate::writer::{GifBuilder, ImageBuilder}; + use crate::Color; + + #[test] + fn to_vec_gif87a() { + 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 + 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()), + ) + .image(ImageBuilder::new(4, 4).indicies(indicies)) + .build() + .to_vec(); + + 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); + } +} diff --git a/src/lib.rs b/src/lib.rs index 83b0b77..6c0203c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,20 @@ mod gif; mod lzw; pub mod block; +pub mod reader; pub mod writer; pub use color::Color; pub use gif::Gif; pub use lzw::LZW; +/// Perform the algorithm to get the length of a color table from +/// the value of the packed field. The max value here is 256 +pub(crate) fn packed_to_color_table_length(packed: u8) -> usize { + 2usize.pow(packed as u32 + 1) +} + //TODO: Be sure to check that fields in LSD and Img. Desc. that were reserved //in 87a aren't set if version is 87a, or that we return a warning, etc. Just //remember about this. -//bottom of page 24 in 89a \ No newline at end of file +//bottom of page 24 in 89a diff --git a/src/lzw.rs b/src/lzw.rs index 1970617..053c5c4 100644 --- a/src/lzw.rs +++ b/src/lzw.rs @@ -2,174 +2,172 @@ use std::collections::HashMap; pub struct LZW {} impl LZW { - //TODO: Cleanup and make not awful - pub fn encode(minimum_size: u8, indicies: &[u8]) -> Vec<u8> { - let mut dictionary: HashMap<Vec<u8>, u16> = HashMap::new(); - - let cc: u16 = 2u16.pow(minimum_size as u32); - let eoi: u16 = cc+1; - - let mut index: u16 = eoi+1; // Next code number - let mut codesize: u8 = minimum_size+1; // Current code size - - //println!("cc: {} - eoi: {}", cc, eoi); - - // Populate starting codes - for code in 0..cc { - dictionary.insert(vec![code as u8], code); - } - - let mut iter = indicies.iter(); - let mut out = BitStream::new(); - let mut buffer = vec![*iter.next().unwrap()]; //TODO: Remove unwrap - - //"Encoders should output a Clear code as the first code of each image data stream." - out.push_bits(codesize, cc); - - //println!("Before Loop\n\tBuffer: {:?}\n\tCodesize:{}\n\tIndex:{}", buffer, codesize, index); - - for next_code in iter { - buffer.push(*next_code); - - //println!("Buffer: {:?} - Codesize:{} - Index:{}\n\tDict: {:?}", buffer, codesize, index, dictionary); - - match dictionary.get(&buffer) { - Some(_code) => { - //println!("\tPresent!"); - continue; - }, - None => { - buffer.pop(); - match dictionary.get(&buffer) { - Some(code) => { - out.push_bits(codesize, *code); - //println!("\tOutputting {} with {} bits. Buffer is {:?}", *code, codesize, buffer); - - // Add new entry for buffer and increase the index - buffer.push(*next_code); - dictionary.insert(buffer, index); - index += 1; - - // Increase code size if we should - if index-1 == 2u16.pow(codesize as u32) { - codesize += 1; - } - - // Reset the buffer to only contain next_code - buffer = vec![*next_code]; - }, - None => panic!("No dictionary entry when there should be! Something is wrong!") - } - } - } - } - - if buffer.len() > 0 { - match dictionary.get(&buffer) { - None => panic!("No codes left but not in the dictionary!"), - Some(code) => out.push_bits(codesize, *code) - } - } - out.push_bits(codesize, eoi); - - out.vec() - } + pub fn encode(minimum_size: u8, indicies: &[u8]) -> Vec<u8> { + let mut dictionary: HashMap<Vec<u8>, u16> = HashMap::new(); + + let cc = 2u16.pow(minimum_size as u32); + let eoi = cc + 1; + + // Fill dictionary with self-descriptive values + for value in 0..cc { + dictionary.insert(vec![value as u8], value); + } + + let mut next_code = eoi + 1; + let mut code_size = minimum_size + 1; + + let mut iter = indicies.into_iter(); + let mut out = BitStream::new(); + let mut buffer = vec![*iter.next().unwrap()]; + + out.push_bits(code_size, cc); + + for &indicie in iter { + buffer.push(indicie); + + if !dictionary.contains_key(&buffer) { + buffer.pop(); + + if let Some(&code) = dictionary.get(&buffer) { + out.push_bits(code_size, code); + + // Put the code back and add the vec to the dict + buffer.push(indicie); + dictionary.insert(buffer.clone(), next_code); + next_code += 1; + + // If the next_code can't fit in the code_size, we have to increase it + if next_code - 1 == 2u16.pow(code_size as u32) { + code_size += 1; + } + + buffer.clear(); + buffer.push(indicie); + } else { + unreachable!() + } + } + } + + if buffer.len() > 0 { + match dictionary.get(&buffer) { + Some(&code) => out.push_bits(code_size, code), + None => { + panic!("Codes left in the buffer but the buffer is not a valid dictionary key!") + } + } + } + out.push_bits(code_size, eoi); + + out.vec() + } } #[cfg(test)] mod lzw_test { - use super::*; + use super::*; - #[test] - fn encode() { - let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; - let output = vec![0x84, 0x1D, 0x81, 0x7A, 0x50]; + #[test] + fn encode() { + let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; + let output = vec![0x84, 0x1D, 0x81, 0x7A, 0x50]; - let lzout = LZW::encode(2, &indicies); + let lzout = LZW::encode(2, &indicies); - assert_eq!(lzout, output); - } + assert_eq!(lzout, output); + } + + #[test] + fn against_weezl() { + let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; + let weezl = weezl::encode::Encoder::new(weezl::BitOrder::Lsb, 2) + .encode(&indicies) + .unwrap(); + let us = LZW::encode(2, &indicies); + + assert_eq!(weezl, us); + } } struct BitStream { - formed: Vec<u8>, - current: u8, - index: u8 + formed: Vec<u8>, + current: u8, + index: u8, } impl BitStream { - fn new() -> Self { - Self { - formed: vec![], - current: 0, - index: 0 - } - } - - fn push_bits(&mut self, count: u8, data: u16) { - let mut new_index = self.index + count; - let mut current32 = (self.current as u32) | ((data as u32) << self.index); - - loop { - if new_index >= 8 { - self.formed.push(current32 as u8); - current32 = current32 >> 8; - new_index -= 8; - } else { - self.current = current32 as u8; - self.index = new_index; - - break; - } - } - } - - fn vec(self) -> Vec<u8> { - let mut out = self.formed; - - if self.index != 0 { - out.push(self.current); - } - - out - } + fn new() -> Self { + Self { + formed: vec![], + current: 0, + index: 0, + } + } + + fn push_bits(&mut self, count: u8, data: u16) { + let mut new_index = self.index + count; + let mut current32 = (self.current as u32) | ((data as u32) << self.index); + + loop { + if new_index >= 8 { + self.formed.push(current32 as u8); + current32 = current32 >> 8; + new_index -= 8; + } else { + self.current = current32 as u8; + self.index = new_index; + + break; + } + } + } + + fn vec(self) -> Vec<u8> { + let mut out = self.formed; + + if self.index != 0 { + out.push(self.current); + } + + out + } } #[cfg(test)] mod bitstream_test { - use super::*; - - #[test] - fn short_push() { - let mut bs = BitStream::new(); - bs.push_bits(2, 3); - bs.push_bits(2, 3); - bs.push_bits(3, 1); - bs.push_bits(2, 3); - - let bsvec = bs.vec(); - - for byte in &bsvec { - print!("{:b} ", byte); - } - println!(""); - - assert_eq!(bsvec, vec![0b1001_1111, 0b0000_0001]); - } - - #[test] - fn long_push() { - let mut bs = BitStream::new(); - bs.push_bits(1, 1); - bs.push_bits(12, 2049); - - let bsvec = bs.vec(); - - for byte in &bsvec { - print!("{:b} ", byte); - } - println!(""); - - assert_eq!(bsvec, vec![0b0000_0011, 0b0001_0000]); - } -} \ No newline at end of file + use super::*; + + #[test] + fn short_push() { + let mut bs = BitStream::new(); + bs.push_bits(2, 3); + bs.push_bits(2, 3); + bs.push_bits(3, 1); + bs.push_bits(2, 3); + + let bsvec = bs.vec(); + + for byte in &bsvec { + print!("{:b} ", byte); + } + println!(""); + + assert_eq!(bsvec, vec![0b1001_1111, 0b0000_0001]); + } + + #[test] + fn long_push() { + let mut bs = BitStream::new(); + bs.push_bits(1, 1); + bs.push_bits(12, 2049); + + let bsvec = bs.vec(); + + for byte in &bsvec { + print!("{:b} ", byte); + } + println!(""); + + assert_eq!(bsvec, vec![0b0000_0011, 0b0001_0000]); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..269470a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,195 @@ +use std::{fs::File, io::Write}; + +use gifed::{ + block::{ + extension::{Extension, GraphicControl}, + BlockedImage, ColorTable, Version, + }, + reader::GifReader, + writer::{GifBuilder, ImageBuilder}, + Color, +}; +use owo_colors::OwoColorize; + +fn main() { + write_test(); + //return; + let gif = GifReader::file("test.gif"); + //let gif = GifReader::file("/home/gen/Downloads/tas_tillie.gif"); + + println!("Version {}", gif.header.yellow()); + println!( + "Logical Screen Descriptor\n\tDimensions {}x{}", + gif.screen_descriptor.width.yellow(), + gif.screen_descriptor.height.yellow() + ); + + if gif.screen_descriptor.color_table_present() { + println!( + "\tGlobal Color Table Present {}\n\tGlobal Color Table Size {}", + "Yes".green(), + gif.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() + ); + } + + for block in gif.blocks { + match block { + gifed::block::Block::IndexedImage(_) => todo!(), + gifed::block::Block::BlockedImage(bli) => describe_blocked_image(bli), + gifed::block::Block::Extension(ext) => match ext { + gifed::block::extension::Extension::GraphicControl(gce) => { + println!( + "Graphic Control Extension\n\tDelay Time {}", + format!("{}s", gce.delay_time() as f32 / 100.0).yellow() + ) + } + gifed::block::extension::Extension::Looping(_) => todo!(), + gifed::block::extension::Extension::Comment(cmt) => { + println!("Comment Extension\n\tLength {}", cmt.len()) + } + gifed::block::extension::Extension::Application(app) => { + let auth = app.authentication_code(); + println!("Application Extension\n\tIdentifier {}\n\tAuthentication {:02X} {:02X} {:02X}",app.identifier().yellow(), auth[0].yellow(), auth[1].yellow(), auth[2].yellow()); + } + }, + } + } +} + +fn describe_blocked_image(bli: BlockedImage) { + println!( + "Image\n\tOffset {}x{}\n\tDimensions {}x{}", + bli.image_descriptor.left.yellow(), + bli.image_descriptor.top.yellow(), + bli.image_descriptor.width.yellow(), + bli.image_descriptor.height.yellow(), + ); + + if bli.image_descriptor.color_table_present() { + println!( + "\tLocal Color Table Present {}\n\tLocal Color Table Size {}", + "Yes".green(), + bli.image_descriptor.color_table_size().green() + ); + } else { + println!( + "\tLocal Color Table Present {}\n\tLocal Color Table Size {}", + "No".red(), + bli.image_descriptor.color_table_size().red() + ); + } + + println!( + "\t{} image data sub-blocks totaling {} bytes", + bli.blocks.len().yellow(), + bli.blocks + .iter() + .map(|vec| vec.len()) + .sum::<usize>() + .yellow() + ) +} + +fn write_test() { + const size: u16 = 256; + let gcon = GraphicControl::new( + gifed::block::extension::DisposalMethod::Clear, + false, + false, + 25, + 0, + ); + + let mut gct = ColorTable::new(); + gct.push(Color { + r: 0x44, + g: 0x44, + b: 0x44, + }); + gct.push(Color { + r: 0x88, + g: 0x44, + b: 0xDD, + }); + + println!("{} {}", gct.packed_len(), gct.len()); + + let mut builder = GifBuilder::new(Version::Gif89a, size, size).global_color_table(gct); + + for x in 4..16 { + let mut raw = vec![0; size as usize * size as usize]; + + for i in x * 7..x * 7 + 32 { + for j in x * 7..x * 7 + 32 { + let index = i * size as usize + j; + raw[index as usize] = 1; + } + } + + builder = builder + .extension(Extension::GraphicControl(gcon.clone())) + .image(ImageBuilder::new(size, size).indicies(raw)); + } + + builder = builder.extension(Extension::Looping(0)); + + let vec = builder.build().to_vec(); + File::create("test.gif").unwrap().write_all(&vec).unwrap(); + + let mut text_gif = File::create("test_gif.txt").unwrap(); + for byte in vec { + text_gif + .write_all(&format!("{:02X} ", byte).as_bytes()) + .unwrap(); + } +} + +fn write_static_test() { + const size: u16 = 256; + + let mut gct = ColorTable::new(); + gct.push(Color { + r: 0x44, + g: 0x44, + b: 0x44, + }); + gct.push(Color { + r: 0x55, + g: 0x44, + b: 0x88, + }); + + println!("{} {}", gct.packed_len(), gct.len()); + + let mut builder = GifBuilder::new(Version::Gif87a, size, size).global_color_table(gct); + + let x = 4; + let mut raw = vec![0; size as usize * size as usize]; + + for i in x * 7..x * 7 + 8 { + for j in x * 7..x * 7 + 8 { + let index = i * size as usize + j; + raw[index as usize] = 1; + } + } + + builder = builder.image(ImageBuilder::new(size, size).indicies(raw)); + + //builder = builder.extension(Extension::Looping(0)); + + let vec = builder.build().to_vec(); + File::create("test.gif").unwrap().write_all(&vec).unwrap(); + + let mut text_gif = File::create("test_gif.txt").unwrap(); + for byte in vec { + text_gif + .write_all(&format!("{:02X} ", byte).as_bytes()) + .unwrap(); + } +} diff --git a/src/reader/mod.rs b/src/reader/mod.rs new file mode 100644 index 0000000..606de11 --- /dev/null +++ b/src/reader/mod.rs @@ -0,0 +1,239 @@ +use std::{ + borrow::Cow, + convert::{TryFrom, TryInto}, + fs::File, + io::{BufRead, BufReader, Read}, + path::Path, +}; + +use crate::{ + block::{ + extension::{Application, Extension, GraphicControl}, + Block, BlockedImage, ColorTable, ImageDescriptor, ScreenDescriptor, Version, + }, + Gif, +}; + +pub struct GifReader {} + +impl GifReader { + pub fn file<P: AsRef<Path>>(path: P) -> Gif { + let mut file = File::open(path).expect("Failed to open file"); + let mut reader = SmartReader { + inner: vec![], + position: 0, + }; + file.read_to_end(&mut reader.inner) + .expect("Failed to read gif"); + + let mut gif = Self::read_required(&mut reader); + + if gif.screen_descriptor.color_table_present() { + 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) => { + /*match &block { + Block::IndexedImage(_) => println!("Indexed Image"), + Block::BlockedImage(_) => println!("Blocked Image"), + Block::Extension(ext) => match ext { + Extension::GraphicControl(_) => println!("Graphic Cotrol Extension"), + Extension::Looping(_) => println!("Netscape Extension"), + Extension::Comment(vec) => { + println!("Comment Extension {:X}", vec.len()) + } + Extension::Application(_) => todo!(), + }, + }*/ + + gif.blocks.push(block) + } + None => return gif, + } + } + } + + fn read_required(reader: &mut SmartReader) -> Gif { + let version = match reader.take_lossy_utf8(6).as_deref() { + Some("GIF87a") => Version::Gif87a, + Some("GIF89a") => Version::Gif89a, + _ => panic!("Version string is unknown"), + }; + + let mut lsd_buffer: [u8; 7] = [0; 7]; + reader + .read_exact(&mut lsd_buffer) + .expect("Failed to read Logical Screen Descriptor from gif"); + + let lsd = ScreenDescriptor::from(lsd_buffer); + + Gif { + header: version, + screen_descriptor: lsd, + global_color_table: None, + blocks: vec![], + } + } + + fn read_color_table(reader: &mut SmartReader, size: usize) -> ColorTable { + let buffer = reader + .take(size as usize) + .expect("Failed to read Color Table"); + + ColorTable::try_from(&buffer[..]).expect("Failed to parse Color Table") + } + + fn read_block(reader: &mut SmartReader) -> Option<Block> { + let block_id = reader.u8().expect("File ended early"); + + match block_id { + 0x21 => Some(Self::read_extension(reader)), + 0x2C => Some(Self::read_image(reader)), + 0x3B => None, + _ => None, /*panic!( + "Unknown block identifier {:X} {:X}", + block_id, reader.position + ),*/ + } + } + + fn read_extension(mut reader: &mut SmartReader) -> Block { + let mut extension_id = reader.u8().expect("File ended early"); + + match extension_id { + 0xF9 => { + reader.skip(1); // Skip block length, we know it + let mut data = [0u8; 4]; + reader + .read_exact(&mut data) + .expect("Data ended early in graphics control extension sublock"); + reader.skip(1); // Skip block terminator + + Block::Extension(Extension::GraphicControl(GraphicControl::from(data))) + } + 0xFE => Block::Extension(Extension::Comment(reader.take_and_collapse_subblocks())), + 0x01 => todo!(), // plain text extension + 0xFF => { + assert_eq!(Some(11), reader.u8()); + let identifier = reader.take_lossy_utf8(8).unwrap().to_string(); + let authentication_code: [u8; 3] = + TryInto::try_into(reader.take(3).unwrap()).unwrap(); + let data = reader.take_and_collapse_subblocks(); + + Block::Extension(Extension::Application(Application { + identifier, + authentication_code, + data, + })) + } + _ => panic!("Unknown Extension Identifier!"), + } + } + + fn read_image(mut reader: &mut SmartReader) -> Block { + let mut buffer = [0u8; 9]; + reader + .read_exact(&mut buffer) + .expect("Failed to read Image Descriptor"); + let descriptor = ImageDescriptor::from(buffer); + + let color_table = if descriptor.color_table_present() { + let size = descriptor.color_table_size() * 3; + Some(Self::read_color_table(&mut reader, size)) + } else { + None + }; + + let lzw_csize = reader.u8().expect("Failed to read LZW Minimum Code Size"); + + let blocks = reader.take_data_subblocks(); + + Block::BlockedImage(BlockedImage { + image_descriptor: descriptor, + local_color_table: color_table, + lzw_minimum_code_size: lzw_csize, + blocks, + }) + } +} + +struct SmartReader { + inner: Vec<u8>, + position: usize, +} + +impl SmartReader { + pub fn u8(&mut self) -> Option<u8> { + self.position += 1; + self.inner.get(self.position - 1).map(|b| *b) + } + + 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 skip(&mut self, size: usize) { + self.position += size; + } + + pub fn take(&mut self, size: usize) -> Option<&[u8]> { + self.position += size; + self.inner.get(self.position - size..self.position) + } + + //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 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>> { + let mut blocks = vec![]; + + loop { + let block_size = self.u8().expect("Failed to read length of sublock"); + + if block_size == 0 { + return blocks; + } + + let block = self + .take_vec(block_size as usize) + .expect("Failed to read sublock"); + + blocks.push(block); + } + } + + pub fn take_and_collapse_subblocks(&mut self) -> Vec<u8> { + let blocks = self.take_data_subblocks(); + let mut ret = vec![]; + for block in blocks { + ret.extend_from_slice(&block) + } + + ret + } +} diff --git a/src/writer/gifbuilder.rs b/src/writer/gifbuilder.rs index 7e5138a..d16be56 100644 --- a/src/writer/gifbuilder.rs +++ b/src/writer/gifbuilder.rs @@ -1,73 +1,74 @@ -use crate::block::{Block, ColorTable, ScreenDescriptor, Version, extension::Extension}; +use crate::block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}; use crate::writer::ImageBuilder; use crate::Gif; pub struct GifBuilder { - version: Version, - width: u16, - height: u16, - background_color_index: u8, - global_color_table: Option<ColorTable>, - blocks: Vec<Block> + version: Version, + width: u16, + height: u16, + background_color_index: u8, + global_color_table: Option<ColorTable>, + blocks: Vec<Block>, } impl GifBuilder { - pub fn new(version: Version, width: u16, height: u16) -> Self { - Self { - version, - width, - height, - background_color_index: 0, - global_color_table: None, - blocks: vec![] - } - } + pub fn new(version: Version, width: u16, height: u16) -> Self { + Self { + version, + width, + height, + background_color_index: 0, + global_color_table: None, + blocks: vec![], + } + } - pub fn global_color_table(mut self, table: ColorTable) -> Self { - self.global_color_table = Some(table); + pub fn global_color_table(mut self, table: ColorTable) -> Self { + self.global_color_table = Some(table); - self - } + self + } - pub fn background_color_index(mut self, ind: u8) -> Self { - if self.global_color_table.is_none() { - //TODO: Throw error or let it go by, who knows - panic!("Setting background color index with noGCT!"); - } + pub fn background_color_index(mut self, ind: u8) -> Self { + if self.global_color_table.is_none() { + //TODO: Throw error or let it go by, who knows + panic!("Setting background color index with noGCT!"); + } - self.background_color_index = ind; - self - } + self.background_color_index = ind; + self + } - pub fn image(mut self, ib: ImageBuilder) -> Self { - self.blocks.push(Block::IndexedImage(ib.build())); - self - } + pub fn image(mut self, ib: ImageBuilder) -> Self { + self.blocks.push(Block::IndexedImage(ib.build())); + self + } - pub fn extension(mut self, ext: Extension) -> Self { - self.blocks.push(Block::Extension(ext)); - 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, - height: self.height, - packed: 0, // Set later - background_color_index: self.background_color_index, - pixel_aspect_ratio: 0 //TODO: Allow configuring - }; + pub fn build(self) -> Gif { + let mut lsd = ScreenDescriptor { + width: self.width, + height: self.height, + packed: 0, // Set later + background_color_index: self.background_color_index, + pixel_aspect_ratio: 0, //TODO: Allow configuring + }; - if let Some(gct) = &self.global_color_table { - lsd.color_table_present(true); - lsd.color_table_size(gct.len() as u8); - } + if let Some(gct) = &self.global_color_table { + println!("build {}", gct.len()); + lsd.set_color_table_present(true); + lsd.set_color_table_size((gct.len() - 1) as u8); + } - Gif { - header: self.version, - screen_descriptor: lsd, - global_color_table: self.global_color_table, - blocks: self.blocks - } - } -} \ No newline at end of file + Gif { + header: self.version, + screen_descriptor: lsd, + global_color_table: self.global_color_table, + blocks: self.blocks, + } + } +} diff --git a/src/writer/imagebuilder.rs b/src/writer/imagebuilder.rs index f2156ac..d38687e 100644 --- a/src/writer/imagebuilder.rs +++ b/src/writer/imagebuilder.rs @@ -1,71 +1,71 @@ -use crate::block::{ColorTable, IndexedImage, ImageDescriptor}; +use crate::block::{ColorTable, ImageDescriptor, IndexedImage}; pub struct ImageBuilder { - left_offset: u16, - top_offset: u16, - width: u16, - height: u16, - color_table: Option<ColorTable>, - indicies: Vec<u8> + left_offset: u16, + top_offset: u16, + width: u16, + height: u16, + color_table: Option<ColorTable>, + indicies: Vec<u8>, } impl ImageBuilder { - pub fn new(width: u16, height: u16) -> Self { - Self { - left_offset: 0, - top_offset: 0, - width, - height, - color_table: None, - indicies: vec![] - } - } + pub fn new(width: u16, height: u16) -> Self { + Self { + left_offset: 0, + top_offset: 0, + width, + height, + color_table: None, + indicies: vec![], + } + } - pub fn offsets(mut self, left_offset: u16, top_offset: u16) -> Self { - self.left_offset = left_offset; - self.top_offset = top_offset; - self - } + pub fn offsets(mut self, left_offset: u16, top_offset: u16) -> Self { + self.left_offset = left_offset; + self.top_offset = top_offset; + self + } - pub fn left_offset(mut self, offset: u16) -> Self { - self.left_offset = offset; - self - } + pub fn left_offset(mut self, offset: u16) -> Self { + self.left_offset = offset; + self + } - pub fn top_offset(mut self, offset: u16) -> Self { - self.top_offset = offset; - self - } + pub fn top_offset(mut self, offset: u16) -> Self { + self.top_offset = offset; + self + } - pub fn color_table(mut self, table: ColorTable) -> Self { - self.color_table = Some(table); + pub fn color_table(mut self, table: ColorTable) -> Self { + self.color_table = Some(table); - self - } + self + } - pub fn indicies(mut self, vec: Vec<u8>) -> Self { - self.indicies = vec; - self - } + pub fn indicies(mut self, vec: Vec<u8>) -> Self { + self.indicies = vec; + self + } - pub fn build(self) -> IndexedImage { - let mut imgdesc = ImageDescriptor { - left: self.left_offset, - top: self.top_offset, - width: self.width, - height: self.height, - packed: 0 // Set later - }; + pub fn build(self) -> IndexedImage { + let mut imgdesc = ImageDescriptor { + left: self.left_offset, + top: self.top_offset, + width: self.width, + height: self.height, + packed: 0, // Set later + }; - if let Some(lct) = &self.color_table { - imgdesc.color_table_present(true); - imgdesc.color_table_size(lct.packed_len()); - } + if let Some(lct) = &self.color_table { + imgdesc.set_color_table_present(true); + imgdesc.set_color_table_size(lct.packed_len()); + } - IndexedImage { - image_descriptor: imgdesc, - local_color_table: self.color_table, - indicies: self.indicies - } - } + IndexedImage { + image_descriptor: imgdesc, + local_color_table: self.color_table, + indicies: self.indicies, + } + } } |