about summary refs log tree commit diff
path: root/gifcheck
diff options
context:
space:
mode:
Diffstat (limited to 'gifcheck')
-rw-r--r--gifcheck/Cargo.toml1
-rw-r--r--gifcheck/src/fix.rs103
-rw-r--r--gifcheck/src/main.rs103
3 files changed, 206 insertions, 1 deletions
diff --git a/gifcheck/Cargo.toml b/gifcheck/Cargo.toml
index 8ae0b40..5c63df7 100644
--- a/gifcheck/Cargo.toml
+++ b/gifcheck/Cargo.toml
@@ -7,3 +7,4 @@ license = "ISC"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+gifed = { path = "../gifed" }
diff --git a/gifcheck/src/fix.rs b/gifcheck/src/fix.rs
new file mode 100644
index 0000000..95f7ef2
--- /dev/null
+++ b/gifcheck/src/fix.rs
@@ -0,0 +1,103 @@
+use gifed::{
+	block::{Block, Palette},
+	Color, Gif,
+};
+
+use crate::PaletteReport;
+
+pub fn palette_errors(gif: &Gif, report: PaletteReport) -> Option<Gif> {
+	if report.local_matching_indicies {
+		let mut new = gif.clone();
+
+		for block in new.blocks.iter_mut() {
+			if let Block::CompressedImage(comp) = block {
+				comp.image_descriptor.packed.set_color_table(false);
+				comp.image_descriptor.packed.set_color_table_size(0);
+
+				if let Some(plt) = comp.local_color_table.take() {
+					new.global_color_table.get_or_insert(plt);
+				}
+			}
+		}
+
+		Some(new)
+	} else {
+		None
+	}
+}
+
+pub fn images_match_exactly(gifa: &Gif, gifb: &Gif) -> bool {
+	let mut a_buf = vec![0; gifa.width() * gifa.height() * 4];
+	let mut b_buf = vec![0; gifb.width() * gifb.height() * 4];
+
+	for (a, b) in gifa.images().zip(gifb.images()) {
+		if a.width() != b.width() || a.height() != b.height() {
+			return false;
+		}
+
+		if a.left() != b.left() || a.top() != b.top() {
+			return false;
+		}
+
+		let a_decomp = a.decompess();
+		let b_decomp = b.decompess();
+
+		let a_size = deindex(
+			&a_decomp.indicies,
+			a.palette(),
+			a.transparent_index(),
+			&mut a_buf,
+		);
+
+		let b_size = deindex(
+			&b_decomp.indicies,
+			b.palette(),
+			b.transparent_index(),
+			&mut b_buf,
+		);
+
+		match (a_size, b_size) {
+			(None, _) | (_, None) => return false,
+			(Some(asize), Some(bsize)) => {
+				if asize != bsize {
+					return false;
+				}
+
+				if a_buf[..asize] != b_buf[..bsize] {
+					return false;
+				}
+			}
+		}
+	}
+
+	true
+}
+
+fn deindex(indicies: &[u8], plt: &Palette, trns: Option<u8>, buffer: &mut [u8]) -> Option<usize> {
+	let mut rgba = |idx: usize, clr: Option<Color>| match clr {
+		None => {
+			buffer[idx] = 0;
+			buffer[idx + 1] = 0;
+			buffer[idx + 2] = 0;
+			buffer[idx + 3] = 0;
+		}
+		Some(clr) => {
+			buffer[idx] = clr.r;
+			buffer[idx + 1] = clr.g;
+			buffer[idx + 2] = clr.b;
+			buffer[idx + 3] = 255;
+		}
+	};
+
+	for (idx, color_idx) in indicies.iter().enumerate() {
+		match (trns, plt.get(*color_idx)) {
+			(Some(trns_idx), _) if trns_idx == *color_idx => rgba(idx * 4, None),
+			(_, Some(color)) => rgba(idx * 4, Some(color)),
+			(Some(_) | None, None) => {
+				return None;
+			}
+		}
+	}
+
+	Some(indicies.len() * 4)
+}
diff --git a/gifcheck/src/main.rs b/gifcheck/src/main.rs
index a30eb95..8eec4c1 100644
--- a/gifcheck/src/main.rs
+++ b/gifcheck/src/main.rs
@@ -1,3 +1,104 @@
+use std::{ops::Deref, path::PathBuf};
+
+use gifed::{reader::Decoder, Gif};
+
+mod fix;
+
 fn main() {
-	println!("Hello, world!");
+	let file = std::env::args().nth(1).unwrap();
+	let arg = std::env::args().nth(2).map(|cmd| cmd.to_lowercase());
+
+	let gif = Decoder::file(&file).unwrap().read_all().unwrap();
+
+	let plt_report = same_palette(&gif);
+	match plt_report {
+		PaletteReport {
+			has_local: true,
+			local_redundant: true,
+			local_matching_indicies,
+		} => {
+			if local_matching_indicies {
+				println!("!!! LOCPLT_NORE. This could've been a global palette");
+			} else {
+				println!("!!  LOCPLT. This gif can be reindexed and have a global palette");
+			}
+		}
+		PaletteReport {
+			has_local: true,
+			local_redundant: false,
+			..
+		} => {
+			println!("    gif has local palettes and they differ");
+		}
+		PaletteReport {
+			has_local: false, ..
+		} => {
+			println!("    gif only has a global palette");
+		}
+	}
+
+	if arg.as_deref() == Some("fix") {
+		if let Some(fix_gif) = fix::palette_errors(&gif, plt_report) {
+			if !fix::images_match_exactly(&gif, &fix_gif) {
+				panic!("fixed images did not exactly match, this is a hard error")
+			}
+
+			println!("--- fixing, writing!");
+			let mut path = PathBuf::from(file);
+			path.set_file_name(format!(
+				"{}_fix",
+				path.file_stem().unwrap().to_string_lossy()
+			));
+
+			fix_gif.save(path).unwrap();
+		}
+	}
+}
+
+pub struct PaletteReport {
+	// Does the gif even contain local color tables?
+	has_local: bool,
+	// ... do those color tables always contain the same colors?
+	local_redundant: bool,
+	// ... and do those colors all have matching inidices, making it possible
+	// to simply set the global palette and remove the locals?
+	local_matching_indicies: bool,
+}
+
+fn same_palette(gif: &Gif) -> PaletteReport {
+	let mut palette = gif.global_color_table.as_ref();
+	let mut report = PaletteReport {
+		has_local: false,
+		local_redundant: true,
+		local_matching_indicies: true,
+	};
+
+	for img in gif.images() {
+		if let Some(local_palette) = img.compressed.palette() {
+			report.has_local = true;
+
+			match palette {
+				None => palette = Some(local_palette),
+				Some(known_palette) => {
+					if !local_palette.eq(known_palette) {
+						// Are the palletes equal, even?
+						report.local_redundant = false;
+						report.local_matching_indicies = false;
+						return report;
+					} else if report.local_matching_indicies {
+						// it's matching, but are the indicies the same?
+						for known_color in known_palette.deref() {
+							for local_color in local_palette.deref() {
+								if known_color != local_color {
+									report.local_matching_indicies = false;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	report
 }