diff options
author | Genny <gen@nyble.dev> | 2022-11-25 18:40:52 -0600 |
---|---|---|
committer | Genny <gen@nyble.dev> | 2022-11-25 18:40:52 -0600 |
commit | 080f6297f28a9800186376742827de60d17e6160 (patch) | |
tree | 8a5030ba3a8949ee030926451c9c59c040b7f565 | |
parent | d19e7b983ec034004cb27ab647634474ada10a18 (diff) | |
download | gifed-080f6297f28a9800186376742827de60d17e6160.tar.gz gifed-080f6297f28a9800186376742827de60d17e6160.zip |
gifed: streaming writer
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | gifed/examples/streaming_write.rs | 39 | ||||
-rw-r--r-- | gifed/examples/write.rs | 20 | ||||
-rw-r--r-- | gifed/src/block/screendescriptor.rs | 12 | ||||
-rw-r--r-- | gifed/src/lib.rs | 27 | ||||
-rw-r--r-- | gifed/src/writer/gifbuilder.rs | 4 | ||||
-rw-r--r-- | gifed/src/writer/mod.rs | 120 |
7 files changed, 185 insertions, 42 deletions
diff --git a/README.md b/README.md index c0b631e..f2c625f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This crate is still pretty rough. I hope to make it feature complete and intuiti - [ ] Reader - [x] Streaming Reader - [x] Writer -- [ ] Streaming Writer +- [x] Streaming Writer - [ ] Feature to allow using the [weezl][weezl-crates] crate for LZW compression instead of the built-in - [ ] Feature to allow using the [rgb][rgb-crates] crate for the color type. - [ ] Well written and easy to understand docs! `bitvec` quality, but who can match that? @@ -38,6 +38,9 @@ These are part of the 89a spec, but are kept separate as they're not "core" to t [gif89a]: https://www.w3.org/Graphics/GIF/spec-gif89a.txt [netscape]: http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension +## gif-frames +GIF Frames helps you manage the weird disposal methods that the Graphic Control Extension introduced. Also provides Indexed -> Rgb/Rgba conversion. + ## gifprobe Similar to FFMPEG's ffprobe, gifprobe will print details of a gif to stdout. diff --git a/gifed/examples/streaming_write.rs b/gifed/examples/streaming_write.rs new file mode 100644 index 0000000..7fab196 --- /dev/null +++ b/gifed/examples/streaming_write.rs @@ -0,0 +1,39 @@ +use std::fs::File; + +use gifed::{ + block::{LoopCount, Palette}, + writer::{ImageBuilder, Writer}, + Color, EncodeError, Gif, +}; + +fn main() -> Result<(), EncodeError> { + let gif_path = match std::env::args().nth(1) { + None => { + eprintln!("Expected a path to output the gif to"); + std::process::exit(-1); + } + Some(path) => path, + }; + + let mut palette = Palette::new(); + + // Fill the palette with every gray + for gray in 0..=255 { + palette.push(Color::new(gray, gray, gray)); + } + + let mut image = vec![0; 128 * 128]; + + // Create a file to write the gif to. We can try here, with the ?, because + // EncodeError has a From<std::io::Error> impl + let file = File::create(gif_path)?; + let mut writer = Writer::new(file, 128, 128, Some(palette))?; + for idx in 0..=255 { + image.fill(idx); + + writer.image(ImageBuilder::new(128, 128).delay(3).build(image.clone())?)?; + } + + writer.repeat(LoopCount::Forever)?; + writer.done() +} diff --git a/gifed/examples/write.rs b/gifed/examples/write.rs index ff8809f..39e28d6 100644 --- a/gifed/examples/write.rs +++ b/gifed/examples/write.rs @@ -1,10 +1,10 @@ use gifed::{ block::{LoopCount, Palette}, writer::ImageBuilder, - Color, Gif, + Color, EncodeError, Gif, }; -fn main() { +fn main() -> Result<(), EncodeError> { let gif_path = match std::env::args().nth(1) { None => { eprintln!("Expected a path to output the gif to"); @@ -26,18 +26,10 @@ fn main() { for idx in 0..=255 { image.fill(idx); - builder = builder.image( - ImageBuilder::new(128, 128) - .delay(3) - .build(image.clone()) - .unwrap(), - ); + builder = builder.image(ImageBuilder::new(128, 128).delay(3).build(image.clone())?); } - builder - .repeat(LoopCount::Forever) - .build() - .unwrap() - .save(gif_path) - .unwrap(); + builder.repeat(LoopCount::Forever).build()?.save(gif_path)?; + + Ok(()) } diff --git a/gifed/src/block/screendescriptor.rs b/gifed/src/block/screendescriptor.rs index aaeea53..d44ca2f 100644 --- a/gifed/src/block/screendescriptor.rs +++ b/gifed/src/block/screendescriptor.rs @@ -43,6 +43,18 @@ impl ScreenDescriptor { crate::packed_to_color_table_length(self.packed.color_table_size()) } + /// Returns the background color index. Defaults to zero. + pub fn background_color(&self) -> u8 { + self.background_color_index + } + + /// Set the color used to fill the space in the gif's canvas that no image + /// has occupied yet. If there is no palette associated with this + /// Screen Descriptor, this value should be ignored by the decoder. + pub fn set_background_color(&mut self, index: u8) { + self.background_color_index = index; + } + pub fn as_bytes(&self) -> Vec<u8> { let mut vec = vec![]; vec.extend_from_slice(&self.width.to_le_bytes()); diff --git a/gifed/src/lib.rs b/gifed/src/lib.rs index f2dfba5..966b46c 100644 --- a/gifed/src/lib.rs +++ b/gifed/src/lib.rs @@ -6,8 +6,8 @@ pub mod block; pub mod reader; pub mod writer; -use core::fmt; -use std::error::Error; +pub use reader::DecodeError; +pub use writer::EncodeError; pub use color::Color; pub use gif::{Gif, Image}; @@ -23,26 +23,3 @@ 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 EncodeError { - TooManyColors, - IndicieSizeMismatch { expected: usize, got: usize }, - InvalidCodeSize { lzw_code_size: u8 }, -} - -impl fmt::Display for EncodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TooManyColors => write!(f, "A palette is limited to 256 colors"), - Self::IndicieSizeMismatch { expected, got } => { - write!(f, "Expected to have {expected} indicies but got {got}") - } - Self::InvalidCodeSize { lzw_code_size } => { - write!(f, "InvalidCodeSize => {lzw_code_size}") - } - } - } -} - -impl Error for EncodeError {} diff --git a/gifed/src/writer/gifbuilder.rs b/gifed/src/writer/gifbuilder.rs index 1d3ff1a..2aaf51f 100644 --- a/gifed/src/writer/gifbuilder.rs +++ b/gifed/src/writer/gifbuilder.rs @@ -66,14 +66,14 @@ impl GifBuilder { .push(BuildBlock::Block(Block::CompressedImage(ci))), EncodeImage::IndexedImage(ii) => self.blocks.push(BuildBlock::Indexed(ii)), EncodeImage::BuiltImage(BuiltImage { image, gce }) => { - self.blocks.push(BuildBlock::Indexed(image)); - if let Some(gce) = gce { self.version = Version::Gif89a; self.blocks .push(BuildBlock::Block(Block::GraphicControlExtension(gce))); } + + self.blocks.push(BuildBlock::Indexed(image)); } } diff --git a/gifed/src/writer/mod.rs b/gifed/src/writer/mod.rs index 88311fc..ad380a8 100644 --- a/gifed/src/writer/mod.rs +++ b/gifed/src/writer/mod.rs @@ -1,5 +1,125 @@ mod gifbuilder; mod imagebuilder; +use std::{error::Error, fmt, io::Write}; + pub use gifbuilder::GifBuilder; pub use imagebuilder::ImageBuilder; + +use crate::block::{encode_block, Block, LoopCount, Palette, ScreenDescriptor, Version}; + +use self::gifbuilder::EncodeImage; + +pub struct Writer<W: Write> { + writer: W, + global_palette: Option<Palette>, +} + +impl<W: Write> Writer<W> { + /// Write the required bits of a GIF. The version written to the stream is + /// [Version::Gif89a]. If for some reason you need [Version::Gif87a] I + /// recommend you use [Writer::from_parts]. + pub fn new( + writer: W, + width: u16, + height: u16, + global_palette: Option<Palette>, + ) -> Result<Self, EncodeError> { + let screen_descriptor = ScreenDescriptor::new(width, height); + Self::from_parts(writer, Version::Gif89a, screen_descriptor, global_palette) + } + + //FIXME: gen- This name sucks + /// Create a new [Writer] from the provided required blocks. + pub fn from_parts( + writer: W, + version: Version, + screen_descriptor: ScreenDescriptor, + global_palette: Option<Palette>, + ) -> Result<Self, EncodeError> { + let mut this = Self { + writer, + global_palette, + }; + this.write_all(version.as_bytes())?; + this.write_all(&screen_descriptor.as_bytes())?; + + if let Some(palette) = this.global_palette.as_ref() { + this.write_all(&palette.as_bytes())?; + } + + Ok(this) + } + + fn write_all(&mut self, buf: &[u8]) -> Result<(), EncodeError> { + self.writer + .write_all(buf) + .map_err(|error| EncodeError::IoError { error }) + } + + pub fn block(&mut self, block: Block) -> Result<(), EncodeError> { + self.write_all(&encode_block(&block)) + } + + pub fn repeat(&mut self, count: LoopCount) -> Result<(), EncodeError> { + self.write_all(&encode_block(&Block::LoopingExtension(count))) + } + + pub fn image<I: Into<EncodeImage>>(&mut self, image: I) -> Result<(), EncodeError> { + match image.into() { + EncodeImage::CompressedImage(compressed) => self.write_all(&compressed.as_bytes()), + EncodeImage::IndexedImage(indexed) => { + let lzw_code_size = self.global_palette.as_ref().map(|p| p.lzw_code_size()); + + let compressed = indexed.compress(lzw_code_size)?; + self.write_all(&compressed.as_bytes()) + } + EncodeImage::BuiltImage(built) => { + if let Some(gce) = built.gce { + self.write_all(&encode_block(&Block::GraphicControlExtension(gce)))?; + } + + let lzw_code_size = self.global_palette.as_ref().map(|p| p.lzw_code_size()); + + let compressed = built.image.compress(lzw_code_size)?; + self.write_all(&compressed.as_bytes()) + } + } + } + + pub fn done(mut self) -> Result<(), EncodeError> { + self.write_all(&[0x3B]) + } +} + +#[derive(Debug)] +pub enum EncodeError { + IoError { error: std::io::Error }, + TooManyColors, + IndicieSizeMismatch { expected: usize, got: usize }, + InvalidCodeSize { lzw_code_size: u8 }, +} + +impl Error for EncodeError {} +impl fmt::Display for EncodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::IoError { error } => { + write!(f, "{error}") + } + Self::TooManyColors => write!(f, "A palette is limited to 256 colors"), + Self::IndicieSizeMismatch { expected, got } => { + write!(f, "Expected to have {expected} indicies but got {got}") + } + Self::InvalidCodeSize { lzw_code_size } => { + write!(f, "InvalidCodeSize => {lzw_code_size}") + } + } + } +} + +impl From<std::io::Error> for EncodeError { + fn from(error: std::io::Error) -> Self { + EncodeError::IoError { error } + } +} |