diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | src/block/colortable.rs | 47 | ||||
-rw-r--r-- | src/block/extension/graphiccontrol.rs | 38 | ||||
-rw-r--r-- | src/block/extension/mod.rs | 7 | ||||
-rw-r--r-- | src/block/indexedimage.rs | 18 | ||||
-rw-r--r-- | src/block/mod.rs | 10 | ||||
-rw-r--r-- | src/block/screendescriptor.rs | 10 | ||||
-rw-r--r-- | src/block/version.rs | 1 | ||||
-rw-r--r-- | src/color.rs | 18 | ||||
-rw-r--r-- | src/colorimage.rs | 16 | ||||
-rw-r--r-- | src/gif.rs | 140 | ||||
-rw-r--r-- | src/lib.rs | 27 | ||||
-rw-r--r-- | src/main.rs | 195 | ||||
-rw-r--r-- | src/reader/mod.rs | 21 | ||||
-rw-r--r-- | src/writer/gifbuilder.rs | 44 | ||||
-rw-r--r-- | src/writer/imagebuilder.rs | 92 | ||||
-rw-r--r-- | src/writer/mod.rs | 2 |
19 files changed, 351 insertions, 343 deletions
diff --git a/.gitignore b/.gitignore index 96ef6c0..02f12fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /target Cargo.lock +*.gif +*.txt +*.png \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index ae6962b..e416b18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ criterion = "0.3" rand = "0.7.3" [dependencies] +png = "0.17.1" weezl = "0.1.5" owo-colors = "2.0.0" diff --git a/README.md b/README.md index d48b26e..8a76871 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ always will be, one of the main goals of this crate. ### TODO - [x] Writing GIF87a -- [ ] Writing GIF89a -- [ ] Automatically select the lowest version possible when writing +- [x] Writing GIF89a +- [x] Automatically select the lowest version possible when writing - [ ] Read GIF87a - [ ] Read GIF89a - [ ] Feature to allow using the [weezl][weezl-crates] crate for LZW compression instead of the built-in diff --git a/src/block/colortable.rs b/src/block/colortable.rs index da9458c..d8241f7 100644 --- a/src/block/colortable.rs +++ b/src/block/colortable.rs @@ -1,9 +1,11 @@ pub use crate::Color; +use crate::EncodingError; use std::{ convert::{TryFrom, TryInto}, ops::Deref, }; +#[derive(Clone, Debug)] pub struct ColorTable { table: Vec<Color>, } @@ -29,6 +31,19 @@ impl ColorTable { 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 { @@ -39,12 +54,6 @@ impl Deref for ColorTable { } } -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![]; @@ -80,3 +89,29 @@ impl TryFrom<&[u8]> for ColorTable { } } } + +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/graphiccontrol.rs b/src/block/extension/graphiccontrol.rs index 0d4edd1..830dff4 100644 --- a/src/block/extension/graphiccontrol.rs +++ b/src/block/extension/graphiccontrol.rs @@ -1,4 +1,4 @@ -use std::convert::TryInto; +use std::{convert::TryInto, fmt}; #[derive(Clone, Debug)] pub struct GraphicControl { @@ -21,22 +21,36 @@ impl GraphicControl { transparency_index, }; - ret.disposal_method(disposal_method); + ret.set_disposal_method(disposal_method); ret.user_input(user_input_flag); ret.transparency(transparency_flag); ret } - pub fn disposal_method(&mut self, method: DisposalMethod) { + 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::Clear => self.packed &= 0b111_000_1_1, + 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; @@ -78,9 +92,23 @@ impl From<[u8; 4]> for GraphicControl { } } +#[derive(Clone, Copy, Debug, PartialEq)] pub enum DisposalMethod { - Clear, + 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 index d0e57c6..f328357 100644 --- a/src/block/extension/mod.rs +++ b/src/block/extension/mod.rs @@ -2,6 +2,7 @@ mod application; mod graphiccontrol; pub use graphiccontrol::{DisposalMethod, GraphicControl}; +use owo_colors::colors::xterm::GrandisCaramel; pub use self::application::Application; @@ -41,3 +42,9 @@ impl From<&Extension> for Box<[u8]> { vec.into_boxed_slice() } } + +impl From<GraphicControl> for Extension { + fn from(gce: GraphicControl) -> Self { + Extension::GraphicControl(gce) + } +} diff --git a/src/block/indexedimage.rs b/src/block/indexedimage.rs index 52be3d5..0be066f 100644 --- a/src/block/indexedimage.rs +++ b/src/block/indexedimage.rs @@ -10,6 +10,22 @@ pub struct IndexedImage { } 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![]; @@ -46,7 +62,7 @@ impl IndexedImage { } } -pub struct BlockedImage { +pub struct CompressedImage { pub image_descriptor: ImageDescriptor, pub local_color_table: Option<ColorTable>, pub lzw_minimum_code_size: u8, diff --git a/src/block/mod.rs b/src/block/mod.rs index e3136bf..645c11d 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -7,13 +7,19 @@ mod version; pub use colortable::ColorTable; pub use imagedescriptor::ImageDescriptor; -pub use indexedimage::BlockedImage; +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), - BlockedImage(BlockedImage), Extension(extension::Extension), } + +enum WriteBlock { + ImageBuilder(ImageBuilder), + Block(Block), +} diff --git a/src/block/screendescriptor.rs b/src/block/screendescriptor.rs index ff70896..444b44f 100644 --- a/src/block/screendescriptor.rs +++ b/src/block/screendescriptor.rs @@ -9,6 +9,16 @@ pub struct ScreenDescriptor { } 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; diff --git a/src/block/version.rs b/src/block/version.rs index b785f27..c52439c 100644 --- a/src/block/version.rs +++ b/src/block/version.rs @@ -1,5 +1,6 @@ use std::fmt; +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Version { Gif87a, Gif89a, diff --git a/src/color.rs b/src/color.rs index 764acaf..dd96280 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,4 @@ -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct Color { pub r: u8, pub g: u8, @@ -20,3 +20,19 @@ impl From<[u8; 3]> for Color { } } } + +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 new file mode 100644 index 0000000..71f2bee --- /dev/null +++ b/src/colorimage.rs @@ -0,0 +1,16 @@ +pub struct ColorImage { + width: u16, + height: u16, + data: Vec<Pixel> +} + +impl ColorImage { + pub fn new() { + + } +} + +pub enum Pixel { + Color(Color), + Transparent +} \ No newline at end of file diff --git a/src/gif.rs b/src/gif.rs index 90564fc..837de3a 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -1,4 +1,4 @@ -use crate::block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}; +use crate::{block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}, writer::GifBuilder}; pub struct Gif { pub header: Version, pub screen_descriptor: ScreenDescriptor, @@ -7,6 +7,10 @@ pub struct Gif { } 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![]; @@ -32,7 +36,7 @@ impl Gif { boxed = image.as_boxed_slice(mcs); out.extend_from_slice(&*boxed); } - Block::BlockedImage(_) => unreachable!(), + //Block::BlockedImage(_) => unreachable!(), Block::Extension(ext) => { boxed = ext.into(); out.extend_from_slice(&*boxed); @@ -47,10 +51,24 @@ impl Gif { } } +/*struct FrameIterator { + +} + +impl Iterator for FrameIterator { + type Item = (); + + fn next(&mut self) -> Option<Self::Item> { + todo!() + } +}*/ + #[cfg(test)] pub mod gif { - use super::*; - use crate::block::extension::{DisposalMethod, GraphicControl}; + use std::convert::TryInto; + use std::io::Write; + + use crate::block::extension::DisposalMethod; use crate::writer::{GifBuilder, ImageBuilder}; use crate::Color; @@ -125,18 +143,19 @@ pub mod gif { 0x3B, // Trailer ]; - let actual_out = GifBuilder::new(Version::Gif87a, 4, 4) - .global_color_table(gct.into()) + let actual = GifBuilder::new(4, 4) + .palette(gct.try_into().unwrap()) .image( ImageBuilder::new(4, 4) - .color_table(colortable.into()) + .palette(colortable.try_into().unwrap()) .indicies(indicies.clone()), ) + .unwrap() .image(ImageBuilder::new(4, 4).indicies(indicies)) - .build() - .to_vec(); + .unwrap(); - assert_eq!(actual_out, expected_out); + let bytes = actual.build().to_vec(); + assert_eq!(bytes, expected_out); } #[test] @@ -146,96 +165,35 @@ pub mod gif { 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 - 0x21, - 0xF9, - 0x04, - 0b000_010_0_0, - 0x40, - 0x00, - 0x00, - 0x00, // Graphic Control Extension - 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 + 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(Version::Gif87a, 4, 4) - .global_color_table(gct.into()) + let actual_out = GifBuilder::new(4, 4) + .palette(gct.try_into().unwrap()) .image( ImageBuilder::new(4, 4) - .color_table(colortable.into()) - .indicies(indicies.clone()), + .palette(colortable.try_into().unwrap()) + .indicies(indicies.clone()) + .disposal_method(DisposalMethod::RestoreBackground) + .delay(64), ) - .extension(Extension::GraphicControl(GraphicControl::new( - DisposalMethod::RestoreBackground, - false, - false, - 64, - 0, - ))) + .unwrap() .image(ImageBuilder::new(4, 4).indicies(indicies)) + .unwrap() .build() .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 index 6c0203c..97bab10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,9 @@ pub mod block; pub mod reader; pub mod writer; +use core::fmt; +use std::error::Error; + pub use color::Color; pub use gif::Gif; pub use lzw::LZW; @@ -20,3 +23,27 @@ pub(crate) fn packed_to_color_table_length(packed: u8) -> usize { //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/main.rs b/src/main.rs deleted file mode 100644 index 269470a..0000000 --- a/src/main.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::{fs::File, io::Write}; - -use gifed::{ - block::{ - extension::{Extension, GraphicControl}, - BlockedImage, ColorTable, Version, - }, - reader::GifReader, - writer::{GifBuilder, ImageBuilder}, - Color, -}; -use owo_colors::OwoColorize; - -fn main() { - write_test(); - //return; - let gif = GifReader::file("test.gif"); - //let gif = GifReader::file("/home/gen/Downloads/tas_tillie.gif"); - - println!("Version {}", gif.header.yellow()); - println!( - "Logical Screen Descriptor\n\tDimensions {}x{}", - gif.screen_descriptor.width.yellow(), - gif.screen_descriptor.height.yellow() - ); - - if gif.screen_descriptor.color_table_present() { - println!( - "\tGlobal Color Table Present {}\n\tGlobal Color Table Size {}", - "Yes".green(), - gif.screen_descriptor.color_table_len().green() - ); - } else { - println!( - "\tGlobal Color Table Present {}\n\tGlobal Color Table Size {}", - "No".red(), - gif.screen_descriptor.color_table_len().red() - ); - } - - for block in gif.blocks { - match block { - gifed::block::Block::IndexedImage(_) => todo!(), - gifed::block::Block::BlockedImage(bli) => describe_blocked_image(bli), - gifed::block::Block::Extension(ext) => match ext { - gifed::block::extension::Extension::GraphicControl(gce) => { - println!( - "Graphic Control Extension\n\tDelay Time {}", - format!("{}s", gce.delay_time() as f32 / 100.0).yellow() - ) - } - gifed::block::extension::Extension::Looping(_) => todo!(), - gifed::block::extension::Extension::Comment(cmt) => { - println!("Comment Extension\n\tLength {}", cmt.len()) - } - gifed::block::extension::Extension::Application(app) => { - let auth = app.authentication_code(); - println!("Application Extension\n\tIdentifier {}\n\tAuthentication {:02X} {:02X} {:02X}",app.identifier().yellow(), auth[0].yellow(), auth[1].yellow(), auth[2].yellow()); - } - }, - } - } -} - -fn describe_blocked_image(bli: BlockedImage) { - println!( - "Image\n\tOffset {}x{}\n\tDimensions {}x{}", - bli.image_descriptor.left.yellow(), - bli.image_descriptor.top.yellow(), - bli.image_descriptor.width.yellow(), - bli.image_descriptor.height.yellow(), - ); - - if bli.image_descriptor.color_table_present() { - println!( - "\tLocal Color Table Present {}\n\tLocal Color Table Size {}", - "Yes".green(), - bli.image_descriptor.color_table_size().green() - ); - } else { - println!( - "\tLocal Color Table Present {}\n\tLocal Color Table Size {}", - "No".red(), - bli.image_descriptor.color_table_size().red() - ); - } - - println!( - "\t{} image data sub-blocks totaling {} bytes", - bli.blocks.len().yellow(), - bli.blocks - .iter() - .map(|vec| vec.len()) - .sum::<usize>() - .yellow() - ) -} - -fn write_test() { - const size: u16 = 256; - let gcon = GraphicControl::new( - gifed::block::extension::DisposalMethod::Clear, - false, - false, - 25, - 0, - ); - - let mut gct = ColorTable::new(); - gct.push(Color { - r: 0x44, - g: 0x44, - b: 0x44, - }); - gct.push(Color { - r: 0x88, - g: 0x44, - b: 0xDD, - }); - - println!("{} {}", gct.packed_len(), gct.len()); - - let mut builder = GifBuilder::new(Version::Gif89a, size, size).global_color_table(gct); - - for x in 4..16 { - let mut raw = vec![0; size as usize * size as usize]; - - for i in x * 7..x * 7 + 32 { - for j in x * 7..x * 7 + 32 { - let index = i * size as usize + j; - raw[index as usize] = 1; - } - } - - builder = builder - .extension(Extension::GraphicControl(gcon.clone())) - .image(ImageBuilder::new(size, size).indicies(raw)); - } - - builder = builder.extension(Extension::Looping(0)); - - let vec = builder.build().to_vec(); - File::create("test.gif").unwrap().write_all(&vec).unwrap(); - - let mut text_gif = File::create("test_gif.txt").unwrap(); - for byte in vec { - text_gif - .write_all(&format!("{:02X} ", byte).as_bytes()) - .unwrap(); - } -} - -fn write_static_test() { - const size: u16 = 256; - - let mut gct = ColorTable::new(); - gct.push(Color { - r: 0x44, - g: 0x44, - b: 0x44, - }); - gct.push(Color { - r: 0x55, - g: 0x44, - b: 0x88, - }); - - println!("{} {}", gct.packed_len(), gct.len()); - - let mut builder = GifBuilder::new(Version::Gif87a, size, size).global_color_table(gct); - - let x = 4; - let mut raw = vec![0; size as usize * size as usize]; - - for i in x * 7..x * 7 + 8 { - for j in x * 7..x * 7 + 8 { - let index = i * size as usize + j; - raw[index as usize] = 1; - } - } - - builder = builder.image(ImageBuilder::new(size, size).indicies(raw)); - - //builder = builder.extension(Extension::Looping(0)); - - let vec = builder.build().to_vec(); - File::create("test.gif").unwrap().write_all(&vec).unwrap(); - - let mut text_gif = File::create("test_gif.txt").unwrap(); - for byte in vec { - text_gif - .write_all(&format!("{:02X} ", byte).as_bytes()) - .unwrap(); - } -} diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 606de11..7653216 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -9,9 +9,10 @@ use std::{ use crate::{ block::{ extension::{Application, Extension, GraphicControl}, - Block, BlockedImage, ColorTable, ImageDescriptor, ScreenDescriptor, Version, + Block, ColorTable, CompressedImage, ImageDescriptor, IndexedImage, ScreenDescriptor, + Version, }, - Gif, + color, Gif, }; pub struct GifReader {} @@ -100,8 +101,8 @@ impl GifReader { } } - fn read_extension(mut reader: &mut SmartReader) -> Block { - let mut extension_id = reader.u8().expect("File ended early"); + fn read_extension(reader: &mut SmartReader) -> Block { + let extension_id = reader.u8().expect("File ended early"); match extension_id { 0xF9 => { @@ -149,13 +150,17 @@ impl GifReader { let lzw_csize = reader.u8().expect("Failed to read LZW Minimum Code Size"); - let blocks = reader.take_data_subblocks(); + let compressed_data = reader.take_and_collapse_subblocks(); + println!("c{}", compressed_data.len()); - Block::BlockedImage(BlockedImage { + let mut decompress = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, lzw_csize); + //TODO: remove unwrap + let mut decompressed_data = decompress.decode(&compressed_data).unwrap(); + + Block::IndexedImage(IndexedImage { image_descriptor: descriptor, local_color_table: color_table, - lzw_minimum_code_size: lzw_csize, - blocks, + indicies: decompressed_data, }) } } diff --git a/src/writer/gifbuilder.rs b/src/writer/gifbuilder.rs index d16be56..ec3c304 100644 --- a/src/writer/gifbuilder.rs +++ b/src/writer/gifbuilder.rs @@ -1,6 +1,8 @@ +use std::convert::TryInto; + use crate::block::{extension::Extension, Block, ColorTable, ScreenDescriptor, Version}; use crate::writer::ImageBuilder; -use crate::Gif; +use crate::{EncodingError, Gif}; pub struct GifBuilder { version: Version, @@ -12,9 +14,9 @@ pub struct GifBuilder { } impl GifBuilder { - pub fn new(version: Version, width: u16, height: u16) -> Self { + pub fn new(width: u16, height: u16) -> Self { Self { - version, + version: Version::Gif87a, width, height, background_color_index: 0, @@ -23,30 +25,40 @@ impl GifBuilder { } } - pub fn global_color_table(mut self, table: ColorTable) -> Self { - self.global_color_table = Some(table); - + pub fn palette(mut self, palette: ColorTable) -> Self { + self.global_color_table = Some(palette); self } - pub fn background_color_index(mut self, ind: u8) -> Self { + pub fn background_index(mut self, ind: u8) -> Result<Self, EncodingError> { if self.global_color_table.is_none() { - //TODO: Throw error or let it go by, who knows - panic!("Setting background color index with noGCT!"); + Err(EncodingError::NoColorTable) + } else { + self.background_color_index = ind; + Ok(self) } - - self.background_color_index = ind; - self } - pub fn image(mut self, ib: ImageBuilder) -> Self { - self.blocks.push(Block::IndexedImage(ib.build())); - self + pub fn image(mut self, ib: ImageBuilder) -> Result<Self, EncodingError> { + 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())); + } + + self.blocks.push(Block::IndexedImage(ib.build()?)); + Ok(self) } - pub fn extension(mut self, ext: Extension) -> Self { + /*pub fn extension(mut self, ext: Extension) -> Self { self.blocks.push(Block::Extension(ext)); self + }*/ + + pub fn repeat(&mut self, count: u16) { + self.blocks.push(Block::Extension(Extension::Looping(count))) } pub fn build(self) -> Gif { diff --git a/src/writer/imagebuilder.rs b/src/writer/imagebuilder.rs index d38687e..1486f16 100644 --- a/src/writer/imagebuilder.rs +++ b/src/writer/imagebuilder.rs @@ -1,4 +1,10 @@ -use crate::block::{ColorTable, ImageDescriptor, IndexedImage}; +use crate::{ + block::{ + extension::{DisposalMethod, GraphicControl}, + ColorTable, ImageDescriptor, IndexedImage, Version, + }, + EncodingError, +}; pub struct ImageBuilder { left_offset: u16, @@ -6,6 +12,11 @@ pub struct ImageBuilder { width: u16, height: u16, color_table: Option<ColorTable>, + + delay: u16, + disposal_method: DisposalMethod, + transparent_index: Option<u8>, + indicies: Vec<u8>, } @@ -17,38 +28,89 @@ impl ImageBuilder { width, height, color_table: None, + delay: 0, + disposal_method: DisposalMethod::NoAction, + transparent_index: None, indicies: vec![], } } - pub fn offsets(mut self, left_offset: u16, top_offset: u16) -> Self { - self.left_offset = left_offset; - self.top_offset = top_offset; + pub fn offset(mut self, left: u16, top: u16) -> Self { + self.left_offset = left; + self.top_offset = top; self } - pub fn left_offset(mut self, offset: u16) -> Self { - self.left_offset = offset; + pub fn palette(mut self, table: ColorTable) -> Self { + self.color_table = Some(table); self } - pub fn top_offset(mut self, offset: u16) -> Self { - self.top_offset = offset; + /// 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 color_table(mut self, table: ColorTable) -> Self { - self.color_table = Some(table); + 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 indicies(mut self, vec: Vec<u8>) -> Self { - self.indicies = vec; + 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: Vec<u8>) -> Self { + self.indicies = indicies; self } - pub fn build(self) -> IndexedImage { + 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, @@ -62,10 +124,10 @@ impl ImageBuilder { imgdesc.set_color_table_size(lct.packed_len()); } - IndexedImage { + 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 index b801a3a..88311fc 100644 --- a/src/writer/mod.rs +++ b/src/writer/mod.rs @@ -2,4 +2,4 @@ mod gifbuilder; mod imagebuilder; pub use gifbuilder::GifBuilder; -pub use imagebuilder::ImageBuilder; \ No newline at end of file +pub use imagebuilder::ImageBuilder; |