diff options
author | Genny <gen@nyble.dev> | 2020-11-06 23:33:01 -0600 |
---|---|---|
committer | Genny <gen@nyble.dev> | 2020-11-06 23:33:01 -0600 |
commit | c54872f011c135bbbc963f4a2b6b1d8ee4fa92bc (patch) | |
tree | 6ee047a5929fbd92a664c041b0596b53f58d59aa | |
parent | 85b5d7de253f5f91dbca4047196e005856fd52de (diff) | |
download | gifed-c54872f011c135bbbc963f4a2b6b1d8ee4fa92bc.tar.gz gifed-c54872f011c135bbbc963f4a2b6b1d8ee4fa92bc.zip |
Add gif components
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | benches/lzw_encode.rs | 13 | ||||
-rw-r--r-- | src/components/color.rs (renamed from src/common/mod.rs) | 11 | ||||
-rw-r--r-- | src/components/colortable.rs | 67 | ||||
-rw-r--r-- | src/components/gif.rs | 82 | ||||
-rw-r--r-- | src/components/image.rs | 47 | ||||
-rw-r--r-- | src/components/imagedescriptor.rs | 45 | ||||
-rw-r--r-- | src/components/logicalscreendescriptor.rs | 40 | ||||
-rw-r--r-- | src/components/mod.rs | 15 | ||||
-rw-r--r-- | src/components/version.rs | 13 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/lzw.rs (renamed from src/writer/lzw.rs) | 0 | ||||
-rw-r--r-- | src/writer/gifbuilder.rs | 208 | ||||
-rw-r--r-- | src/writer/imagebuilder.rs | 165 | ||||
-rw-r--r-- | src/writer/mod.rs | 4 | ||||
-rw-r--r-- | src/writer/outvec.rs | 64 |
17 files changed, 382 insertions, 417 deletions
diff --git a/Cargo.toml b/Cargo.toml index 2121b44..f4ffb22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,12 @@ license = "CC0-1.0" description = "Gif encoding and decoding with fine control" repository = "https://github.com/genuinebyte/gifed" +[dev-dependencies] +criterion = "0.3" +rand = "0.7.3" + [dependencies] + +[[bench]] +name = "lzw_encode" +harness = false \ No newline at end of file diff --git a/README.md b/README.md index e6bb540..d48b26e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ always will be, one of the main goals of this crate. - [ ] Automatically select the lowest version possible when writing - [ ] Read GIF87a - [ ] Read GIF89a -- [ ] Feature to allow using [weezl][weezl-crates] for LZW compression instead of the built-in -- [ ] Well written and easy to understand docs! (Can never truly be finished, but "good enough") +- [ ] Feature to allow using the [weezl][weezl-crates] crate for LZW compression instead of the built-in +- [ ] Feature to allow using the [rgb][rgb-crates] crate for the color type. +- [ ] Well written and easy to understand docs! `bitvec` quality, but who can match that? -[weezl-crates]: https://crates.io/crates/weezl \ No newline at end of file +[weezl-crates]: https://crates.io/crates/weezl +[rgb-crates]: https://crates.io/crates/rgb \ No newline at end of file diff --git a/benches/lzw_encode.rs b/benches/lzw_encode.rs new file mode 100644 index 0000000..33440b2 --- /dev/null +++ b/benches/lzw_encode.rs @@ -0,0 +1,13 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::{thread_rng, Rng}; +use gifed::LZW; + +pub fn criterion_benchmark(c: &mut Criterion) { + let mut random = [0u8; 255]; + thread_rng().fill(&mut random[..]); + + c.bench_function("lzw encode 255bytes", |b| b.iter(|| LZW::encode(8, black_box(&random)))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); \ No newline at end of file diff --git a/src/common/mod.rs b/src/components/color.rs index 1080e29..dc134ef 100644 --- a/src/common/mod.rs +++ b/src/components/color.rs @@ -1,8 +1,3 @@ -pub enum Version { - Gif87a, - Gif89a -} - pub struct Color { pub r: u8, pub g: u8, @@ -11,10 +6,6 @@ pub struct Color { impl Color { pub fn new(r: u8, g: u8, b: u8) -> Self { - Self { - r, - g, - b - } + Self { r, g, b } } } \ No newline at end of file diff --git a/src/components/colortable.rs b/src/components/colortable.rs new file mode 100644 index 0000000..f756169 --- /dev/null +++ b/src/components/colortable.rs @@ -0,0 +1,67 @@ +use std::ops::Deref; +pub use super::Color; + +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); + } +} + +impl Deref for ColorTable { + type Target = [Color]; + + fn deref(&self) -> &Self::Target { + &self.table + } +} + +impl From<Vec<Color>> for ColorTable { + fn from(table: Vec<Color>) -> Self{ + ColorTable { + 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 = 2u8.pow(table.packed_len() as u32 + 1); + let padding = (packed_len as usize - table.len()) * 3; + if padding > 0 { + vec.extend_from_slice(&vec![0; padding]); + } + + vec.into_boxed_slice() + } +} + +//TODO: TryFrom Vec<u8> (must be multiple of 3 len) and From Vec<Color> \ No newline at end of file diff --git a/src/components/gif.rs b/src/components/gif.rs new file mode 100644 index 0000000..2709fe8 --- /dev/null +++ b/src/components/gif.rs @@ -0,0 +1,82 @@ +use super::{ColorTable, Image, LogicalScreenDescriptor, Version}; + +pub struct Gif { + pub header: Version, + pub logical_screen_descriptor: LogicalScreenDescriptor, + pub global_color_table: Option<ColorTable>, + pub images: Vec<Image> + // Trailer at the end of this struct is 0x3B // +} + +impl Gif { + pub fn to_vec(&self) -> Vec<u8> { + let mut out = vec![]; + + out.extend_from_slice((&self.header).into()); + + let mut boxed: Box<[u8]> = (&self.logical_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 image in self.images.iter() { + boxed = image.as_boxed_slice(mcs); + out.extend_from_slice(&*boxed); + } + + // Write Trailer + out.push(0x3B); + + out + } +} + +#[cfg(test)] +pub mod gif { + use crate::Color; + use crate::writer::{GifBuilder, ImageBuilder}; + use super::*; + + #[test] + fn to_vec() { + let gct = vec![ + Color::new(1, 2, 3), Color::new(253, 254, 255) + ]; + let colortable = vec![ + Color::new(0, 0, 0), Color::new(128, 0, 255) + ]; + let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; + + let expected_out = vec![ + 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, // Version - GIF87a + 0x04, 0x00, 0x04, 0x00, 0b1000_0000, 0x00, 0x00, // Logical Screen Descriptor + 1, 2, 3, 253, 254, 255, // Global Color Table + 0x2C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0b1000_0000, // Image Descriptor 1 + 0, 0, 0, 128, 0, 255, // Color Table + 0x02, 0x05, 0x84, 0x1D, 0x81, 0x7A, 0x50, 0x00, // Image Data 1 + 0x2C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0b0000_0000, // Image Descriptor 2 + 0x02, 0x05, 0x84, 0x1D, 0x81, 0x7A, 0x50, 0x00, // Image Data 2 + 0x3B // Trailer + ]; + + let actual_out = GifBuilder::new(Version::Gif87a, 4, 4) + .global_color_table(gct.into()) + .image(ImageBuilder::new(4, 4) + .color_table(colortable.into()) + .indicies(indicies.clone()) + ).image(ImageBuilder::new(4, 4) + .indicies(indicies) + ).build().to_vec(); + + assert_eq!(actual_out, expected_out); + } +} \ No newline at end of file diff --git a/src/components/image.rs b/src/components/image.rs new file mode 100644 index 0000000..07f9555 --- /dev/null +++ b/src/components/image.rs @@ -0,0 +1,47 @@ +use crate::LZW; +use super::{ColorTable, ImageDescriptor}; + +pub struct Image { + pub image_descriptor: ImageDescriptor, + pub local_color_table: Option<ColorTable>, + pub indicies: Vec<u8> +} + +impl Image { + 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); + + // Table based image data // + + // Get the mcs while we write out the color table + let mut mcs = if let Some(lct) = &self.local_color_table { + boxed = lct.into(); + out.extend_from_slice(&*boxed); + + lct.packed_len() + } else { + minimum_code_size + }; + + if mcs < 2 { + mcs = 2; // Must be true: 0 <= mcs <= 8 + } + + // First write out the MCS + out.push(mcs); + + let compressed = LZW::encode(mcs, &self.indicies); + + for chunk in compressed.chunks(255) { + out.push(chunk.len() as u8); + out.extend_from_slice(chunk); + } + // Data block length 0 to indicate an end + out.push(0x00); + + out.into_boxed_slice() + } +} \ No newline at end of file diff --git a/src/components/imagedescriptor.rs b/src/components/imagedescriptor.rs new file mode 100644 index 0000000..c911baa --- /dev/null +++ b/src/components/imagedescriptor.rs @@ -0,0 +1,45 @@ +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 color_table_present(&mut self, is_present: bool) { + if is_present { + self.packed |= 0b1000_0000; + } else { + self.packed &= 0b0111_1111; + } + } + + pub fn color_table_size(&mut self, size: u8) { + // GCT size is calulated by raising two to this number plus one, + // so we have to work backwards. + let size = (size as f32).log2().ceil() - 1f32; + self.packed |= size as u8; + } + + //TODO: Setter for sort flag in packed field + //TODO: Setter for interlace flag in packed field +} + +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() + } +} + +//TODO: Impl to allow changing the packed field easier \ No newline at end of file diff --git a/src/components/logicalscreendescriptor.rs b/src/components/logicalscreendescriptor.rs new file mode 100644 index 0000000..b20c9d5 --- /dev/null +++ b/src/components/logicalscreendescriptor.rs @@ -0,0 +1,40 @@ +pub struct LogicalScreenDescriptor { + pub width: u16, + pub height: u16, + pub packed: u8, + pub background_color_index: u8, + pub pixel_aspect_ratio: u8 +} + +impl LogicalScreenDescriptor { + pub fn color_table_present(&mut self, is_present: bool) { + if is_present { + self.packed |= 0b1000_0000; + } else { + self.packed &= 0b0111_1111; + } + } + + pub fn color_table_size(&mut self, size: u8) { + // GCT size is calulated by raising two to this number plus one, + // so we have to work backwards. + let size = (size as f32).log2().ceil() - 1f32; + self.packed |= size as u8; + } + + //TODO: Setter for sort flag in packed field + //TODO: Setter for color resolution in packed field +} + +impl From<&LogicalScreenDescriptor> for Box<[u8]> { + fn from(lsd: &LogicalScreenDescriptor) -> Self { + let mut vec = vec![]; + vec.extend_from_slice(&lsd.width.to_le_bytes()); + vec.extend_from_slice(&lsd.height.to_le_bytes()); + vec.push(lsd.packed); + vec.push(lsd.background_color_index); + vec.push(lsd.pixel_aspect_ratio); + + vec.into_boxed_slice() + } +} \ No newline at end of file diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..1650d36 --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,15 @@ +mod color; +mod colortable; +mod gif; +mod image; +mod imagedescriptor; +mod logicalscreendescriptor; +mod version; + +pub use color::Color; +pub use colortable::ColorTable; +pub use gif::Gif; +pub use image::Image; +pub use imagedescriptor::ImageDescriptor; +pub use logicalscreendescriptor::LogicalScreenDescriptor; +pub use version::Version; \ No newline at end of file diff --git a/src/components/version.rs b/src/components/version.rs new file mode 100644 index 0000000..a5d688d --- /dev/null +++ b/src/components/version.rs @@ -0,0 +1,13 @@ +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" + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 187decb..6eb07d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ -mod common; -mod writer; +mod components; +mod lzw; +pub mod writer; -pub use common::Version; -pub use writer::GifBuilder; \ No newline at end of file +pub use components::*; +pub use lzw::LZW; \ No newline at end of file diff --git a/src/writer/lzw.rs b/src/lzw.rs index 1970617..1970617 100644 --- a/src/writer/lzw.rs +++ b/src/lzw.rs diff --git a/src/writer/gifbuilder.rs b/src/writer/gifbuilder.rs index ff98ba7..6ae55d5 100644 --- a/src/writer/gifbuilder.rs +++ b/src/writer/gifbuilder.rs @@ -1,12 +1,11 @@ -use crate::common::{Color, Version}; -use super::OutVec; +use crate::components::{ColorTable, Gif, LogicalScreenDescriptor, Version}; use super::ImageBuilder; pub struct GifBuilder { version: Version, width: u16, height: u16, - global_color_table: Option<Vec<Color>>, + global_color_table: Option<ColorTable>, background_color_index: u8, imagebuilders: Vec<ImageBuilder> } @@ -23,13 +22,8 @@ impl GifBuilder { } } - pub fn global_color_table(mut self, vec: Vec<Color>) -> Self { - if vec.len() == 0 || vec.len() > 256 { - //TODO: Throw error instead of panic - panic!("GCT has to be less than or 256 colors in size, and at least one"); - } - - self.global_color_table = Some(vec); + pub fn global_color_table(mut self, table: ColorTable) -> Self { + self.global_color_table = Some(table); self } @@ -49,190 +43,30 @@ impl GifBuilder { self } - pub fn write_to_vec(&self) -> Vec<u8> { - let mut out = OutVec::new(); - - self - .write_header(&mut out) - .write_logical_screen_descriptor(&mut out) - .write_global_color_table(&mut out) - .write_images(&mut out) - .write_trailer(&mut out); - - out.vec() - } - - fn write_header(&self, out: &mut OutVec) -> &Self { - out.push_slice(b"GIF"); - - //TODO: Automatically detect version - match self.version { - Version::Gif87a => out.push_slice(b"87a"), - Version::Gif89a => out.push_slice(b"89a") + pub fn build(self) -> Gif { + let mut lsd = LogicalScreenDescriptor { + width: self.width, + height: self.height, + packed: 0, // Set later + background_color_index: self.background_color_index, + pixel_aspect_ratio: 0 //TODO: Allow configuring }; - self - } - - fn write_logical_screen_descriptor(&self, out: &mut OutVec) -> &Self { - out.push_u16(self.width).push_u16(self.height); - - let mut packed: u8 = 0; - if let Some(gct) = &self.global_color_table { - packed |= 0b1000_0000; // Set the GCT flag - - // GCT size is calulated by raising two to this number plus one, - // so we have to work backwards. - let size = (gct.len() as f32).log2().ceil() - 1f32; - - packed |= size as u8; + lsd.color_table_present(true); + lsd.color_table_size(gct.len() as u8); } - //TODO: Color Resolution and Sort Flag fields in packed. - out.push_u8(packed); - - out.push_u8(self.background_color_index) - .push_u8(0x00); //TOOD: Allow setting pixel aspect ratio - self - } - - fn write_global_color_table(&self, out: &mut OutVec) -> &Self { - if let Some(gct) = &self.global_color_table { - out.push_colors(&gct); + let mut images = vec![]; + for builder in self.imagebuilders.into_iter() { + images.push(builder.build()); } - self - } - - fn write_images(&self, out: &mut OutVec) -> &Self { - //TODO: Deduplicate color table size code - let mcs = if let Some(gct) = &self.global_color_table { - ((gct.len() as f32).log2().ceil() - 1f32) as u8 - } else { - 0 - }; - - for ib in &self.imagebuilders { - let image = ib.write_to_vec(mcs); - out.push_slice(&image); + Gif { + header: self.version, + logical_screen_descriptor: lsd, + global_color_table: self.global_color_table, + images } - - self - } - - fn write_trailer(&self, out: &mut OutVec) { - /* - "This block is a single-field block indicating the end of the GIF Data Stream. - It contains the fixed value 0x3B." - */ - out.push_u8(0x3B); - } -} - -#[cfg(test)] -pub mod gifbuilder_test { - use super::*; - - #[test] - pub fn writer_header() { - let mut out = OutVec::new(); - GifBuilder::new(Version::Gif87a, 0, 0).write_header(&mut out); - - assert_eq!(out.vec(), b"GIF87a"); - } - - #[test] - pub fn write_logical_screen_descriptor() { - let mut out = OutVec::new(); - GifBuilder::new(Version::Gif87a, 4, 4).write_logical_screen_descriptor(&mut out); - - assert_eq!(out.vec(), vec![0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00]); - - let mut out = OutVec::new(); - let gct = vec![ - Color::new(0, 0, 0), Color::new(0, 0, 0), Color::new(0, 0, 0), - Color::new(0, 0, 0), Color::new(0, 0, 0) - ]; - GifBuilder::new(Version::Gif87a, 4, 4).global_color_table(gct).write_logical_screen_descriptor(&mut out); - - assert_eq!(out.vec(), vec![0x04, 0x00, 0x04, 0x00, 0b1000_0010, 0x00, 0x00]); - } - - #[test] - pub fn write_global_color_table() { - let mut out = OutVec::new(); - let gct = vec![ - Color::new(1, 2, 3), Color::new(253, 254, 255) - ]; - GifBuilder::new(Version::Gif87a, 4, 4).global_color_table(gct).write_global_color_table(&mut out); - - assert_eq!(out.vec(), vec![1, 2, 3, 253, 254, 255]); - } - - #[test] - fn write_images() { - 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![ - 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 - ]; - - let mut out = OutVec::new(); - GifBuilder::new(Version::Gif87a, 4, 4) - .global_color_table(gct) - .image(ImageBuilder::new(4, 4) - .color_table(colortable) - .indicies(indicies.clone()) - ).image(ImageBuilder::new(4, 4) - .indicies(indicies) - ).write_images(&mut out); - - assert_eq!(out.vec(), expected_out); - } - - #[test] - fn write_to_vec() { - 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 mut out = OutVec::new(); - let actual_out = GifBuilder::new(Version::Gif87a, 4, 4) - .global_color_table(gct) - .image(ImageBuilder::new(4, 4) - .color_table(colortable) - .indicies(indicies.clone()) - ).image(ImageBuilder::new(4, 4) - .indicies(indicies) - ).write_to_vec(); - - assert_eq!(actual_out, expected_out); } } \ No newline at end of file diff --git a/src/writer/imagebuilder.rs b/src/writer/imagebuilder.rs index cd96908..0636bb3 100644 --- a/src/writer/imagebuilder.rs +++ b/src/writer/imagebuilder.rs @@ -1,13 +1,11 @@ -use crate::common::Color; -use super::OutVec; -use super::LZW; +use crate::components::{ColorTable, Image, ImageDescriptor}; pub struct ImageBuilder { left_offset: u16, top_offset: u16, width: u16, height: u16, - color_table: Option<Vec<Color>>, + color_table: Option<ColorTable>, indicies: Vec<u8> } @@ -39,13 +37,8 @@ impl ImageBuilder { self } - pub fn color_table(mut self, vec: Vec<Color>) -> Self { - if vec.len() == 0 || vec.len() > 256 { - //TODO: Throw error instead of panic - panic!("Color table has to be less than or 256 colors in size, and at least one"); - } - - self.color_table = Some(vec); + pub fn color_table(mut self, table: ColorTable) -> Self { + self.color_table = Some(table); self } @@ -55,142 +48,24 @@ impl ImageBuilder { self } - //TODO: Make lzw_minimum_code_size optional. ONly needed with global color tables - pub fn write_to_vec(&self, lzw_minimum_code_size: u8) -> Vec<u8> { - let mut out = OutVec::new(); - - self.write_image_descriptor(&mut out) - .write_color_table(&mut out) - .write_image_data(&mut out, lzw_minimum_code_size); - - out.vec() - } - - fn write_image_descriptor(&self, out: &mut OutVec) -> &Self { - // Image seperator. At the start of every image descriptor - out.push_u8(0x2C) - .push_u16(self.left_offset) - .push_u16(self.top_offset) - .push_u16(self.width) - .push_u16(self.height); - - // Taken from gifbuilder.rs - //TODO: deduplciate code - let mut packed: u8 = 0; - if let Some(ct) = &self.color_table { - packed |= 0b1000_0000; // Set the color table flag - - let size = (ct.len() as f32).log2().ceil() - 1f32; - - packed |= size as u8; + pub fn build(self) -> Image { + let mut imgdesc = ImageDescriptor { + left: self.left_offset, + top: self.top_offset, + width: self.width, + height: self.height, + packed: 0 // Set later + }; + + if let Some(lct) = &self.color_table { + imgdesc.color_table_present(true); + imgdesc.color_table_size(lct.packed_len()); } - //TODO: Interlace and Sort flags in packed - out.push_u8(packed); - - self - } - fn write_color_table(&self, out: &mut OutVec) -> &Self { - if let Some(ct) = &self.color_table { - out.push_colors(&ct); + Image { + image_descriptor: imgdesc, + local_color_table: self.color_table, + indicies: self.indicies } - - self - } - - fn write_image_data(&self, out: &mut OutVec, minimum_code_size: u8) -> &Self { - let mut mcs = minimum_code_size; - - //TODO: Deduplicate color table size code - if let Some(ct) = &self.color_table { - mcs = ((ct.len() as f32).log2().ceil() - 1f32) as u8; - } - - if mcs < 2 { - // Must always be true: 2 <= mcs <= 8 - mcs = 2; - } - - // First write out the MCS - out.push_u8(mcs); - - let compressed = LZW::encode(mcs, &self.indicies); - - for chunk in compressed.chunks(255) { - out.push_u8(chunk.len() as u8); - out.push_slice(chunk); - } - // Data block length 0 to indicate an end - out.push_u8(0x00); - - self } } - -#[cfg(test)] -mod imagebuilder_test { - use super::*; - - #[test] - fn write_to_vec() { - 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![ - 0x2C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0b1000_0000, // Image Descriptor - 0, 0, 0, 128, 0, 255, // Color Table - 0x02, 0x05, 0x84, 0x1D, 0x81, 0x7A, 0x50, 0x00 // Image Data - ]; - let actual_out = ImageBuilder::new(4, 4) - .color_table(colortable) - .indicies(indicies) - .write_to_vec(0); - - assert_eq!(actual_out, expected_out); - } - - #[test] - fn write_image_descriptor() { - let mut out = OutVec::new(); - ImageBuilder::new(16, 16).offsets(1, 6).write_image_descriptor(&mut out); - - assert_eq!(out.vec(), vec![0x2C, 0x01, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00]); - - let mut out = OutVec::new(); - ImageBuilder::new(16, 16) - .offsets(1, 6) - .color_table(vec![Color::new(0, 0, 0)]) - .write_image_descriptor(&mut out); - - assert_eq!(out.vec(), vec![0x2C, 0x01, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0b1000_0000]); - } - - #[test] - fn write_color_table() { - let mut out = OutVec::new(); - ImageBuilder::new(16, 16) - .color_table(vec![Color::new(1, 2, 3), Color::new(253, 254, 255)]) - .write_color_table(&mut out); - - assert_eq!(out.vec(), vec![0x01, 0x02, 0x03, 0xFD, 0xFE, 0xFF]); - } - - #[test] - fn write_image_data() { - #[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![0x02, 0x05, 0x84, 0x1D, 0x81, 0x7A, 0x50, 0x00]; - - let mut out = OutVec::new(); - ImageBuilder::new(16, 16) - .indicies(indicies) - .write_image_data(&mut out, 2); - - assert_eq!(out.vec(), output); - } - } -} \ No newline at end of file diff --git a/src/writer/mod.rs b/src/writer/mod.rs index 965dcfa..b801a3a 100644 --- a/src/writer/mod.rs +++ b/src/writer/mod.rs @@ -1,9 +1,5 @@ -mod outvec; mod gifbuilder; -mod lzw; mod imagebuilder; -use outvec::OutVec; pub use gifbuilder::GifBuilder; -use lzw::LZW; pub use imagebuilder::ImageBuilder; \ No newline at end of file diff --git a/src/writer/outvec.rs b/src/writer/outvec.rs deleted file mode 100644 index eb04934..0000000 --- a/src/writer/outvec.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::common::Color; - -#[derive(Debug, PartialEq)] -pub struct OutVec { - data: Vec<u8> -} - -impl OutVec { - pub fn new() -> Self { - Self { - data: vec![] - } - } - - pub fn push_u8(&mut self, n: u8) -> &mut Self { - self.data.push(n); - self - } - - pub fn push_u16(&mut self, n: u16) -> &mut Self { - self.data.extend_from_slice(&n.to_le_bytes()); - self - } - - pub fn push_u32(&mut self, n: u32) -> &mut Self { - self.data.extend_from_slice(&n.to_le_bytes()); - self - } - - pub fn push_u64(&mut self, n: u64) -> &mut Self { - self.data.extend_from_slice(&n.to_le_bytes()); - self - } - - pub fn push_slice(&mut self, slice: &[u8]) -> &mut Self { - self.data.extend_from_slice(slice); - self - } - - pub fn push_color(&mut self, color: &Color) -> &mut Self { - self.data.extend_from_slice(&[color.r, color.g, color.b]); - self - } - - pub fn push_colors(&mut self, colors: &[Color]) -> &mut Self { - for color in colors { - self.push_color(color); - } - self - } - - pub fn vec(self) -> Vec<u8> { - self.data - } -} - -impl From<Vec<u8>> for OutVec { - fn from(v: Vec<u8>) -> Self { - let mut outvec = Self::new(); - outvec.push_slice(&v); - - outvec - } -} \ No newline at end of file |