diff options
author | gennyble <gen@nyble.dev> | 2023-11-21 16:07:12 -0600 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2023-11-21 16:07:12 -0600 |
commit | 077e8385c9bb4bb0ac5dfe2279903d4dab11d063 (patch) | |
tree | cf06df720eacab4dc327cd736916522e5aaab5f8 | |
parent | 56418912c5308d15215c38cd29836d8912783a8f (diff) | |
download | gifed-077e8385c9bb4bb0ac5dfe2279903d4dab11d063.tar.gz gifed-077e8385c9bb4bb0ac5dfe2279903d4dab11d063.zip |
remove gifbuilder and work towards improving api
-rw-r--r-- | gifed/src/gif.rs | 200 | ||||
-rw-r--r-- | gifed/src/reader/mod.rs | 6 | ||||
-rw-r--r-- | gifed/src/writer/gifbuilder.rs | 137 | ||||
-rw-r--r-- | gifed/src/writer/mod.rs | 33 |
4 files changed, 71 insertions, 305 deletions
diff --git a/gifed/src/gif.rs b/gifed/src/gif.rs index 46807a9..81aea28 100644 --- a/gifed/src/gif.rs +++ b/gifed/src/gif.rs @@ -1,40 +1,50 @@ use std::{fs::File, io::Write, path::Path, time::Duration}; -use crate::{ - block::{ - encode_block, - extension::{DisposalMethod, GraphicControl}, - Block, CompressedImage, IndexedImage, Palette, ScreenDescriptor, Version, - }, - writer::GifBuilder, +use crate::block::{ + encode_block, + extension::{DisposalMethod, GraphicControl}, + Block, CompressedImage, IndexedImage, Palette, ScreenDescriptor, Version, }; #[derive(Clone, Debug)] pub struct Gif { - pub header: Version, - pub screen_descriptor: ScreenDescriptor, - pub global_color_table: Option<Palette>, + /// Usually [Version::Gif89a], but might be [Version::Gif87a] for very + /// simple images. + pub version: Version, + pub descriptor: ScreenDescriptor, + pub palette: Option<Palette>, pub blocks: Vec<Block>, // Trailer at the end of this struct is 0x3B // } impl Gif { - pub fn builder(width: u16, height: u16) -> GifBuilder { - GifBuilder::new(width, height) + pub fn set_width(&mut self, width: u16) { + self.descriptor.width = width; } - pub fn width(&self) -> usize { - self.screen_descriptor.width as usize + pub fn width(&self) -> u16 { + self.descriptor.width + } + + pub fn set_height(&mut self, height: u16) { + self.descriptor.height = height; + } + + pub fn height(&self) -> u16 { + self.descriptor.height } - pub fn height(&self) -> usize { - self.screen_descriptor.height as usize + pub fn set_background_color(&mut self, idx: u8) { + self.descriptor.background_color_index = idx; } 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) + // vii) Background Color Index - Index into the Global Color Table for + // the Background Color. The Background Color is the color used for + // those pixels on the screen that are not covered by an image. If the + // Global Color Table Flag is set to (zero), this field should be zero + // and should be ignored. + if self.descriptor.has_color_table() { + Some(self.descriptor.background_color_index) } else { None } @@ -43,10 +53,10 @@ 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.screen_descriptor.as_bytes()); + out.extend_from_slice(self.version.as_bytes()); + out.extend_from_slice(&self.descriptor.as_bytes()); - if let Some(gct) = &self.global_color_table { + if let Some(gct) = &self.palette { out.extend_from_slice(&gct.as_bytes()); } @@ -64,6 +74,7 @@ impl Gif { File::create(path.as_ref())?.write_all(&self.as_bytes()) } + /// An iterator over the discrete images in the gif. pub fn images(&self) -> ImageIterator<'_> { ImageIterator { gif: self, @@ -97,7 +108,7 @@ impl<'a> Iterator for ImageIterator<'a> { Some(Image { compressed: img, - global_palette: self.gif.global_color_table.as_ref(), + global_palette: self.gif.palette.as_ref(), blocks: &self.gif.blocks[starting_block..self.block_index], }) } @@ -169,11 +180,14 @@ impl<'a> Image<'a> { if let Some(plt) = self.compressed.local_color_table.as_ref() { plt } else { - //FIXME: Maybe don't panic here + //FIXME: Maybe don't panic here. + // images can lack a palette entirely. in that case it's up to the + // decoder to pick one. self.global_palette.unwrap() } } + /// Make a tRNS block for PNG files. pub fn png_trns(&self) -> Option<Vec<u8>> { let palette = self.palette(); if let Some(trans_idx) = self.transparent_index() { @@ -195,7 +209,7 @@ impl<'a> Image<'a> { /// Clones the CompressedImage and decompresses it. pub fn decompess(&self) -> IndexedImage { - //FIXME: unwrap + //FIXME: remove unwrap self.compressed.clone().decompress().unwrap() } } @@ -205,137 +219,3 @@ 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/reader/mod.rs b/gifed/src/reader/mod.rs index 763f34e..672b4ab 100644 --- a/gifed/src/reader/mod.rs +++ b/gifed/src/reader/mod.rs @@ -66,9 +66,9 @@ impl<R: Read> Decoder<R> { } Ok(Gif { - header: decoder.version, - screen_descriptor: decoder.screen_descriptor, - global_color_table: decoder.palette, + version: decoder.version, + descriptor: decoder.screen_descriptor, + palette: decoder.palette, blocks, }) } diff --git a/gifed/src/writer/gifbuilder.rs b/gifed/src/writer/gifbuilder.rs deleted file mode 100644 index 3d2dc23..0000000 --- a/gifed/src/writer/gifbuilder.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::{ - block::{ - packed::ScreenPacked, Block, CompressedImage, IndexedImage, LoopCount, Palette, - ScreenDescriptor, Version, - }, - EncodeError, Gif, -}; - -use super::imagebuilder::BuiltImage; - -// We want to be able to gold [IndexedImage] as well as [CompressedImage], -// but [Block] does not allow that, so -enum BuildBlock { - Indexed(IndexedImage), - Block(Block), -} - -pub struct GifBuilder { - version: Version, - width: u16, - height: u16, - background_color_index: u8, - global_color_table: Option<Palette>, - blocks: Vec<BuildBlock>, -} - -impl GifBuilder { - pub fn new(width: u16, height: u16) -> Self { - Self { - version: Version::Gif87a, - width, - height, - background_color_index: 0, - global_color_table: None, - blocks: vec![], - } - } - - pub fn palette(mut self, palette: Palette) -> Self { - self.global_color_table = Some(palette); - self - } - - pub fn background_index(mut self, ind: u8) -> Self { - self.background_color_index = ind; - self - } - - pub fn block(mut self, block: Block) -> Self { - self.blocks.push(BuildBlock::Block(block)); - self - } - - pub fn repeat(mut self, count: LoopCount) -> Self { - self.blocks - .push(BuildBlock::Block(Block::LoopingExtension(count))); - self - } - - pub fn image<I: Into<EncodeImage>>(mut self, img: I) -> Self { - match img.into() { - EncodeImage::CompressedImage(ci) => self - .blocks - .push(BuildBlock::Block(Block::CompressedImage(ci))), - EncodeImage::IndexedImage(ii) => self.blocks.push(BuildBlock::Indexed(ii)), - EncodeImage::BuiltImage(BuiltImage { image, gce }) => { - if let Some(gce) = gce { - self.version = Version::Gif89a; - - self.blocks - .push(BuildBlock::Block(Block::GraphicControlExtension(gce))); - } - - self.blocks.push(BuildBlock::Indexed(image)); - } - } - - self - } - - pub fn build(self) -> Result<Gif, EncodeError> { - let mut screen_descriptor = ScreenDescriptor { - width: self.width, - height: self.height, - packed: ScreenPacked { raw: 0 }, // Set later - background_color_index: self.background_color_index, - pixel_aspect_ratio: 0, //TODO - }; - - screen_descriptor.set_color_table_metadata(self.global_color_table.as_ref()); - - let mut gif = Gif { - header: self.version, - screen_descriptor, - global_color_table: self.global_color_table, - blocks: vec![], - }; - - let lzw_gct_size = gif.global_color_table.as_ref().map(|ct| ct.lzw_code_size()); - - for block in self.blocks { - match block { - BuildBlock::Indexed(indexed) => { - let compressed = indexed.compress(lzw_gct_size)?; - gif.blocks.push(Block::CompressedImage(compressed)); - } - BuildBlock::Block(block) => gif.blocks.push(block), - } - } - - Ok(gif) - } -} - -pub enum EncodeImage { - CompressedImage(CompressedImage), - IndexedImage(IndexedImage), - BuiltImage(BuiltImage), -} - -impl From<CompressedImage> for EncodeImage { - fn from(ci: CompressedImage) -> Self { - EncodeImage::CompressedImage(ci) - } -} - -impl From<IndexedImage> for EncodeImage { - fn from(ii: IndexedImage) -> Self { - EncodeImage::IndexedImage(ii) - } -} - -impl From<BuiltImage> for EncodeImage { - fn from(bi: BuiltImage) -> Self { - EncodeImage::BuiltImage(bi) - } -} diff --git a/gifed/src/writer/mod.rs b/gifed/src/writer/mod.rs index c2e8382..e115ce8 100644 --- a/gifed/src/writer/mod.rs +++ b/gifed/src/writer/mod.rs @@ -1,14 +1,13 @@ -mod gifbuilder; mod imagebuilder; use std::{error::Error, fmt, io::Write}; -pub use gifbuilder::GifBuilder; pub use imagebuilder::{BuiltImage, ImageBuilder}; -use crate::block::{encode_block, Block, LoopCount, Palette, ScreenDescriptor, Version}; - -use self::gifbuilder::EncodeImage; +use crate::block::{ + encode_block, Block, CompressedImage, IndexedImage, LoopCount, Palette, ScreenDescriptor, + Version, +}; pub struct Writer<W: Write> { writer: W, @@ -125,3 +124,27 @@ impl From<std::io::Error> for EncodeError { EncodeError::IoError { error } } } + +pub enum EncodeImage { + CompressedImage(CompressedImage), + IndexedImage(IndexedImage), + BuiltImage(BuiltImage), +} + +impl From<CompressedImage> for EncodeImage { + fn from(ci: CompressedImage) -> Self { + EncodeImage::CompressedImage(ci) + } +} + +impl From<IndexedImage> for EncodeImage { + fn from(ii: IndexedImage) -> Self { + EncodeImage::IndexedImage(ii) + } +} + +impl From<BuiltImage> for EncodeImage { + fn from(bi: BuiltImage) -> Self { + EncodeImage::BuiltImage(bi) + } +} |