commit b8bf240101617814c591788f9bf5fea60478edd7 Author: Alexander Bass Date: Wed May 24 22:48:43 2023 -0400 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09936c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +test.jpg \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..655e96d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,224 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ASCIIfryer" +version = "0.1.0" +dependencies = [ + "argh", + "image", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "argh" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "png" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a0002b6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ASCIIfryer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argh = "0.1.10" +image = { version = "0.24.6", default-features = false, features = [ + "jpeg", + "png", + "bmp", +] } + +[profile.dev] +lto = true +opt-level = 1 + +[profile.release] +lto = true + +[profile.dev.package."*"] +opt-level = 3 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2356706 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,98 @@ +#![allow(non_snake_case)] +use argh::FromArgs; +use image::{self, imageops, Rgb}; +use std::fs; +use std::io::{self, BufRead, BufReader, Read}; +#[derive(FromArgs)] +/// Convert images to ascii. +/// works with Jpeg, PNG, and BMP formats +struct ASCIIfryer { + /// width in columns of output ASCII + #[argh(option, short = 'w', default = "40")] + width: u32, + /// height in rows of output ASCII + #[argh(option, short = 'h', default = "40")] + height: u32, + /// palette of ASCII characters to render image with + #[argh(option, short = 'p', default = "String::from(\".,-+=oO0#\")")] + palette: String, + + /// read image from standard input + #[argh(switch, short = 's')] + stdin: bool, + + /// input file path (jpeg/png/bmp) + #[argh(positional)] + input: Option, +} + +struct Charset { + characters: String, + mult: f32, +} +impl Charset { + fn new(characters: &str) -> Self { + let characters = characters.to_owned(); + let mult = characters.chars().count() as f32 / (255.0 * 3.0); + + Charset { characters, mult } + } + fn convert_pixel(&self, px: &Rgb) -> char { + let p = px[0] as u16 + px[1] as u16 + px[2] as u16; + let scaled = p as f32 * self.mult; + + let scaled = scaled.floor() as usize; + self.characters.chars().nth(scaled).unwrap_or('X') + } +} + +fn main() -> Result<(), &'static str> { + // Args + let settings: ASCIIfryer = argh::from_env(); + + // Specifying whether reading from file or from stdin + // invalid states panic + let mut reader: Box = match (settings.input, settings.stdin) { + (Some(filename), false) => Box::new(BufReader::new( + fs::File::open(filename).expect("Could not read file"), + )), + (None, true) => Box::new(BufReader::new(io::stdin().lock())), + (Some(_), true) => { + return Err("Can not load image from file and stdin. Use one or the other"); + } + (None, false) => { + return Err("No input provided"); + } + }; + let mut file = vec![]; + // Load data from file/stdin + reader + .read_to_end(&mut file) + .expect("Error while reading input"); + // Parse data to image + let image = image::load_from_memory(&file) + .expect("Could not load as image. PNG, BMP, and JPEG are supported"); + // Downscale image to reasonable size + let image = image.resize( + settings.width, + settings.height, + imageops::FilterType::Nearest, + ); + // Convert image into buffer of Rgb (pixels) + let buffer = image + .as_rgb8() + .expect("Could not extract 8-bit RGB buffer from image"); + + let width = image.width(); + let charset = Charset::new(&settings.palette); + + // Iterate through each pixel and print resulting ASCII + for (col, _, pix) in buffer.enumerate_pixels() { + let symbol = charset.convert_pixel(pix); + print!("{symbol}"); + if col == width - 1 { + println!(); + } + } + Ok(()) +}