diff options
Diffstat (limited to 'src/writer')
-rw-r--r-- | src/writer/gifbuilder.rs | 208 | ||||
-rw-r--r-- | src/writer/imagebuilder.rs | 165 | ||||
-rw-r--r-- | src/writer/lzw.rs | 175 | ||||
-rw-r--r-- | src/writer/mod.rs | 4 | ||||
-rw-r--r-- | src/writer/outvec.rs | 64 |
5 files changed, 41 insertions, 575 deletions
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/lzw.rs b/src/writer/lzw.rs deleted file mode 100644 index 1970617..0000000 --- a/src/writer/lzw.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::collections::HashMap; - -pub struct LZW {} -impl LZW { - //TODO: Cleanup and make not awful - pub fn encode(minimum_size: u8, indicies: &[u8]) -> Vec<u8> { - let mut dictionary: HashMap<Vec<u8>, u16> = HashMap::new(); - - let cc: u16 = 2u16.pow(minimum_size as u32); - let eoi: u16 = cc+1; - - let mut index: u16 = eoi+1; // Next code number - let mut codesize: u8 = minimum_size+1; // Current code size - - //println!("cc: {} - eoi: {}", cc, eoi); - - // Populate starting codes - for code in 0..cc { - dictionary.insert(vec![code as u8], code); - } - - let mut iter = indicies.iter(); - let mut out = BitStream::new(); - let mut buffer = vec![*iter.next().unwrap()]; //TODO: Remove unwrap - - //"Encoders should output a Clear code as the first code of each image data stream." - out.push_bits(codesize, cc); - - //println!("Before Loop\n\tBuffer: {:?}\n\tCodesize:{}\n\tIndex:{}", buffer, codesize, index); - - for next_code in iter { - buffer.push(*next_code); - - //println!("Buffer: {:?} - Codesize:{} - Index:{}\n\tDict: {:?}", buffer, codesize, index, dictionary); - - match dictionary.get(&buffer) { - Some(_code) => { - //println!("\tPresent!"); - continue; - }, - None => { - buffer.pop(); - match dictionary.get(&buffer) { - Some(code) => { - out.push_bits(codesize, *code); - //println!("\tOutputting {} with {} bits. Buffer is {:?}", *code, codesize, buffer); - - // Add new entry for buffer and increase the index - buffer.push(*next_code); - dictionary.insert(buffer, index); - index += 1; - - // Increase code size if we should - if index-1 == 2u16.pow(codesize as u32) { - codesize += 1; - } - - // Reset the buffer to only contain next_code - buffer = vec![*next_code]; - }, - None => panic!("No dictionary entry when there should be! Something is wrong!") - } - } - } - } - - if buffer.len() > 0 { - match dictionary.get(&buffer) { - None => panic!("No codes left but not in the dictionary!"), - Some(code) => out.push_bits(codesize, *code) - } - } - out.push_bits(codesize, eoi); - - out.vec() - } -} - -#[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); - } -} - -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]); - } -} \ 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 |