about summary refs log tree commit diff
path: root/squash/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'squash/src/main.rs')
-rw-r--r--squash/src/main.rs163
1 files changed, 14 insertions, 149 deletions
diff --git a/squash/src/main.rs b/squash/src/main.rs
index 80c7cb0..d5cd2d8 100644
--- a/squash/src/main.rs
+++ b/squash/src/main.rs
@@ -1,166 +1,31 @@
-use std::{fs::File, io::BufWriter};
-
-use anyhow::{anyhow, bail};
-use camino::{Utf8Path, Utf8PathBuf};
 use colorsquash::Squasher;
-use gifed::writer::{GifBuilder, ImageBuilder};
-use png::{ColorType, Decoder, Encoder};
-use zune_jpeg::{zune_core::colorspace::ColorSpace, JpegDecoder};
-
-fn main() -> Result<(), anyhow::Error> {
-	// I should use clap or at least getopt, but this is fine. It's 20LOC.
-	let usage = || -> ! {
-		println!("usage: squash <color count> <input> <output>");
-		std::process::exit(0);
-	};
-	let mut argv = std::env::args().skip(1);
 
-	let color_count: u8 = if let Some(Ok(count)) = argv.next().map(|r| r.parse::<usize>()) {
-		if count > 256 {
-			eprintln!("max colour count must be 256 or below");
-			std::process::exit(1);
-		} else {
-			(count - 1) as u8
-		}
-	} else {
-		usage()
-	};
+use crate::cli::{InType, OutType};
 
-	let input_path: Utf8PathBuf = if let Some(path) = argv.next() {
-		path.into()
-	} else {
-		usage();
-	};
+mod cli;
+mod image;
 
-	let output_path: Utf8PathBuf = if let Some(path) = argv.next() {
-		path.into()
-	} else {
-		usage();
-	};
+fn main() -> Result<(), anyhow::Error> {
+	//gen: I should use clap or at least getopt, but this is fine.
+	let cli = cli::get();
 
-	let mut image = match input_path.extension() {
-		None => {
-			eprintln!("can't determine input filetype!\nSupported input types: PNG, JPG");
-			std::process::exit(1);
-		}
-		Some("png") => get_png(input_path)?,
-		Some("jpg") | Some("jpeg") => get_jpg(input_path)?,
-		Some(ext) => {
-			eprintln!("unknown filetype '{ext}'!\nSupported input types: PNG, JPG");
-			std::process::exit(1);
-		}
+	let mut image = match cli.in_type {
+		InType::Png => image::get_png(cli.input)?,
+		InType::Jpeg => image::get_jpg(cli.input)?,
 	};
 
-	let mut squasher = Squasher::new(color_count, &image.data);
+	let mut squasher = Squasher::new(cli.color_count, &image.data);
 	let size = squasher.map_over(&mut image.data);
 	image.data.resize(size, 0);
 
 	println!(
 		"selected {} colours of max {}",
 		squasher.palette().len(),
-		color_count
+		cli.color_count
 	);
 
-	match output_path.extension() {
-		None => {
-			eprintln!("can't determine output filetype! defaulting to png");
-			save_png(image, squasher, output_path)
-		}
-		Some("png") => save_png(image, squasher, output_path),
-		Some("gif") => save_gif(image, squasher, output_path),
-		Some(ext) => {
-			eprintln!("unknown filetype '{ext}'!\nSupport output types are: GIF, PNG");
-			std::process::exit(1);
-		}
+	match cli.out_type {
+		OutType::Png => image::save_png(image, squasher, cli.output),
+		OutType::Gif => image::save_gif(image, squasher, cli.output),
 	}
 }
-
-struct Image {
-	width: usize,
-	height: usize,
-	data: Vec<u8>,
-}
-
-fn get_png<P: AsRef<Utf8Path>>(path: P) -> Result<Image, anyhow::Error> {
-	let decoder = Decoder::new(File::open(path.as_ref())?);
-	let mut reader = decoder.read_info()?;
-
-	let mut data = vec![0; reader.output_buffer_size()];
-	let info = reader.next_frame(&mut data)?;
-	data.resize(info.buffer_size(), 0);
-
-	let colors = info.color_type;
-	match colors {
-		ColorType::Grayscale | ColorType::GrayscaleAlpha | ColorType::Indexed => {
-			bail!("colortype {colors:?} not supported")
-		}
-		ColorType::Rgba => {
-			let pixels = info.width as usize * info.height as usize;
-
-			// the first RGB is fine, we don't need to touch it
-			for idx in 1..pixels {
-				data[idx * 3] = data[idx * 4];
-				data[idx * 3 + 1] = data[idx * 4 + 1];
-				data[idx * 3 + 2] = data[idx * 4 + 2];
-			}
-			data.resize(pixels * 3, 0);
-
-			Ok(Image {
-				width: info.width as usize,
-				height: info.height as usize,
-				data,
-			})
-		}
-		ColorType::Rgb => Ok(Image {
-			width: info.width as usize,
-			height: info.height as usize,
-			data,
-		}),
-	}
-}
-
-fn get_jpg<P: AsRef<Utf8Path>>(path: P) -> Result<Image, anyhow::Error> {
-	let content = std::fs::read(path.as_ref())?;
-	let mut dec = JpegDecoder::new(&content);
-	let pixels = dec.decode()?;
-	let info = dec
-		.info()
-		.ok_or(anyhow!("image had no info; this should be impossible"))?;
-
-	let colorspace = dec.get_output_colorspace();
-	match colorspace {
-		Some(ColorSpace::RGB) => (),
-		_ => bail!("colorspace {colorspace:?} not supported"),
-	}
-
-	Ok(Image {
-		width: info.width as usize,
-		height: info.height as usize,
-		data: pixels,
-	})
-}
-
-fn save_png(image: Image, squasher: Squasher<u8>, path: Utf8PathBuf) -> Result<(), anyhow::Error> {
-	let file = File::create(path)?;
-	let bufw = BufWriter::new(file);
-
-	let mut enc = Encoder::new(bufw, image.width as u32, image.height as u32);
-	enc.set_color(ColorType::Indexed);
-	enc.set_depth(png::BitDepth::Eight);
-	enc.set_palette(squasher.palette_bytes());
-	enc.write_header()?.write_image_data(&image.data)?;
-
-	Ok(())
-}
-
-fn save_gif(image: Image, squasher: Squasher<u8>, path: Utf8PathBuf) -> Result<(), anyhow::Error> {
-	// I don't think I like this API anymore. It's a builder API, that's fine.
-	// I should make it so you can mutate the Gif directly.
-	GifBuilder::new(image.width as u16, image.height as u16)
-		.palette(squasher.palette_bytes().as_slice().try_into().unwrap())
-		.image(ImageBuilder::new(image.width as u16, image.height as u16).build(image.data)?)
-		.build()?
-		.save(path)?;
-
-	Ok(())
-}