diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | gifed/Cargo.toml | 4 | ||||
-rw-r--r-- | gifed/src/block/indexedimage.rs | 4 | ||||
-rw-r--r-- | gifed/src/block/packed.rs | 2 | ||||
-rw-r--r-- | gifed/src/block/palette.rs | 18 | ||||
-rw-r--r-- | gifed/src/color.rs | 6 | ||||
-rw-r--r-- | gifed/src/gif.rs | 160 | ||||
-rw-r--r-- | gifed/src/lib.rs | 2 | ||||
-rw-r--r-- | gifed/src/lzw.rs | 175 | ||||
-rw-r--r-- | gifed/src/reader/mod.rs | 11 | ||||
-rw-r--r-- | gifprobe/src/main.rs | 6 |
11 files changed, 292 insertions, 97 deletions
diff --git a/Cargo.toml b/Cargo.toml index 948388b..c3f707d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] members = ["gifed", "gifprobe", "gaudio", "gifcheck"] +resolver = "2" diff --git a/gifed/Cargo.toml b/gifed/Cargo.toml index 79f86ce..a27ff7b 100644 --- a/gifed/Cargo.toml +++ b/gifed/Cargo.toml @@ -8,4 +8,8 @@ description = "Gif encoding and decoding with fine control" repository = "https://github.com/genuinebyte/gifed" [dependencies] +bitvec = "1.0.1" weezl = "0.1.5" + +[dev-dependencies] +rand = "0.8.5" diff --git a/gifed/src/block/indexedimage.rs b/gifed/src/block/indexedimage.rs index 261ff38..c98e753 100644 --- a/gifed/src/block/indexedimage.rs +++ b/gifed/src/block/indexedimage.rs @@ -17,7 +17,7 @@ impl IndexedImage { } pub fn top(&self) -> u16 { - self.image_descriptor.left + self.image_descriptor.top } pub fn width(&self) -> u16 { @@ -129,7 +129,7 @@ impl CompressedImage { blocks, } = self; - let data: Vec<u8> = blocks.into_iter().map(<_>::into_iter).flatten().collect(); + let data: Vec<u8> = blocks.into_iter().flat_map(<_>::into_iter).collect(); println!("lzw: {lzw_code_size}"); diff --git a/gifed/src/block/packed.rs b/gifed/src/block/packed.rs index 455f43a..0752e39 100644 --- a/gifed/src/block/packed.rs +++ b/gifed/src/block/packed.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unusual_byte_groupings)] + #[derive(Clone, Copy, Debug, PartialEq)] pub struct GraphicPacked { pub raw: u8, diff --git a/gifed/src/block/palette.rs b/gifed/src/block/palette.rs index 67d9ae8..8a06883 100644 --- a/gifed/src/block/palette.rs +++ b/gifed/src/block/palette.rs @@ -36,10 +36,14 @@ impl Palette { self.table.len() } + pub fn is_empty(&self) -> bool { + self.len() > 0 + } + /// Returns the number of items that the decoder *thinks* is in the palette. /// This is 2^(n + 1) where n = [Palette::packed_len] pub fn computed_len(&self) -> usize { - 2usize.pow(self.packed_len() as u32 + 1) + 1 << (self.packed_len() + 1) } /// Pushes a color on to the end of the table @@ -48,7 +52,7 @@ impl Palette { } pub fn get(&self, index: u8) -> Option<Color> { - self.table.get(index as usize).map(|v| v.clone()) + self.table.get(index as usize).copied() } pub fn from_color<C: AsRef<Color>>(&self, color: C) -> Option<u8> { @@ -65,7 +69,7 @@ impl Palette { //TODO: gen- better docs fn padding(&self) -> usize { let comp = self.computed_len(); - (comp as usize - self.len()) * 3 + (comp - self.len()) * 3 } /// The palette with padding if required @@ -81,6 +85,12 @@ impl Palette { } } +impl Default for Palette { + fn default() -> Self { + Self::new() + } +} + impl Deref for Palette { type Target = [Color]; @@ -117,7 +127,7 @@ impl TryFrom<&[u8]> for Palette { fn try_from(value: &[u8]) -> Result<Self, Self::Error> { if value.len() % 3 != 0 { - return Err(()); + Err(()) } else { Ok(Self { table: value diff --git a/gifed/src/color.rs b/gifed/src/color.rs index e1b727a..9f2bbe5 100644 --- a/gifed/src/color.rs +++ b/gifed/src/color.rs @@ -37,8 +37,8 @@ impl From<(u8, u8, u8)> for Color { } } -impl Into<[u8; 3]> for Color { - fn into(self) -> [u8; 3] { - [self.r, self.g, self.b] +impl From<Color> for [u8; 3] { + fn from(val: Color) -> Self { + [val.r, val.g, val.b] } } diff --git a/gifed/src/gif.rs b/gifed/src/gif.rs index 4e70ffd..46807a9 100644 --- a/gifed/src/gif.rs +++ b/gifed/src/gif.rs @@ -43,7 +43,7 @@ impl Gif { pub fn as_bytes(&self) -> Vec<u8> { let mut out = vec![]; - out.extend_from_slice(&self.header.as_bytes()); + out.extend_from_slice(self.header.as_bytes()); out.extend_from_slice(&self.screen_descriptor.as_bytes()); if let Some(gct) = &self.global_color_table { @@ -64,7 +64,7 @@ impl Gif { File::create(path.as_ref())?.write_all(&self.as_bytes()) } - pub fn images<'a>(&'a self) -> ImageIterator<'a> { + pub fn images(&self) -> ImageIterator<'_> { ImageIterator { gif: self, block_index: 0, @@ -84,24 +84,19 @@ impl<'a> Iterator for ImageIterator<'a> { let starting_block = self.block_index; let img = loop { - match self.gif.blocks.get(self.block_index) { - Some(block) => match block { - Block::CompressedImage(img) => { - // Step over this image so we don't hit it next time - self.block_index += 1; - - break img; - } - _ => (), - }, - None => return None, + let block = self.gif.blocks.get(self.block_index)?; + if let Block::CompressedImage(img) = block { + // Step over this image so we don't hit it next time + self.block_index += 1; + + break img; } self.block_index += 1; }; Some(Image { - compressed: &img, + compressed: img, global_palette: self.gif.global_color_table.as_ref(), blocks: &self.gif.blocks[starting_block..self.block_index], }) @@ -143,8 +138,7 @@ impl<'a> Image<'a> { pub fn transparent_index(&self) -> Option<u8> { self.graphic_control() - .map(|gce| gce.transparent_index()) - .flatten() + .and_then(|gce| gce.transparent_index()) } pub fn frame_control(&self) -> Option<FrameControl> { @@ -211,3 +205,137 @@ pub enum FrameControl { Input, InputOrDelay(Duration), } + +#[cfg(test)] +pub mod gif_test { + 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/lib.rs b/gifed/src/lib.rs index 966b46c..cfb124f 100644 --- a/gifed/src/lib.rs +++ b/gifed/src/lib.rs @@ -16,7 +16,7 @@ pub use lzw::LZW; /// Perform the algorithm to get the length of a color table from /// the value of the packed field. The max value here is 256 pub(crate) fn packed_to_color_table_length(packed: u8) -> usize { - 2usize.pow(packed as u32 + 1) + 1 << (packed + 1) } //TODO: Be sure to check that fields in LSD and Img. Desc. that were reserved diff --git a/gifed/src/lzw.rs b/gifed/src/lzw.rs index 9fdfcdd..89b43e5 100644 --- a/gifed/src/lzw.rs +++ b/gifed/src/lzw.rs @@ -1,68 +1,99 @@ use std::collections::HashMap; -pub struct LZW {} +use bitvec::prelude::*; + +pub struct LZW { + minimum_size: u8, + clear_code: u16, + end_of_information_code: u16, + dictionary: HashMap<Vec<u8>, u16>, +} impl LZW { - pub fn encode(minimum_size: u8, indicies: &[u8]) -> Vec<u8> { + pub fn new(minimum_size: u8) -> Self { let mut dictionary: HashMap<Vec<u8>, u16> = HashMap::new(); - let cc = 2u16.pow(minimum_size as u32); - let eoi = cc + 1; + let clear_code = 1 << minimum_size; + let end_of_information_code = clear_code + 1; - println!("mcs {} | cc {}", minimum_size, cc); + println!("mcs {minimum_size} | cc {clear_code}"); // Fill dictionary with self-descriptive values - for value in 0..cc { + for value in 0..clear_code { dictionary.insert(vec![value as u8], value); } - let mut next_code = eoi + 1; - let mut code_size = minimum_size + 1; + Self { + minimum_size, + clear_code, + end_of_information_code, + dictionary, + } + } + + pub fn reset(&mut self) { + *self = Self::new(self.minimum_size) + } + + pub fn decode(&mut self, encoded: &[u8]) -> Vec<u8> { + let mut input = BitStream::new(); + for &byte in encoded { + input.push_bits(8, byte as u16); + } - let mut iter = indicies.into_iter(); let mut out = BitStream::new(); - let mut buffer = vec![*iter.next().unwrap()]; - out.push_bits(code_size, cc); + todo!(); - for &indicie in iter { - buffer.push(indicie); + out.vec() + } + + pub fn encode(&mut self, indices: &[u8]) -> Vec<u8> { + let mut next_code = self.end_of_information_code + 1; + let mut code_size = self.minimum_size + 1; - if !dictionary.contains_key(&buffer) { - buffer.pop(); + let mut iter = indices.iter(); + let mut out = BitStream::new(); + let mut buffer = vec![*iter.next().unwrap()]; - if let Some(&code) = dictionary.get(&buffer) { + out.push_bits(code_size, self.clear_code); + + for &index in iter { + buffer.push(index); + + if !self.dictionary.contains_key(&buffer) { + if let Some(&code) = self.dictionary.get(&buffer[..buffer.len() - 1]) { 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); + // add the vec to the dict + self.dictionary.insert(buffer, 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) { + if next_code - 1 == 1 << code_size { code_size += 1; } - buffer.clear(); - buffer.push(indicie); + buffer = vec![index]; } else { - println!("indicie is: {}", indicie); + println!("index is: {index}"); println!("buffer is: {:?}", buffer); - println!("dictionary: {:?}", dictionary); + println!("dictionary: {:?}", self.dictionary); unreachable!() } } } - if buffer.len() > 0 { - match dictionary.get(&buffer) { + if !buffer.is_empty() { + match self.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!") + unreachable!( + "Codes left in the buffer but the buffer is not a valid dictionary key!" + ) } } } - out.push_bits(code_size, eoi); + out.push_bits(code_size, self.end_of_information_code); out.vec() } @@ -72,70 +103,92 @@ impl LZW { mod lzw_test { use super::*; + fn rand_against_weezl(length: usize) { + let range = rand::distributions::Uniform::from(0..=1); + let indices = rand::Rng::sample_iter(rand::thread_rng(), &range) + .take(length) + .collect::<Vec<_>>(); + let weezl = weezl::encode::Encoder::new(weezl::BitOrder::Lsb, 2) + .encode(&indices) + .unwrap(); + let us = LZW::new(2).encode(&indices); + + assert_eq!(us.len(), weezl.len()); + } + + #[test] + #[ignore] + fn fortyk_against_weezl() { + rand_against_weezl(40_000); + } + + #[test] + #[ignore] + fn thirtyeightk_against_weezl() { + rand_against_weezl(38_000); + } + + #[test] + #[ignore] + fn twentyk_against_weezl_repeated() { + for _ in 0..100 { + rand_against_weezl(20_000) + } + } + #[test] fn encode() { - let indicies = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; + let indices = vec![0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]; let output = vec![0x84, 0x1D, 0x81, 0x7A, 0x50]; - let lzout = LZW::encode(2, &indicies); + let lzout = LZW::new(2).encode(&indices); 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 indices = 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) + .encode(&indices) .unwrap(); - let us = LZW::encode(2, &indicies); + let us = LZW::new(2).encode(&indices); assert_eq!(weezl, us); } } struct BitStream { - formed: Vec<u8>, - current: u8, - index: u8, + formed: BitVec<u8, Lsb0>, } impl BitStream { fn new() -> Self { Self { - formed: vec![], - current: 0, - index: 0, + formed: BitVec::EMPTY, } } 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; - } + for i in 0..count { + self.formed.push((data & (1 << i)) > 0) } } - fn vec(self) -> Vec<u8> { - let mut out = self.formed; - - if self.index != 0 { - out.push(self.current); + fn pop_bits(&mut self, count: u8) -> u16 { + let mut out = 0; + for i in (0..count).filter_map(|_| self.formed.pop()) { + out <<= 1; + let int: u16 = i.into(); + out |= int; } - out } + + fn vec(mut self) -> Vec<u8> { + self.formed.set_uninitialized(false); + self.formed.into_vec() + } } #[cfg(test)] @@ -155,7 +208,7 @@ mod bitstream_test { for byte in &bsvec { print!("{:b} ", byte); } - println!(""); + println!(); assert_eq!(bsvec, vec![0b1001_1111, 0b0000_0001]); } @@ -171,7 +224,7 @@ mod bitstream_test { for byte in &bsvec { print!("{:b} ", byte); } - println!(""); + println!(); assert_eq!(bsvec, vec![0b0000_0011, 0b0001_0000]); } diff --git a/gifed/src/reader/mod.rs b/gifed/src/reader/mod.rs index aed2142..763f34e 100644 --- a/gifed/src/reader/mod.rs +++ b/gifed/src/reader/mod.rs @@ -22,7 +22,7 @@ pub struct Decoder<R: Read> { impl Decoder<BufReader<File>> { pub fn file<P: AsRef<Path>>(path: P) -> Result<Self, DecodeError> { - let file = File::open(path).map_err(|e| DecodeError::IoError(e))?; + let file = File::open(path).map_err(DecodeError::IoError)?; let buffreader = BufReader::new(file); Ok(Decoder::new(buffreader)) } @@ -61,11 +61,8 @@ impl<R: Read> Decoder<R> { let mut decoder = self.read()?; let mut blocks = vec![]; - loop { - match decoder.block()? { - Some(block) => blocks.push(block.block), - None => break, - } + while let Some(block) = decoder.block()? { + blocks.push(block.block) } Ok(Gif { @@ -283,7 +280,7 @@ impl<R: Read> SmartReader<R> { } pub fn read_palette(&mut self, count: usize) -> Result<Palette, DecodeError> { - let mut buf = vec![0; count as usize * 3]; + let mut buf = vec![0; count * 3]; self.read_exact(&mut buf)?; Ok(Palette::try_from(buf.as_slice()).unwrap()) diff --git a/gifprobe/src/main.rs b/gifprobe/src/main.rs index 64cee94..fd6b189 100644 --- a/gifprobe/src/main.rs +++ b/gifprobe/src/main.rs @@ -10,7 +10,7 @@ use gifed::{ use owo_colors::OwoColorize; fn main() { - let file = if let Some(file) = std::env::args().skip(1).next() { + let file = if let Some(file) = std::env::args().nth(1) { file } else { println!("usage: gifprobe file.gif"); @@ -49,7 +49,7 @@ fn main() { ); if colors { - for (idx, clr) in plt.into_iter().enumerate() { + for (idx, clr) in plt.iter().enumerate() { println!( "\t{} {}, {}, {}", idx.color(owo_colors::Rgb(clr.r, clr.g, clr.b)), @@ -217,7 +217,7 @@ fn describe_image(bli: CompressedImage, offset: Range<usize>, expand: bool, colo ); if colors { - for (idx, clr) in plt.into_iter().enumerate() { + for (idx, clr) in plt.iter().enumerate() { println!("\t{idx} {}, {}, {}", clr.r, clr.g, clr.b); } } |