diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/block/colortable.rs | 117 | ||||
-rw-r--r-- | src/block/extension/application.rs | 15 | ||||
-rw-r--r-- | src/block/extension/graphiccontrol.rs | 116 | ||||
-rw-r--r-- | src/block/extension/mod.rs | 49 | ||||
-rw-r--r-- | src/block/imagedescriptor.rs | 73 | ||||
-rw-r--r-- | src/block/indexedimage.rs | 70 | ||||
-rw-r--r-- | src/block/mod.rs | 25 | ||||
-rw-r--r-- | src/block/screendescriptor.rs | 79 | ||||
-rw-r--r-- | src/block/version.rs | 25 | ||||
-rw-r--r-- | src/color.rs | 38 | ||||
-rw-r--r-- | src/colorimage.rs | 61 | ||||
-rw-r--r-- | src/gif.rs | 290 | ||||
-rw-r--r-- | src/lib.rs | 51 | ||||
-rw-r--r-- | src/lzw.rs | 173 | ||||
-rw-r--r-- | src/reader/mod.rs | 274 | ||||
-rw-r--r-- | src/writer/gifbuilder.rs | 106 | ||||
-rw-r--r-- | src/writer/imagebuilder.rs | 133 | ||||
-rw-r--r-- | src/writer/mod.rs | 5 |
18 files changed, 0 insertions, 1700 deletions
diff --git a/src/block/colortable.rs b/src/block/colortable.rs deleted file mode 100644 index 01fe00b..0000000 --- a/src/block/colortable.rs +++ /dev/null @@ -1,117 +0,0 @@ -pub use crate::Color; -use crate::EncodingError; -use std::{ - convert::{TryFrom, TryInto}, - ops::Deref, -}; - -#[derive(Clone, Debug)] -pub struct ColorTable { - 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 - } -} - -impl Deref for ColorTable { - type Target = [Color]; - - fn deref(&self) -> &Self::Target { - &self.table - } -} - -impl From<&ColorTable> for Box<[u8]> { - fn from(table: &ColorTable) -> Self { - let mut vec = vec![]; - - 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]); - } - - 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>>(), - }) - } - } -} - -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 }) - } - } -} - -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(), - }) - } - } -} diff --git a/src/block/extension/application.rs b/src/block/extension/application.rs deleted file mode 100644 index 9ec1814..0000000 --- a/src/block/extension/application.rs +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index b595554..0000000 --- a/src/block/extension/graphiccontrol.rs +++ /dev/null @@ -1,116 +0,0 @@ -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, -} - -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 - } - - pub fn is_transparent(&self) -> bool { - self.packed & 0b000_000_0_1 > 0 - } -} - -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, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum DisposalMethod { - 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) - } -} diff --git a/src/block/extension/mod.rs b/src/block/extension/mod.rs deleted file mode 100644 index fb5eb20..0000000 --- a/src/block/extension/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -mod application; -mod graphiccontrol; - -pub use graphiccontrol::{DisposalMethod, GraphicControl}; - -pub use self::application::Application; - -pub enum Extension { - 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 - - 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() - } -} - -impl From<GraphicControl> for Extension { - fn from(gce: GraphicControl) -> Self { - Extension::GraphicControl(gce) - } -} diff --git a/src/block/imagedescriptor.rs b/src/block/imagedescriptor.rs deleted file mode 100644 index 25567b2..0000000 --- a/src/block/imagedescriptor.rs +++ /dev/null @@ -1,73 +0,0 @@ -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, -} - -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_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() - } -} - -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 diff --git a/src/block/indexedimage.rs b/src/block/indexedimage.rs deleted file mode 100644 index 8ed0319..0000000 --- a/src/block/indexedimage.rs +++ /dev/null @@ -1,70 +0,0 @@ -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>, -} - -impl IndexedImage { - pub fn left(&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 height(&self) -> u16 { - self.image_descriptor.height - } - - 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 CompressedImage { - 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 deleted file mode 100644 index e35224b..0000000 --- a/src/block/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod colortable; -pub mod extension; -mod imagedescriptor; -mod indexedimage; -mod screendescriptor; -mod version; - -pub use colortable::ColorTable; -pub use imagedescriptor::ImageDescriptor; -pub use indexedimage::CompressedImage; -pub use indexedimage::IndexedImage; -pub use screendescriptor::ScreenDescriptor; -pub use version::Version; - -use crate::writer::ImageBuilder; - -pub enum Block { - IndexedImage(IndexedImage), - Extension(extension::Extension), -} - -enum WriteBlock { - ImageBuilder(ImageBuilder), - Block(Block), -} diff --git a/src/block/screendescriptor.rs b/src/block/screendescriptor.rs deleted file mode 100644 index dc0257d..0000000 --- a/src/block/screendescriptor.rs +++ /dev/null @@ -1,79 +0,0 @@ -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, -} - -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 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() - } -} - -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 deleted file mode 100644 index 0171ad4..0000000 --- a/src/block/version.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::fmt; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Version { - Gif87a, - Gif89a, -} - -impl From<&Version> for &[u8] { - 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 deleted file mode 100644 index e18ce58..0000000 --- a/src/color.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Color { - 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 } - } -} - -impl From<[u8; 3]> for Color { - 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, - } - } -} - -impl Into<[u8; 3]> for Color { - fn into(self) -> [u8; 3] { - [self.r, self.g, self.b] - } -} diff --git a/src/colorimage.rs b/src/colorimage.rs deleted file mode 100644 index 69dac1e..0000000 --- a/src/colorimage.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::convert::TryFrom; - -use crate::{block::ColorTable, gif::Image, reader::DecodingError, Color}; - -pub struct ColorImage { - 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, - }) - } -} - -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, - ) - } -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum Pixel { - Color(Color), - Transparent, -} diff --git a/src/gif.rs b/src/gif.rs deleted file mode 100644 index de84764..0000000 --- a/src/gif.rs +++ /dev/null @@ -1,290 +0,0 @@ -use std::{fs::File, io::Write, path::Path}; - -use crate::{ - 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 // -} - -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 { - gif: self, - veciter: self.blocks.iter(), - } - } -} - -pub struct ImageIterator<'a> { - gif: &'a Gif, - veciter: std::slice::Iter<'a, Block>, -} - -impl<'a> Iterator for ImageIterator<'a> { - type Item = Image<'a>; - - fn next(&mut self) -> Option<Self::Item> { - let mut transparent = None; - - let img = loop { - match self.veciter.next() { - Some(block) => match block { - Block::IndexedImage(img) => break img, - Block::Extension(Extension::GraphicControl(gce)) => { - if gce.is_transparent() { - transparent = Some(gce.transparency_index()); - } else { - transparent = None; - } - } - _ => (), - }, - None => return None, - } - }; - - if img.image_descriptor.color_table_present() { - Some(Image { - width: img.image_descriptor.width, - height: img.image_descriptor.height, - left_offset: img.image_descriptor.left, - top_offset: img.image_descriptor.top, - palette: &img.local_color_table.as_ref().unwrap(), - transparent_index: transparent, - indicies: &img.indicies, - }) - } else { - Some(Image { - width: img.image_descriptor.width, - height: img.image_descriptor.height, - left_offset: img.image_descriptor.left, - top_offset: img.image_descriptor.top, - palette: self.gif.global_color_table.as_ref().unwrap(), - transparent_index: transparent, - indicies: &img.indicies, - }) - } - } -} - -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], -} - -impl<'a> Image<'a> { - pub fn width(&self) -> u16 { - self.width - } - - pub fn height(&self) -> u16 { - self.height - } - - pub fn position(&self) -> (u16, u16) { - (self.left_offset, self.top_offset) - } - - pub fn palette(&self) -> &ColorTable { - self.palette - } - - pub fn transparent_index(&self) -> Option<u8> { - self.transparent_index - } - - pub fn indicies(&self) -> &[u8] { - self.indicies - } -} - -#[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); - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 0a11fdc..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -mod color; -mod colorimage; -mod gif; -mod lzw; - -pub mod block; -pub mod reader; -pub mod writer; - -use core::fmt; -use std::error::Error; - -pub use color::Color; -pub use colorimage::ColorImage; -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 - -#[derive(Clone, Copy, Debug)] -pub enum EncodingError { - 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) - } - } - } -} - -impl Error for EncodingError {} diff --git a/src/lzw.rs b/src/lzw.rs deleted file mode 100644 index dce6a5d..0000000 --- a/src/lzw.rs +++ /dev/null @@ -1,173 +0,0 @@ -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() - } -} - -#[cfg(test)] -mod lzw_test { - 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]; - - let lzout = LZW::encode(2, &indicies); - - 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, -} - -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 - } -} - -#[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]); - } -} diff --git a/src/reader/mod.rs b/src/reader/mod.rs deleted file mode 100644 index 41494df..0000000 --- a/src/reader/mod.rs +++ /dev/null @@ -1,274 +0,0 @@ -use std::{ - 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, -}; - -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) => 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(); - - 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, -} - -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" - ) - } - } - } -} - -impl From<std::io::Error> for DecodingError { - fn from(ioerror: std::io::Error) -> Self { - DecodingError::IoError(ioerror) - } -} - -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 deleted file mode 100644 index 57a62e3..0000000 --- a/src/writer/gifbuilder.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::convert::TryInto; - -use crate::block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}; -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>, -} - -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, - }) - } -} diff --git a/src/writer/imagebuilder.rs b/src/writer/imagebuilder.rs deleted file mode 100644 index f5c9e2b..0000000 --- a/src/writer/imagebuilder.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::{ - 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>, - - delay: u16, - disposal_method: DisposalMethod, - transparent_index: Option<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, - }) - } -} diff --git a/src/writer/mod.rs b/src/writer/mod.rs deleted file mode 100644 index 88311fc..0000000 --- a/src/writer/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod gifbuilder; -mod imagebuilder; - -pub use gifbuilder::GifBuilder; -pub use imagebuilder::ImageBuilder; |