about summary refs log tree commit diff
path: root/gifed/src
diff options
context:
space:
mode:
authorGenny <gen@nyble.dev>2022-11-25 18:40:52 -0600
committerGenny <gen@nyble.dev>2022-11-25 18:40:52 -0600
commit080f6297f28a9800186376742827de60d17e6160 (patch)
tree8a5030ba3a8949ee030926451c9c59c040b7f565 /gifed/src
parentd19e7b983ec034004cb27ab647634474ada10a18 (diff)
downloadgifed-080f6297f28a9800186376742827de60d17e6160.tar.gz
gifed-080f6297f28a9800186376742827de60d17e6160.zip
gifed: streaming writer
Diffstat (limited to 'gifed/src')
-rw-r--r--gifed/src/block/screendescriptor.rs12
-rw-r--r--gifed/src/lib.rs27
-rw-r--r--gifed/src/writer/gifbuilder.rs4
-rw-r--r--gifed/src/writer/mod.rs120
4 files changed, 136 insertions, 27 deletions
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 }
+	}
+}