From 72672b731cec31239b895f4ca99ea561925f076b Mon Sep 17 00:00:00 2001 From: Nihil Carcosa Date: Fri, 15 Nov 2024 00:41:24 +0100 Subject: [PATCH] scaffolding for proper cli commands is in place, still need to write tests --- Cargo.lock | 85 +++++++++++++++++------- Cargo.toml | 2 +- src/lib.rs | 11 +++- src/main.rs | 184 +++++++++++++++++++++++++++++++--------------------- 4 files changed, 184 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b053c2..2b297ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -65,9 +59,9 @@ dependencies = [ [[package]] name = "array-init" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" [[package]] name = "bitflags" @@ -88,6 +82,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -102,6 +97,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.7.3" @@ -122,9 +129,9 @@ checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -140,14 +147,20 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.5.4", + "miniz_oxide", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -160,15 +173,6 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -189,7 +193,16 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.0", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", ] [[package]] @@ -203,6 +216,15 @@ dependencies = [ "png", ] +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -215,6 +237,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 27cd81f..4d44780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,4 @@ array-init = "2.0.1" png = "0.17.14" log = "0.4.22" colors-transform = "0.2.11" -clap = "4.5.21" +clap = { version = "4.5.18", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index 36b4364..47af193 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,6 +204,16 @@ pub mod qoi_lib { } } + pub fn pixels_to_bytes(&self) -> Vec { + let mut buf: Vec = Vec::with_capacity(self.height as usize * self.width as usize * 4 as usize); + for pixel in &self.pixels { + buf.push(pixel.r); + buf.push(pixel.g); + buf.push(pixel.b); + buf.push(pixel.a); + } + return buf; + } } #[derive(Clone, Copy, Debug, PartialEq)] @@ -402,7 +412,6 @@ pub mod qoi_lib { (store % 64) as u8 } - //Definitely broken in some way pub fn encode_from_image(img: Image) -> Vec { let mut prev_pixel: Pixel = Pixel { r: 0u8, diff --git a/src/main.rs b/src/main.rs index 4d96d82..44ad324 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use std::env; + +use clap::{Parser, Subcommand}; use std::fs::File; -use std::io; use std::io::{BufReader, Read}; use std::time::SystemTime; @@ -96,12 +96,10 @@ fn demo() { } //Attempts to encode given png image as second argument into qoi -fn encode(args: &Vec) { - //Path is fetched from arguments - let path = &args[2]; +fn encode(in_path: &str, out_path: &str) { //Init png decoder, attempt to decode png into bitmap, throw error if unsuccessful - let decoder = png::Decoder::new(File::open(path).unwrap()); + let decoder = png::Decoder::new(File::open(in_path).unwrap()); let mut reader = match decoder.read_info() { Ok(reader) => reader, Err(e) => panic!("ERROR: couldn't read file: {e:}"), @@ -130,30 +128,13 @@ fn encode(args: &Vec) { Err(err) => panic!("Problem generating image: {:?}", err), }; - //encode generated bitmap - if args.len() >= 4 { - let filename: &String = &args[3]; - write_to_file(encode_from_image(img), filename).expect("ERROR: Can't write file."); - } else { - let mut filename = path.clone(); - for _i in 0..4 { - filename.pop(); - } - write_to_file(encode_from_image(img), filename.as_str()).expect("ERROR: Can't write file."); - } + write_to_file(encode_from_image(img), out_path).expect("ERROR: Can't write file."); println!("Encoding successful!"); } -fn decode(args: &Vec) -> io::Result<()> { - let mut path: String = String::new(); - if args.len() > 2 { - path.push_str(args[2].as_str()); - } else { - println!("ERROR: incorrect number of arguments! (specify file to decode!)."); - () - } - let f: File = match File::open(path.as_str()) { +fn decode(path: &str) -> Result { + let f: File = match File::open(path) { Ok(f) => f, Err(e) => panic!("ERROR: {e:?}"), }; @@ -163,68 +144,125 @@ fn decode(args: &Vec) -> io::Result<()> { reader.read_to_end(&mut bytes)?; match qoi::qoi_lib::decode(bytes) { - Ok(_img) => println!("Decoding successful!"), + Ok(img) => { + println!("Decoding successful!"); + return Ok(img); + }, Err(err) => panic!("ERROR: {err:?}"), } - Ok(()) } -fn bench(args: &Vec) { - if args.len() < 4 { - panic!("ERROR: invalid number of arguments!"); - } +fn bench(input: &str, output: Option) { + + let start = SystemTime::now(); + let out_path = match output { + Some(s) => s, + None => input.strip_suffix(".png").unwrap_or(input).to_owned() + }; + + encode(input, &out_path); - let start = SystemTime::now(); - encode(args); match start.elapsed() { - Ok(elapsed) => println!("Encode took {} μs", elapsed.as_micros()), + Ok(elapsed) => { + if elapsed.as_millis() == 0 { + println!("Encode took {:?} μs to complete", elapsed.as_micros()); + } else if elapsed.as_millis() > 999 { + println!("Encode took {:.3} s to complete", elapsed.as_secs_f32()); + } else { + println!("Encode took {:?} ms to complete", elapsed.as_millis()); + } + }, + Err(e) => panic!("ERROR: {e:?}"), } - let mut new_arg: Vec = Vec::new(); - new_arg.push(String::from("")); - new_arg.push(String::from("")); - let mut to_push: String = args[3].clone(); - to_push.push_str(".qoi"); - new_arg.push(to_push); let start = SystemTime::now(); - decode(&new_arg).expect( - "ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read.", - ); + let mut out_path: String = out_path.to_owned(); + if !(out_path.contains(".qoi")) { + out_path.push_str(".qoi"); + } + match decode(&out_path) { + Ok(img) => { + + let out_buf = img.pixels_to_bytes(); + let _ = write_to_file(out_buf, out_path.strip_suffix(".qoi").unwrap()).expect("whoops!"); + }, + Err(e) => panic!("Error: {e:?}") + } match start.elapsed() { - Ok(elapsed) => println!("Decode took {} μs", elapsed.as_micros()), + Ok(elapsed) => { + if elapsed.as_millis() == 0 { + println!("Encode took {:?} μs to complete", elapsed.as_micros()); + } else if elapsed.as_millis() > 999 { + println!("Encode took {:.3} s to complete", elapsed.as_secs_f32()); + } else { + println!("Encode took {:?} ms to complete", elapsed.as_millis()); + } + }, + Err(e) => panic!("ERROR: {e:?}"), } } +#[derive(Parser)] +#[command(name = "QOI Image Transcoder")] +#[command(version, about, long_about = None)] +#[command(next_line_help = true)] +struct Cli { + #[arg(short,long, action = clap::ArgAction::Count)] + verbose: Option, + + #[command(subcommand)] + command: Commands +} + +#[derive(Subcommand)] +enum Commands { + Encode { + input: String, + output: Option + }, + Decode { + input: String, + out_fmt: String, + output: Option + }, + Bench { + input: String, + output: Option + }, + Demo { + } +} + fn main() { - //Initialize logger - init().expect("Failed to initialize logger."); + let cli: Cli = Cli::parse(); - let args: Vec = env::args().collect(); - - if args.len() == 1 { - panic!("ERROR: no arguments send!"); - } - - match args[1].as_str() { - "demo" => { - demo(); - } - //can only handle pngs for now - "encode" => { - encode(&args); - } - "decode" => { - decode(&args).expect("ERROR: Unspecified error during io-pipeline. Ensure file path is valid and can be read."); - } - "bench" => { - bench(&args); - } - "help" => { - println!("qoi supports the following commands: \n encode [IMAGE] (encodes given png-encoded into .qoi) \n decode [IMAGE] decodes given .qoi to .png \n bench [INPUT] [OUTPUT] encodes input .png into .qoi with encoding speed measured in microseconds.") - } - _ => { - panic!("Invalid arguments!") - } + match &cli.command { + Commands::Bench { input, output } => { + bench(&input, output.clone()); + }, + Commands::Decode { input, out_fmt, output } => { + if out_fmt != "png" { + panic!("Unsupported output format!") + } else { + let img = match decode(&input) { + Ok(i) => i, + Err(e) => panic!("Error: {e:?}") + }; + let out_path = match output { + Some(s) => s, + None => input + }; + let _ = write_to_file(img.pixels_to_bytes(), &out_path).expect("Error writing file!"); + } + }, + Commands::Encode { input, output } => { + let out_path = match output { + Some(s) => s, + None => input + }; + encode(&input, &out_path); + }, + Commands::Demo { } => demo() } }