diff options
author | Genny <gen@nyble.dev> | 2021-10-11 03:36:38 -0500 |
---|---|---|
committer | Genny <gen@nyble.dev> | 2021-10-11 03:36:38 -0500 |
commit | 757eab88d67a425728b87286c763387f52367196 (patch) | |
tree | f1d61edb0a838787f6984accdd92df68a69d567a | |
parent | 9b7bd5696a21496fa0c38a17e69c5c0658acfe73 (diff) | |
download | gifed-757eab88d67a425728b87286c763387f52367196.tar.gz gifed-757eab88d67a425728b87286c763387f52367196.zip |
Run rustfmt
-rw-r--r-- | .rustfmt.toml | 1 | ||||
-rw-r--r-- | benches/lzw_encode.rs | 24 | ||||
-rw-r--r-- | src/block/colortable.rs | 172 | ||||
-rw-r--r-- | src/block/extension/application.rs | 18 | ||||
-rw-r--r-- | src/block/extension/graphiccontrol.rs | 192 | ||||
-rw-r--r-- | src/block/extension/mod.rs | 64 | ||||
-rw-r--r-- | src/block/imagedescriptor.rs | 102 | ||||
-rw-r--r-- | src/block/indexedimage.rs | 90 | ||||
-rw-r--r-- | src/block/mod.rs | 8 | ||||
-rw-r--r-- | src/block/screendescriptor.rs | 118 | ||||
-rw-r--r-- | src/block/version.rs | 28 | ||||
-rw-r--r-- | src/color.rs | 46 | ||||
-rw-r--r-- | src/colorimage.rs | 90 | ||||
-rw-r--r-- | src/gif.rs | 408 | ||||
-rw-r--r-- | src/lib.rs | 32 | ||||
-rw-r--r-- | src/lzw.rs | 282 | ||||
-rw-r--r-- | src/reader/mod.rs | 526 | ||||
-rw-r--r-- | src/writer/gifbuilder.rs | 190 | ||||
-rw-r--r-- | src/writer/imagebuilder.rs | 246 |
19 files changed, 1319 insertions, 1318 deletions
diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/benches/lzw_encode.rs b/benches/lzw_encode.rs index aacf4a3..69ed275 100644 --- a/benches/lzw_encode.rs +++ b/benches/lzw_encode.rs @@ -4,19 +4,19 @@ use rand::{thread_rng, Rng}; use weezl::{encode::Encoder, BitOrder}; pub fn criterion_benchmark(c: &mut Criterion) { - let mut random = [0u8; 2048]; - thread_rng().fill(&mut random[..]); + let mut random = [0u8; 2048]; + thread_rng().fill(&mut random[..]); - c.bench_function("lzw encode 255bytes", |b| { - b.iter(|| LZW::encode(8, black_box(&random))) - }); - c.bench_function("weezl encode 255bytes", |b| { - b.iter(|| { - Encoder::new(BitOrder::Msb, 8) - .encode(black_box(&random)) - .unwrap() - }) - }); + c.bench_function("lzw encode 255bytes", |b| { + b.iter(|| LZW::encode(8, black_box(&random))) + }); + c.bench_function("weezl encode 255bytes", |b| { + b.iter(|| { + Encoder::new(BitOrder::Msb, 8) + .encode(black_box(&random)) + .unwrap() + }) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/src/block/colortable.rs b/src/block/colortable.rs index d8241f7..01fe00b 100644 --- a/src/block/colortable.rs +++ b/src/block/colortable.rs @@ -1,117 +1,117 @@ pub use crate::Color; use crate::EncodingError; use std::{ - convert::{TryFrom, TryInto}, - ops::Deref, + convert::{TryFrom, TryInto}, + ops::Deref, }; #[derive(Clone, Debug)] pub struct ColorTable { - table: Vec<Color>, + table: Vec<Color>, } impl ColorTable { - 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 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); - } - - pub fn get(&self, index: u8) -> Option<Color> { - self.table.get(index as usize).map(|v| v.clone()) - } - - pub fn from_color(&self, color: Color) -> Option<u8> { - for (i, &c) in self.table.iter().enumerate() { - if c == color { - return Some(i as u8); - } - } - None - } + 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 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); + } + + pub fn get(&self, index: u8) -> Option<Color> { + self.table.get(index as usize).map(|v| v.clone()) + } + + pub fn from_color(&self, color: Color) -> Option<u8> { + for (i, &c) in self.table.iter().enumerate() { + if c == color { + return Some(i as u8); + } + } + None + } } 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<&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 = 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]); - } + 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> 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>>(), - }) - } - } + 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>>(), + }) + } + } } impl TryFrom<Vec<Color>> for ColorTable { - type Error = EncodingError; - - fn try_from(value: Vec<Color>) -> Result<Self, Self::Error> { - if value.len() > 256 { - Err(EncodingError::TooManyColors) - } else { - Ok(Self { table: value }) - } - } + type Error = EncodingError; + + fn try_from(value: Vec<Color>) -> Result<Self, Self::Error> { + if value.len() > 256 { + Err(EncodingError::TooManyColors) + } else { + Ok(Self { table: value }) + } + } } impl TryFrom<Vec<(u8, u8, u8)>> for ColorTable { - type Error = EncodingError; - - fn try_from(value: Vec<(u8, u8, u8)>) -> Result<Self, Self::Error> { - if value.len() > 256 { - Err(EncodingError::TooManyColors) - } else { - Ok(Self { - table: value.into_iter().map(|c| c.into()).collect(), - }) - } - } + type Error = EncodingError; + + fn try_from(value: Vec<(u8, u8, u8)>) -> Result<Self, Self::Error> { + if value.len() > 256 { + Err(EncodingError::TooManyColors) + } else { + Ok(Self { + table: value.into_iter().map(|c| c.into()).collect(), + }) + } + } } diff --git a/src/block/extension/application.rs b/src/block/extension/application.rs index b3516d8..9ec1814 100644 --- a/src/block/extension/application.rs +++ b/src/block/extension/application.rs @@ -1,15 +1,15 @@ pub struct Application { - pub(crate) identifier: String, // max len 8 - pub(crate) authentication_code: [u8; 3], - pub(crate) data: Vec<u8>, + 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 identifier(&self) -> &str { + &self.identifier + } - pub fn authentication_code(&self) -> &[u8] { - &self.authentication_code - } + pub fn authentication_code(&self) -> &[u8] { + &self.authentication_code + } } diff --git a/src/block/extension/graphiccontrol.rs b/src/block/extension/graphiccontrol.rs index 830dff4..6bbe4ea 100644 --- a/src/block/extension/graphiccontrol.rs +++ b/src/block/extension/graphiccontrol.rs @@ -2,113 +2,113 @@ use std::{convert::TryInto, fmt}; #[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.set_disposal_method(disposal_method); - ret.user_input(user_input_flag); - ret.transparency(transparency_flag); - - ret - } - - pub fn disposal_method(&self) -> Option<DisposalMethod> { - match self.packed & 0b000_111_00 { - 0b000_000_00 => Some(DisposalMethod::NoAction), - 0b000_100_00 => Some(DisposalMethod::DoNotDispose), - 0b000_010_00 => Some(DisposalMethod::RestoreBackground), - 0b000_110_00 => Some(DisposalMethod::RestorePrevious), - _ => None, - } - } - - pub fn set_disposal_method(&mut self, method: DisposalMethod) { - match method { - DisposalMethod::NoAction => 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 transparency_index(&self) -> u8 { - self.transparency_index - } - - 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 + 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.set_disposal_method(disposal_method); + ret.user_input(user_input_flag); + ret.transparency(transparency_flag); + + ret + } + + pub fn disposal_method(&self) -> Option<DisposalMethod> { + match self.packed & 0b000_111_00 { + 0b000_000_00 => Some(DisposalMethod::NoAction), + 0b000_100_00 => Some(DisposalMethod::DoNotDispose), + 0b000_010_00 => Some(DisposalMethod::RestoreBackground), + 0b000_110_00 => Some(DisposalMethod::RestorePrevious), + _ => None, + } + } + + pub fn set_disposal_method(&mut self, method: DisposalMethod) { + match method { + DisposalMethod::NoAction => 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 transparency_index(&self) -> u8 { + self.transparency_index + } + + 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, - } - } + 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, + } + } } #[derive(Clone, Copy, Debug, PartialEq)] pub enum DisposalMethod { - NoAction, - DoNotDispose, - RestoreBackground, - RestorePrevious, + NoAction, + DoNotDispose, + RestoreBackground, + RestorePrevious, } impl fmt::Display for DisposalMethod { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let st = match self { - DisposalMethod::NoAction => "Dispose as Normal", - DisposalMethod::DoNotDispose => "No Dispose", - DisposalMethod::RestoreBackground => "Restore to background", - DisposalMethod::RestorePrevious => "Restore previous image", - }; - - write!(f, "{}", st) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let st = match self { + DisposalMethod::NoAction => "Dispose as Normal", + DisposalMethod::DoNotDispose => "No Dispose", + DisposalMethod::RestoreBackground => "Restore to background", + DisposalMethod::RestorePrevious => "Restore previous image", + }; + + write!(f, "{}", st) + } } diff --git a/src/block/extension/mod.rs b/src/block/extension/mod.rs index fa712a6..fb5eb20 100644 --- a/src/block/extension/mod.rs +++ b/src/block/extension/mod.rs @@ -6,44 +6,44 @@ pub use graphiccontrol::{DisposalMethod, GraphicControl}; pub use self::application::Application; pub enum Extension { - GraphicControl(GraphicControl), - Looping(u16), - Comment(Vec<u8>), // Plain Text - Application(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()); - } - Extension::Comment(_) => todo!(), - Extension::Application(_) => todo!(), - } + 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() - } + vec.push(0x00); // Zero-length data block indicates end of extension + vec.into_boxed_slice() + } } impl From<GraphicControl> for Extension { - fn from(gce: GraphicControl) -> Self { - Extension::GraphicControl(gce) - } + fn from(gce: GraphicControl) -> Self { + Extension::GraphicControl(gce) + } } diff --git a/src/block/imagedescriptor.rs b/src/block/imagedescriptor.rs index 415bee7..25567b2 100644 --- a/src/block/imagedescriptor.rs +++ b/src/block/imagedescriptor.rs @@ -1,73 +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 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_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; - } + 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 + //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_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) - } + 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![]; + 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.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() - } + 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]; + 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, - } - } + Self { + left, + top, + width, + height, + packed, + } + } } //TODO: Impl to allow changing the packed field easier diff --git a/src/block/indexedimage.rs b/src/block/indexedimage.rs index 0be066f..8ed0319 100644 --- a/src/block/indexedimage.rs +++ b/src/block/indexedimage.rs @@ -4,67 +4,67 @@ 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 left(&self) -> u16 { - self.image_descriptor.left - } + pub fn left(&self) -> u16 { + self.image_descriptor.left + } - pub fn top(&self) -> u16 { - self.image_descriptor.left - } + pub fn top(&self) -> u16 { + self.image_descriptor.left + } - pub fn width(&self) -> u16 { - self.image_descriptor.width - } + pub fn width(&self) -> u16 { + self.image_descriptor.width + } - pub fn height(&self) -> u16 { - self.image_descriptor.height - } + pub fn height(&self) -> u16 { + self.image_descriptor.height + } - pub fn as_boxed_slice(&self, minimum_code_size: u8) -> Box<[u8]> { - let mut out = vec![]; + 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); + 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); + // 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 - }; + lct.packed_len() + } else { + minimum_code_size + 1 + }; - if mcs < 2 { - mcs = 2; // Must be true: 0 <= mcs <= 8 - } + if mcs < 2 { + mcs = 2; // Must be true: 0 <= mcs <= 8 + } - // First write out the MCS - out.push(mcs); + // First write out the MCS + out.push(mcs); - let compressed = LZW::encode(mcs, &self.indicies); + 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); + 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() - } + out.into_boxed_slice() + } } pub struct CompressedImage { - pub image_descriptor: ImageDescriptor, - pub local_color_table: Option<ColorTable>, - pub lzw_minimum_code_size: u8, - pub blocks: Vec<Vec<u8>>, + 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 645c11d..e35224b 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -15,11 +15,11 @@ pub use version::Version; use crate::writer::ImageBuilder; pub enum Block { - IndexedImage(IndexedImage), - Extension(extension::Extension), + IndexedImage(IndexedImage), + Extension(extension::Extension), } enum WriteBlock { - ImageBuilder(ImageBuilder), - Block(Block), + ImageBuilder(ImageBuilder), + Block(Block), } diff --git a/src/block/screendescriptor.rs b/src/block/screendescriptor.rs index 444b44f..dc0257d 100644 --- a/src/block/screendescriptor.rs +++ b/src/block/screendescriptor.rs @@ -1,79 +1,79 @@ 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 new(width: u16, height: u16) -> Self { - Self { - width, - height, - packed: 0, - background_color_index: 0, - pixel_aspect_ratio: 0, - } - } + pub fn new(width: u16, height: u16) -> Self { + Self { + width, + height, + packed: 0, + background_color_index: 0, + pixel_aspect_ratio: 0, + } + } - 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_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; - } + 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 + //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_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) - } + 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); + 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() - } + 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]; + 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, - } - } + Self { + width, + height, + packed, + background_color_index, + pixel_aspect_ratio, + } + } } diff --git a/src/block/version.rs b/src/block/version.rs index c52439c..0171ad4 100644 --- a/src/block/version.rs +++ b/src/block/version.rs @@ -2,24 +2,24 @@ use std::fmt; #[derive(Clone, Copy, Debug, PartialEq)] 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", - } - } + 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"), - } - } + 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 dd96280..e18ce58 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,38 +1,38 @@ #[derive(Copy, Clone, Debug, PartialEq)] 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 } - } + 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], - } - } + fn from(arr: [u8; 3]) -> Self { + Self { + r: arr[0], + g: arr[1], + b: arr[2], + } + } } impl From<(u8, u8, u8)> for Color { - fn from(t: (u8, u8, u8)) -> Self { - Self { - r: t.0, - g: t.1, - b: t.2, - } - } + fn from(t: (u8, u8, u8)) -> Self { + Self { + r: t.0, + g: t.1, + b: t.2, + } + } } impl Into<[u8; 3]> for Color { - fn into(self) -> [u8; 3] { - [self.r, self.g, self.b] - } + fn into(self) -> [u8; 3] { + [self.r, self.g, self.b] + } } diff --git a/src/colorimage.rs b/src/colorimage.rs index 1bdb273..69dac1e 100644 --- a/src/colorimage.rs +++ b/src/colorimage.rs @@ -3,59 +3,59 @@ use std::convert::TryFrom; use crate::{block::ColorTable, gif::Image, reader::DecodingError, Color}; pub struct ColorImage { - width: u16, - height: u16, - data: Vec<Pixel>, + width: u16, + height: u16, + data: Vec<Pixel>, } impl ColorImage { - pub(crate) fn from_indicies( - width: u16, - height: u16, - indicies: &[u8], - table: &ColorTable, - transindex: Option<u8>, - ) -> Result<Self, DecodingError> { - let mut data = vec![Pixel::Transparent; (width * height) as usize]; - - for (image_index, color_index) in indicies.into_iter().enumerate() { - if let Some(trans) = transindex { - if trans == *color_index { - data[image_index] = Pixel::Transparent; - } - } else { - data[image_index] = Pixel::Color( - table - .get(*color_index) - .ok_or(DecodingError::ColorIndexOutOfBounds)?, - ); - } - } - - Ok(ColorImage { - width, - height, - data, - }) - } + pub(crate) fn from_indicies( + width: u16, + height: u16, + indicies: &[u8], + table: &ColorTable, + transindex: Option<u8>, + ) -> Result<Self, DecodingError> { + let mut data = vec![Pixel::Transparent; (width * height) as usize]; + + for (image_index, color_index) in indicies.into_iter().enumerate() { + if let Some(trans) = transindex { + if trans == *color_index { + data[image_index] = Pixel::Transparent; + } + } else { + data[image_index] = Pixel::Color( + table + .get(*color_index) + .ok_or(DecodingError::ColorIndexOutOfBounds)?, + ); + } + } + + Ok(ColorImage { + width, + height, + data, + }) + } } impl<'a> TryFrom<Image<'a>> for ColorImage { - type Error = DecodingError; - - fn try_from(img: Image<'a>) -> Result<Self, Self::Error> { - ColorImage::from_indicies( - img.width, - img.height, - img.indicies, - img.palette, - img.transparent_index, - ) - } + type Error = DecodingError; + + fn try_from(img: Image<'a>) -> Result<Self, Self::Error> { + ColorImage::from_indicies( + img.width, + img.height, + img.indicies, + img.palette, + img.transparent_index, + ) + } } #[derive(Copy, Clone, Debug, PartialEq)] pub enum Pixel { - Color(Color), - Transparent, + Color(Color), + Transparent, } diff --git a/src/gif.rs b/src/gif.rs index 3a56850..da7a1d9 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -1,231 +1,231 @@ use std::{fs::File, io::Write, path::Path}; use crate::{ - block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}, - colorimage, - writer::GifBuilder, - ColorImage, + block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}, + colorimage, + writer::GifBuilder, + ColorImage, }; 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 builder(width: u16, height: u16) -> GifBuilder { - GifBuilder::new(width, height) - } - - 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 - } - - pub fn save<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> { - File::create(path.as_ref())?.write_all(&self.to_vec()) - } - - pub fn images<'a>(&'a self) -> ImageIterator<'a> { - ImageIterator { inner: self } - } + pub fn builder(width: u16, height: u16) -> GifBuilder { + GifBuilder::new(width, height) + } + + 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 + } + + pub fn save<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> { + File::create(path.as_ref())?.write_all(&self.to_vec()) + } + + pub fn images<'a>(&'a self) -> ImageIterator<'a> { + ImageIterator { inner: self } + } } pub struct ImageIterator<'a> { - inner: &'a Gif, + inner: &'a Gif, } impl<'a> Iterator for ImageIterator<'a> { - type Item = Image<'a>; + type Item = Image<'a>; - fn next(&mut self) -> Option<Self::Item> { - todo!() - } + fn next(&mut self) -> Option<Self::Item> { + todo!() + } } pub struct Image<'a> { - pub(crate) width: u16, - pub(crate) height: u16, - left_offset: u16, - top_offset: u16, - pub(crate) palette: &'a ColorTable, - pub(crate) transparent_index: Option<u8>, - pub(crate) indicies: &'a [u8], + pub(crate) width: u16, + pub(crate) height: u16, + left_offset: u16, + top_offset: u16, + pub(crate) palette: &'a ColorTable, + pub(crate) transparent_index: Option<u8>, + pub(crate) indicies: &'a [u8], } impl<'a> Image<'a> { - pub fn dimesnions(&self) -> (u16, u16) { - (self.width, self.height) - } + pub fn dimesnions(&self) -> (u16, u16) { + (self.width, self.height) + } - pub fn position(&self) -> (u16, u16) { - (self.left_offset, self.top_offset) - } + pub fn position(&self) -> (u16, u16) { + (self.left_offset, self.top_offset) + } } #[cfg(test)] pub mod gif { - use std::convert::TryInto; - use std::io::Write; - - use crate::block::extension::DisposalMethod; - 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 = GifBuilder::new(4, 4) - .palette(gct.try_into().unwrap()) - .image( - ImageBuilder::new(4, 4) - .palette(colortable.try_into().unwrap()) - .indicies(&indicies), - ) - .image(ImageBuilder::new(4, 4).indicies(&indicies)); - - let bytes = actual.build().unwrap().to_vec(); - assert_eq!(bytes, 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![ - 71, 73, 70, 56, 57, 97, 4, 0, 4, 0, 128, 0, 0, 1, 2, 3, 253, 254, 255, 33, 249, 4, 8, - 64, 0, 0, 0, 44, 0, 0, 0, 0, 4, 0, 4, 0, 128, 0, 0, 0, 128, 0, 255, 2, 5, 132, 29, 129, - 122, 80, 0, 44, 0, 0, 0, 0, 4, 0, 4, 0, 0, 2, 5, 132, 29, 129, 122, 80, 0, 59, - ]; - - let actual_out = GifBuilder::new(4, 4) - .palette(gct.try_into().unwrap()) - .image( - ImageBuilder::new(4, 4) - .palette(colortable.try_into().unwrap()) - .indicies(&indicies) - .disposal_method(DisposalMethod::RestoreBackground) - .delay(64), - ) - .image(ImageBuilder::new(4, 4).indicies(&indicies)) - .build() - .unwrap() - .to_vec(); - - std::fs::File::create("ah.gif") - .unwrap() - .write_all(&actual_out) - .unwrap(); - std::fs::File::create("ah_hand.gif") - .unwrap() - .write_all(&expected_out) - .unwrap(); - - assert_eq!(actual_out, expected_out); - } + use std::convert::TryInto; + use std::io::Write; + + use crate::block::extension::DisposalMethod; + 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 = GifBuilder::new(4, 4) + .palette(gct.try_into().unwrap()) + .image( + ImageBuilder::new(4, 4) + .palette(colortable.try_into().unwrap()) + .indicies(&indicies), + ) + .image(ImageBuilder::new(4, 4).indicies(&indicies)); + + let bytes = actual.build().unwrap().to_vec(); + assert_eq!(bytes, 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![ + 71, 73, 70, 56, 57, 97, 4, 0, 4, 0, 128, 0, 0, 1, 2, 3, 253, 254, 255, 33, 249, 4, 8, + 64, 0, 0, 0, 44, 0, 0, 0, 0, 4, 0, 4, 0, 128, 0, 0, 0, 128, 0, 255, 2, 5, 132, 29, 129, + 122, 80, 0, 44, 0, 0, 0, 0, 4, 0, 4, 0, 0, 2, 5, 132, 29, 129, 122, 80, 0, 59, + ]; + + let actual_out = GifBuilder::new(4, 4) + .palette(gct.try_into().unwrap()) + .image( + ImageBuilder::new(4, 4) + .palette(colortable.try_into().unwrap()) + .indicies(&indicies) + .disposal_method(DisposalMethod::RestoreBackground) + .delay(64), + ) + .image(ImageBuilder::new(4, 4).indicies(&indicies)) + .build() + .unwrap() + .to_vec(); + + std::fs::File::create("ah.gif") + .unwrap() + .write_all(&actual_out) + .unwrap(); + std::fs::File::create("ah_hand.gif") + .unwrap() + .write_all(&expected_out) + .unwrap(); + + assert_eq!(actual_out, expected_out); + } } diff --git a/src/lib.rs b/src/lib.rs index c7d820d..0a11fdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ 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) + 2usize.pow(packed as u32 + 1) } //TODO: Be sure to check that fields in LSD and Img. Desc. that were reserved @@ -28,24 +28,24 @@ pub(crate) fn packed_to_color_table_length(packed: u8) -> usize { #[derive(Clone, Copy, Debug)] pub enum EncodingError { - TooManyColors, - NoColorTable, - IndicieSizeMismatch { expected: usize, got: usize }, + TooManyColors, + NoColorTable, + IndicieSizeMismatch { expected: usize, got: usize }, } impl fmt::Display for EncodingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TooManyColors => write!(f, "A palette is limited to 256 colors"), - Self::NoColorTable => write!( - f, - "Refusing to set the background color index when no color table is set!" - ), - Self::IndicieSizeMismatch { expected, got } => { - write!(f, "Expected to have {} indicies but got {}", expected, got) - } - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TooManyColors => write!(f, "A palette is limited to 256 colors"), + Self::NoColorTable => write!( + f, + "Refusing to set the background color index when no color table is set!" + ), + Self::IndicieSizeMismatch { expected, got } => { + write!(f, "Expected to have {} indicies but got {}", expected, got) + } + } + } } impl Error for EncodingError {} diff --git a/src/lzw.rs b/src/lzw.rs index 053c5c4..dce6a5d 100644 --- a/src/lzw.rs +++ b/src/lzw.rs @@ -2,172 +2,172 @@ use std::collections::HashMap; pub struct LZW {} impl LZW { - 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() - } + 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); + #[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); - } + 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::*; + 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); + #[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(); + let bsvec = bs.vec(); - for byte in &bsvec { - print!("{:b} ", byte); - } - println!(""); + for byte in &bsvec { + print!("{:b} ", byte); + } + println!(""); - assert_eq!(bsvec, vec![0b1001_1111, 0b0000_0001]); - } + 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); + #[test] + fn long_push() { + let mut bs = BitStream::new(); + bs.push_bits(1, 1); + bs.push_bits(12, 2049); - let bsvec = bs.vec(); + let bsvec = bs.vec(); - for byte in &bsvec { - print!("{:b} ", byte); - } - println!(""); + for byte in &bsvec { + print!("{:b} ", byte); + } + println!(""); - assert_eq!(bsvec, vec![0b0000_0011, 0b0001_0000]); - } + assert_eq!(bsvec, vec![0b0000_0011, 0b0001_0000]); + } } diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 1b52874..7cf1fe1 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -1,290 +1,290 @@ use std::{ - borrow::Cow, - convert::{TryFrom, TryInto}, - error::Error, - fmt, - fs::File, - io::{BufRead, BufReader, Read}, - path::Path, + borrow::Cow, + convert::{TryFrom, TryInto}, + error::Error, + fmt, + fs::File, + io::{BufRead, BufReader, Read}, + path::Path, }; use crate::{ - block::{ - extension::{Application, Extension, GraphicControl}, - Block, ColorTable, CompressedImage, ImageDescriptor, IndexedImage, ScreenDescriptor, - Version, - }, - color, Gif, + block::{ + extension::{Application, Extension, GraphicControl}, + Block, ColorTable, CompressedImage, ImageDescriptor, IndexedImage, ScreenDescriptor, + Version, + }, + color, Gif, }; pub struct GifReader {} impl GifReader { - pub fn file<P: AsRef<Path>>(path: P) -> Result<Gif, DecodingError> { - let mut file = File::open(path)?; - let mut reader = SmartReader { - inner: vec![], - position: 0, - }; - file.read_to_end(&mut reader.inner)?; - - 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 Ok(gif), - } - } - } - - fn read_required(reader: &mut SmartReader) -> Result<Gif, DecodingError> { - let version = match reader.take_lossy_utf8(6).as_deref() { - Some("GIF87a") => Version::Gif87a, - Some("GIF89a") => Version::Gif89a, - _ => return Err(DecodingError::UnknownVersionString), - }; - - let mut lsd_buffer: [u8; 7] = [0; 7]; - reader - .read_exact(&mut lsd_buffer) - .ok_or(DecodingError::UnexpectedEof)?; - - let lsd = ScreenDescriptor::from(lsd_buffer); - - Ok(Gif { - header: version, - screen_descriptor: lsd, - global_color_table: None, - blocks: vec![], - }) - } - - fn read_color_table( - reader: &mut SmartReader, - size: usize, - ) -> Result<ColorTable, DecodingError> { - let buffer = reader - .take(size as usize) - .ok_or(DecodingError::UnexpectedEof)?; - - // We get the size from the screen descriptor. This should never return Err - Ok(ColorTable::try_from(&buffer[..]).unwrap()) - } - - fn read_block(reader: &mut SmartReader) -> Result<Option<Block>, DecodingError> { - let block_id = reader.u8().ok_or(DecodingError::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_extension(reader: &mut SmartReader) -> Result<Block, DecodingError> { - let 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) - .ok_or(DecodingError::UnexpectedEof)?; - reader.skip(1); // Skip block terminator - - Ok(Block::Extension(Extension::GraphicControl( - GraphicControl::from(data), - ))) - } - 0xFE => Ok(Block::Extension(Extension::Comment( - 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 = 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(); - - Ok(Block::Extension(Extension::Application(Application { - identifier, - authentication_code, - data, - }))) - } - _ => panic!("Unknown Extension Identifier!"), - } - } - - fn read_image(mut reader: &mut SmartReader) -> Result<Block, DecodingError> { - let mut buffer = [0u8; 9]; - reader - .read_exact(&mut buffer) - .ok_or(DecodingError::UnexpectedEof)?; - 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().ok_or(DecodingError::UnexpectedEof)?; - - let compressed_data = reader.take_and_collapse_subblocks(); - println!("c{}", compressed_data.len()); - - let mut decompress = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, lzw_csize); - //TODO: remove unwrap - let mut decompressed_data = decompress.decode(&compressed_data).unwrap(); - - Ok(Block::IndexedImage(IndexedImage { - image_descriptor: descriptor, - local_color_table: color_table, - indicies: decompressed_data, - })) - } + pub fn file<P: AsRef<Path>>(path: P) -> Result<Gif, DecodingError> { + let mut file = File::open(path)?; + let mut reader = SmartReader { + inner: vec![], + position: 0, + }; + file.read_to_end(&mut reader.inner)?; + + 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 Ok(gif), + } + } + } + + fn read_required(reader: &mut SmartReader) -> Result<Gif, DecodingError> { + let version = match reader.take_lossy_utf8(6).as_deref() { + Some("GIF87a") => Version::Gif87a, + Some("GIF89a") => Version::Gif89a, + _ => return Err(DecodingError::UnknownVersionString), + }; + + let mut lsd_buffer: [u8; 7] = [0; 7]; + reader + .read_exact(&mut lsd_buffer) + .ok_or(DecodingError::UnexpectedEof)?; + + let lsd = ScreenDescriptor::from(lsd_buffer); + + Ok(Gif { + header: version, + screen_descriptor: lsd, + global_color_table: None, + blocks: vec![], + }) + } + + fn read_color_table( + reader: &mut SmartReader, + size: usize, + ) -> Result<ColorTable, DecodingError> { + let buffer = reader + .take(size as usize) + .ok_or(DecodingError::UnexpectedEof)?; + + // We get the size from the screen descriptor. This should never return Err + Ok(ColorTable::try_from(&buffer[..]).unwrap()) + } + + fn read_block(reader: &mut SmartReader) -> Result<Option<Block>, DecodingError> { + let block_id = reader.u8().ok_or(DecodingError::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_extension(reader: &mut SmartReader) -> Result<Block, DecodingError> { + let 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) + .ok_or(DecodingError::UnexpectedEof)?; + reader.skip(1); // Skip block terminator + + Ok(Block::Extension(Extension::GraphicControl( + GraphicControl::from(data), + ))) + } + 0xFE => Ok(Block::Extension(Extension::Comment( + 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 = 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(); + + Ok(Block::Extension(Extension::Application(Application { + identifier, + authentication_code, + data, + }))) + } + _ => panic!("Unknown Extension Identifier!"), + } + } + + fn read_image(mut reader: &mut SmartReader) -> Result<Block, DecodingError> { + let mut buffer = [0u8; 9]; + reader + .read_exact(&mut buffer) + .ok_or(DecodingError::UnexpectedEof)?; + 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().ok_or(DecodingError::UnexpectedEof)?; + + let compressed_data = reader.take_and_collapse_subblocks(); + println!("c{}", compressed_data.len()); + + let mut decompress = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, lzw_csize); + //TODO: remove unwrap + let mut decompressed_data = decompress.decode(&compressed_data).unwrap(); + + Ok(Block::IndexedImage(IndexedImage { + image_descriptor: descriptor, + local_color_table: color_table, + indicies: decompressed_data, + })) + } } #[derive(Debug)] pub enum DecodingError { - IoError(std::io::Error), - UnknownVersionString, - UnexpectedEof, - ColorIndexOutOfBounds, + IoError(std::io::Error), + UnknownVersionString, + UnexpectedEof, + ColorIndexOutOfBounds, } impl Error for DecodingError {} impl fmt::Display for DecodingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DecodingError::IoError(error) => write!(f, "{}", error), - DecodingError::UnknownVersionString => { - write!(f, "File did not start with a valid header") - } - DecodingError::UnexpectedEof => { - write!(f, "Found the end of the data at a weird spot") - } - DecodingError::ColorIndexOutOfBounds => { - write!( - f, - "The image contained an index not found in the color table" - ) - } - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DecodingError::IoError(error) => write!(f, "{}", error), + DecodingError::UnknownVersionString => { + write!(f, "File did not start with a valid header") + } + DecodingError::UnexpectedEof => { + write!(f, "Found the end of the data at a weird spot") + } + DecodingError::ColorIndexOutOfBounds => { + write!( + f, + "The image contained an index not found in the color table" + ) + } + } + } } impl From<std::io::Error> for DecodingError { - fn from(ioerror: std::io::Error) -> Self { - DecodingError::IoError(ioerror) - } + fn from(ioerror: std::io::Error) -> Self { + DecodingError::IoError(ioerror) + } } struct SmartReader { - inner: Vec<u8>, - position: usize, + 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 - } + 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 1466f76..57a62e3 100644 --- a/src/writer/gifbuilder.rs +++ b/src/writer/gifbuilder.rs @@ -5,102 +5,102 @@ use crate::writer::ImageBuilder; use crate::{EncodingError, Gif}; pub struct GifBuilder { - version: Version, - width: u16, - height: u16, - background_color_index: u8, - global_color_table: Option<ColorTable>, - blocks: Vec<Block>, - error: Option<EncodingError>, + version: Version, + width: u16, + height: u16, + background_color_index: u8, + global_color_table: Option<ColorTable>, + blocks: Vec<Block>, + error: Option<EncodingError>, } impl GifBuilder { - pub fn new(width: u16, height: u16) -> Self { - Self { - version: Version::Gif87a, - width, - height, - background_color_index: 0, - global_color_table: None, - blocks: vec![], - error: None, - } - } - - pub fn palette(mut self, palette: ColorTable) -> Self { - self.global_color_table = Some(palette); - self - } - - pub fn background_index(mut self, ind: u8) -> Self { - if self.error.is_some() { - return self; - } - - if self.global_color_table.is_none() { - self.error = Some(EncodingError::NoColorTable); - } else { - self.background_color_index = ind; - } - self - } - - pub fn image(mut self, ib: ImageBuilder) -> Self { - if self.error.is_some() { - return self; - } - - if ib.required_version() == Version::Gif89a { - self.version = Version::Gif89a; - } - - if let Some(gce) = ib.get_graphic_control() { - self.blocks.push(Block::Extension(gce.into())); - } - - match ib.build() { - Ok(image) => self.blocks.push(Block::IndexedImage(image)), - Err(e) => self.error = Some(e), - } - - self - } - - /*pub fn extension(mut self, ext: Extension) -> Self { - self.blocks.push(Block::Extension(ext)); - self - }*/ - - pub fn repeat(mut self, count: u16) -> Self { - self.blocks - .push(Block::Extension(Extension::Looping(count))); - self - } - - pub fn build(self) -> Result<Gif, EncodingError> { - if let Some(error) = self.error { - return Err(error); - } - - 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 { - println!("build {}", gct.len()); - lsd.set_color_table_present(true); - lsd.set_color_table_size((gct.len() - 1) as u8); - } - - Ok(Gif { - header: self.version, - screen_descriptor: lsd, - global_color_table: self.global_color_table, - blocks: self.blocks, - }) - } + pub fn new(width: u16, height: u16) -> Self { + Self { + version: Version::Gif87a, + width, + height, + background_color_index: 0, + global_color_table: None, + blocks: vec![], + error: None, + } + } + + pub fn palette(mut self, palette: ColorTable) -> Self { + self.global_color_table = Some(palette); + self + } + + pub fn background_index(mut self, ind: u8) -> Self { + if self.error.is_some() { + return self; + } + + if self.global_color_table.is_none() { + self.error = Some(EncodingError::NoColorTable); + } else { + self.background_color_index = ind; + } + self + } + + pub fn image(mut self, ib: ImageBuilder) -> Self { + if self.error.is_some() { + return self; + } + + if ib.required_version() == Version::Gif89a { + self.version = Version::Gif89a; + } + + if let Some(gce) = ib.get_graphic_control() { + self.blocks.push(Block::Extension(gce.into())); + } + + match ib.build() { + Ok(image) => self.blocks.push(Block::IndexedImage(image)), + Err(e) => self.error = Some(e), + } + + self + } + + /*pub fn extension(mut self, ext: Extension) -> Self { + self.blocks.push(Block::Extension(ext)); + self + }*/ + + pub fn repeat(mut self, count: u16) -> Self { + self.blocks + .push(Block::Extension(Extension::Looping(count))); + self + } + + pub fn build(self) -> Result<Gif, EncodingError> { + if let Some(error) = self.error { + return Err(error); + } + + 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 { + println!("build {}", gct.len()); + lsd.set_color_table_present(true); + lsd.set_color_table_size((gct.len() - 1) as u8); + } + + Ok(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 42c94f8..f5c9e2b 100644 --- a/src/writer/imagebuilder.rs +++ b/src/writer/imagebuilder.rs @@ -1,133 +1,133 @@ use crate::{ - block::{ - extension::{DisposalMethod, GraphicControl}, - ColorTable, ImageDescriptor, IndexedImage, Version, - }, - EncodingError, + block::{ + extension::{DisposalMethod, GraphicControl}, + ColorTable, ImageDescriptor, IndexedImage, Version, + }, + EncodingError, }; pub struct ImageBuilder { - left_offset: u16, - top_offset: u16, - width: u16, - height: u16, - color_table: Option<ColorTable>, + left_offset: u16, + top_offset: u16, + width: u16, + height: u16, + color_table: Option<ColorTable>, - delay: u16, - disposal_method: DisposalMethod, - transparent_index: Option<u8>, + delay: u16, + disposal_method: DisposalMethod, + transparent_index: Option<u8>, - indicies: Vec<u8>, + 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, - delay: 0, - disposal_method: DisposalMethod::NoAction, - transparent_index: None, - indicies: vec![], - } - } - - pub fn offset(mut self, left: u16, top: u16) -> Self { - self.left_offset = left; - self.top_offset = top; - self - } - - pub fn palette(mut self, table: ColorTable) -> Self { - self.color_table = Some(table); - self - } - - /// Time to wait, in hundreths of a second, before this image is drawn - pub fn delay(mut self, hundreths: u16) -> Self { - self.delay = hundreths; - self - } - - pub fn disposal_method(mut self, method: DisposalMethod) -> Self { - self.disposal_method = method; - self - } - - pub fn transparent_index(mut self, index: Option<u8>) -> Self { - self.transparent_index = index; - self - } - - pub fn required_version(&self) -> Version { - if self.delay > 0 - || self.disposal_method != DisposalMethod::NoAction - || self.transparent_index.is_some() - { - Version::Gif89a - } else { - Version::Gif87a - } - } - - pub fn get_graphic_control(&self) -> Option<GraphicControl> { - if self.required_version() == Version::Gif89a { - if let Some(transindex) = self.transparent_index { - Some(GraphicControl::new( - self.disposal_method, - false, - true, - self.delay, - transindex, - )) - } else { - Some(GraphicControl::new( - self.disposal_method, - false, - false, - self.delay, - 0, - )) - } - } else { - None - } - } - - pub fn indicies(mut self, indicies: &[u8]) -> Self { - self.indicies = indicies.to_vec(); - self - } - - pub fn build(self) -> Result<IndexedImage, EncodingError> { - let expected_len = self.width as usize * self.height as usize; - if self.indicies.len() != expected_len { - return Err(EncodingError::IndicieSizeMismatch { - expected: expected_len, - got: self.indicies.len(), - }); - } - - 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.set_color_table_present(true); - imgdesc.set_color_table_size(lct.packed_len()); - } - - Ok(IndexedImage { - image_descriptor: imgdesc, - local_color_table: self.color_table, - indicies: self.indicies, - }) - } + pub fn new(width: u16, height: u16) -> Self { + Self { + left_offset: 0, + top_offset: 0, + width, + height, + color_table: None, + delay: 0, + disposal_method: DisposalMethod::NoAction, + transparent_index: None, + indicies: vec![], + } + } + + pub fn offset(mut self, left: u16, top: u16) -> Self { + self.left_offset = left; + self.top_offset = top; + self + } + + pub fn palette(mut self, table: ColorTable) -> Self { + self.color_table = Some(table); + self + } + + /// Time to wait, in hundreths of a second, before this image is drawn + pub fn delay(mut self, hundreths: u16) -> Self { + self.delay = hundreths; + self + } + + pub fn disposal_method(mut self, method: DisposalMethod) -> Self { + self.disposal_method = method; + self + } + + pub fn transparent_index(mut self, index: Option<u8>) -> Self { + self.transparent_index = index; + self + } + + pub fn required_version(&self) -> Version { + if self.delay > 0 + || self.disposal_method != DisposalMethod::NoAction + || self.transparent_index.is_some() + { + Version::Gif89a + } else { + Version::Gif87a + } + } + + pub fn get_graphic_control(&self) -> Option<GraphicControl> { + if self.required_version() == Version::Gif89a { + if let Some(transindex) = self.transparent_index { + Some(GraphicControl::new( + self.disposal_method, + false, + true, + self.delay, + transindex, + )) + } else { + Some(GraphicControl::new( + self.disposal_method, + false, + false, + self.delay, + 0, + )) + } + } else { + None + } + } + + pub fn indicies(mut self, indicies: &[u8]) -> Self { + self.indicies = indicies.to_vec(); + self + } + + pub fn build(self) -> Result<IndexedImage, EncodingError> { + let expected_len = self.width as usize * self.height as usize; + if self.indicies.len() != expected_len { + return Err(EncodingError::IndicieSizeMismatch { + expected: expected_len, + got: self.indicies.len(), + }); + } + + 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.set_color_table_present(true); + imgdesc.set_color_table_size(lct.packed_len()); + } + + Ok(IndexedImage { + image_descriptor: imgdesc, + local_color_table: self.color_table, + indicies: self.indicies, + }) + } } |