This commit is contained in:
Alexander Bass 2023-05-24 22:48:43 -04:00
commit b8bf240101
4 changed files with 348 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
test.jpg

224
Cargo.lock generated Normal file
View file

@ -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"

24
Cargo.toml Normal file
View file

@ -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

98
src/main.rs Normal file
View file

@ -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<String>,
}
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<u8>) -> 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<dyn BufRead> = 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<u8> (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(())
}