diff options
author | gennyble <gen@nyble.dev> | 2023-10-09 17:45:19 -0500 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2023-10-09 17:45:19 -0500 |
commit | 25b78bb991fa96864558cccd651519c296af1e91 (patch) | |
tree | 417eb3c94b4dd8477d6971b4d3401af5ebfd928e /gifed/src | |
parent | fa2943a6a4bc1d276b458fefae48b28cd78cdb9c (diff) | |
download | gifed-25b78bb991fa96864558cccd651519c296af1e91.tar.gz gifed-25b78bb991fa96864558cccd651519c296af1e91.zip |
changes
Diffstat (limited to 'gifed/src')
-rw-r--r-- | gifed/src/block/extension/application.rs | 1 | ||||
-rw-r--r-- | gifed/src/block/indexedimage.rs | 24 | ||||
-rw-r--r-- | gifed/src/block/mod.rs | 2 | ||||
-rw-r--r-- | gifed/src/block/palette.rs | 160 | ||||
-rw-r--r-- | gifed/src/block/screendescriptor.rs | 1 | ||||
-rw-r--r-- | gifed/src/gif.rs | 154 | ||||
-rw-r--r-- | gifed/src/writer/mod.rs | 4 |
7 files changed, 200 insertions, 146 deletions
diff --git a/gifed/src/block/extension/application.rs b/gifed/src/block/extension/application.rs index 2244c35..f13047a 100644 --- a/gifed/src/block/extension/application.rs +++ b/gifed/src/block/extension/application.rs @@ -1,3 +1,4 @@ +#[derive(Clone, Debug)] pub struct Application { pub(crate) identifier: [u8; 8], pub(crate) authentication_code: [u8; 3], diff --git a/gifed/src/block/indexedimage.rs b/gifed/src/block/indexedimage.rs index 1868382..261ff38 100644 --- a/gifed/src/block/indexedimage.rs +++ b/gifed/src/block/indexedimage.rs @@ -34,7 +34,7 @@ impl IndexedImage { /// The `lzw_code_size` should be None if there is a local color table present. If /// this image is using the Global Color Table, you must provide an - /// LZW Minimum Code Size here. It is equal to the value of [Palette::packed_len], but + /// LZW Minimum Code Size here. It is equal to the value of [Palette::packed_len] + 1, but /// must also be at least 2. pub fn compress(self, lzw_code_size: Option<u8>) -> Result<CompressedImage, EncodeError> { // gen- The old code had a +1 here. Why? @@ -46,14 +46,12 @@ impl IndexedImage { Some(palette) => palette.lzw_code_size(), None => match lzw_code_size { None => return Err(EncodeError::InvalidCodeSize { lzw_code_size: 0 }), - Some(mcs) => mcs, + Some(mcs) => mcs.max(2), }, }; - let mcs = if mcs < 2 { 2 } else { mcs }; - //FIXME: gen- This seems broken - //let compressed = LZW::encode(mcs, &self.indicies); + //let compressed = crate::LZW::encode(mcs, &self.indicies); let compressed = Encoder::new(weezl::BitOrder::Lsb, mcs) .encode(&self.indicies) .unwrap(); @@ -86,7 +84,7 @@ impl CompressedImage { } pub fn top(&self) -> u16 { - self.image_descriptor.left + self.image_descriptor.top } pub fn width(&self) -> u16 { @@ -105,6 +103,11 @@ impl CompressedImage { let mut ret = vec![]; ret.extend_from_slice(&self.image_descriptor.as_bytes()); + + if let Some(palette) = &self.local_color_table { + ret.extend_from_slice(&palette.as_bytes()); + } + ret.push(self.lzw_code_size); for block in &self.blocks { @@ -128,8 +131,15 @@ impl CompressedImage { let data: Vec<u8> = blocks.into_iter().map(<_>::into_iter).flatten().collect(); + println!("lzw: {lzw_code_size}"); + + if local_color_table.is_some() { + let lct = local_color_table.as_ref().unwrap(); + println!("lct-lzw: {}", lct.lzw_code_size()); + } + //TODO: remove unwrap - let mut decompressor = weezl::decode::Decoder::new(weezl::BitOrder::Msb, lzw_code_size); + let mut decompressor = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, lzw_code_size); let indicies = match decompressor.decode(&data) { Err(LzwError::InvalidCode) => Err(DecodeError::LzwInvalidCode), Ok(o) => Ok(o), diff --git a/gifed/src/block/mod.rs b/gifed/src/block/mod.rs index 36cc6fe..3a18a07 100644 --- a/gifed/src/block/mod.rs +++ b/gifed/src/block/mod.rs @@ -16,6 +16,7 @@ pub use version::Version; use self::extension::Application; use self::extension::GraphicControl; +#[derive(Clone, Debug)] pub enum Block { CompressedImage(CompressedImage), //TODO: Extension(Extension), @@ -26,6 +27,7 @@ pub enum Block { LoopingExtension(LoopCount), } +#[derive(Clone, Debug)] pub enum LoopCount { Forever, Number(u16), diff --git a/gifed/src/block/palette.rs b/gifed/src/block/palette.rs index d5cc696..67d9ae8 100644 --- a/gifed/src/block/palette.rs +++ b/gifed/src/block/palette.rs @@ -27,7 +27,8 @@ impl Palette { } pub fn lzw_code_size(&self) -> u8 { - self.packed_len() + 1 + let table_log = (self.table.len() as f32).log2() as u8; + table_log.max(2) } /// Returns the number of colours in the pallette @@ -62,17 +63,20 @@ impl Palette { /// How many padding bytes we need to write. /// We need to pad the colour table because the size must be a power of two. //TODO: gen- better docs - pub fn padding(&self) -> usize { + fn padding(&self) -> usize { let comp = self.computed_len(); (comp as usize - self.len()) * 3 } + /// The palette with padding if required pub fn as_bytes(&self) -> Vec<u8> { let mut bytes = Vec::with_capacity(self.table.len() * 3); for color in &self.table { bytes.extend_from_slice(&[color.r, color.g, color.b]); } + bytes.extend(std::iter::repeat(0u8).take(self.padding())); + bytes } } @@ -91,6 +95,22 @@ impl AsRef<Palette> for Palette { } } +impl PartialEq for Palette { + fn eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + return false; + } + + for color in &other.table { + if !self.table.contains(color) { + return false; + } + } + + true + } +} + //TODO: TryFrom Vec<u8> (must be multiple of 3 len) and From Vec<Color> impl TryFrom<&[u8]> for Palette { type Error = (); @@ -134,3 +154,139 @@ impl TryFrom<Vec<(u8, u8, u8)>> for Palette { } } } + +#[cfg(test)] +mod test { + use super::*; + + fn vec_tuple_test(vec: Vec<(u8, u8, u8)>, expected: &[u8]) { + let plt: Palette = vec.try_into().unwrap(); + let bytes = plt.as_bytes(); + + assert_eq!(expected, bytes.as_slice()) + } + + #[test] + fn writes_one_with_padding() { + vec_tuple_test(vec![(1, 2, 3)], &[1, 2, 3, 0, 0, 0]) + } + + #[test] + fn writes_two_without_padding() { + vec_tuple_test(vec![(1, 2, 3), (4, 5, 6)], &[1, 2, 3, 4, 5, 6]) + } + + fn test_n_with_padding(real_count: usize, exected_padding_bytes: usize) { + let mut palette = Palette::new(); + let mut expected = vec![]; + + for x in 0..real_count { + let x = x as u8; + palette.push(Color { r: x, g: x, b: x }); + expected.extend_from_slice(&[x, x, x]) + } + + // yes, this is really how I'm doing it. I have... trust issues with + // myself and iter::repeat. i hope you understand + for _ in 0..exected_padding_bytes { + expected.push(0x00); + } + + let bytes = palette.as_bytes(); + assert_eq!(expected, bytes.as_slice()) + } + + fn test_n_with_padding_range(real_count_low: u8, real_count_high: u8, next_padstop: usize) { + for x in real_count_low..=real_count_high { + test_n_with_padding(x as usize, (next_padstop as usize - x as usize) * 3) + } + } + + #[test] + fn writes_three_with_padding() { + test_n_with_padding(3, 3); + } + + #[test] + fn writes_four_without_padding() { + test_n_with_padding(4, 0); + } + + #[test] + fn writes_five_to_seven_with_padding() { + test_n_with_padding_range(5, 7, 8); + } + + #[test] + fn writes_eight_without_padding() { + test_n_with_padding(8, 0); + } + + #[test] + fn writes_nine_to_fifteen_with_padding() { + test_n_with_padding_range(9, 15, 16); + } + + #[test] + fn writes_sixteen_without_padding() { + test_n_with_padding(16, 0); + } + + #[test] + fn writes_seventeen_to_thirtyone_with_padding() { + test_n_with_padding_range(17, 31, 32); + } + + #[test] + fn writes_thirtytwo_without_padding() { + test_n_with_padding(32, 0); + } + + #[test] + fn writes_thirtythree_to_sixtythree_with_padding() { + test_n_with_padding_range(33, 63, 64); + } + + #[test] + fn writes_sixtyfour_without_padding() { + test_n_with_padding(64, 0); + } + + #[test] + fn writes_sixtyfive_to_onehundredtwentyseven_with_padding() { + test_n_with_padding_range(65, 127, 128); + } + + #[test] + fn writes_onetwentyeight_without_padding() { + test_n_with_padding(128, 0); + } + + #[test] + fn writes_onetwentynine_to_twofiftyfive_with_padding() { + test_n_with_padding_range(129, 255, 256); + } + + #[test] + fn writes_256_without_padding() { + test_n_with_padding(256, 0); + } + + #[test] + fn packed_len_are_correct() { + let black = Color::new(0, 0, 0); + let mut palette = Palette::new(); + + // Nothing is nothing + assert_eq!(0, palette.packed_len()); + + // One color is still 0 because the formula is + // 2 ^ (len + 1) + // which means we should increase at 3 + palette.push(black); + assert_eq!(0, palette.packed_len()); + + palette.push(black); + assert_eq!(0, palette.packed_len()); + } +} diff --git a/gifed/src/block/screendescriptor.rs b/gifed/src/block/screendescriptor.rs index d44ca2f..a23bfdd 100644 --- a/gifed/src/block/screendescriptor.rs +++ b/gifed/src/block/screendescriptor.rs @@ -2,6 +2,7 @@ use std::convert::TryInto; use super::{packed::ScreenPacked, Palette}; +#[derive(Clone, Debug)] pub struct ScreenDescriptor { pub width: u16, pub height: u16, diff --git a/gifed/src/gif.rs b/gifed/src/gif.rs index 09b052e..4e70ffd 100644 --- a/gifed/src/gif.rs +++ b/gifed/src/gif.rs @@ -8,6 +8,8 @@ use crate::{ }, writer::GifBuilder, }; + +#[derive(Clone, Debug)] pub struct Gif { pub header: Version, pub screen_descriptor: ScreenDescriptor, @@ -20,6 +22,24 @@ impl Gif { GifBuilder::new(width, height) } + pub fn width(&self) -> usize { + self.screen_descriptor.width as usize + } + + pub fn height(&self) -> usize { + self.screen_descriptor.height as usize + } + + pub fn background_color(&self) -> Option<u8> { + // vii) Background Color Index - If the Global Color Table Flag is set + // to (zero), this field should be zero and should be ignored. + if self.screen_descriptor.has_color_table() { + Some(self.screen_descriptor.background_color_index) + } else { + None + } + } + pub fn as_bytes(&self) -> Vec<u8> { let mut out = vec![]; @@ -191,137 +211,3 @@ pub enum FrameControl { Input, InputOrDelay(Duration), } - -#[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()) - .build(indicies.clone()) - .unwrap(), - ) - .image(ImageBuilder::new(4, 4).build(indicies).unwrap()); - - let bytes = actual.build().unwrap().as_bytes(); - 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()) - .disposal_method(DisposalMethod::RestoreBackground) - .delay(64) - .build(indicies.clone()) - .unwrap(), - ) - .image(ImageBuilder::new(4, 4).build(indicies).unwrap()) - .build() - .unwrap() - .as_bytes(); - - 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/gifed/src/writer/mod.rs b/gifed/src/writer/mod.rs index 493514b..c2e8382 100644 --- a/gifed/src/writer/mod.rs +++ b/gifed/src/writer/mod.rs @@ -4,7 +4,7 @@ mod imagebuilder; use std::{error::Error, fmt, io::Write}; pub use gifbuilder::GifBuilder; -pub use imagebuilder::ImageBuilder; +pub use imagebuilder::{BuiltImage, ImageBuilder}; use crate::block::{encode_block, Block, LoopCount, Palette, ScreenDescriptor, Version}; @@ -47,9 +47,7 @@ impl<W: Write> Writer<W> { this.write_all(&screen_descriptor.as_bytes())?; if let Some(palette) = this.global_palette.as_ref() { - let padding: Vec<u8> = std::iter::repeat(0u8).take(palette.padding()).collect(); this.write_all(&palette.as_bytes())?; - this.write_all(&padding)?; } Ok(this) |