sync with local; working normal/complex conversions
This commit is contained in:
parent
82b85e15a0
commit
5a1eda07da
|
|
@ -8,3 +8,9 @@ Cargo.lock
|
|||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
#intellij folder
|
||||
.idea/
|
||||
|
||||
#test files
|
||||
*.png
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "img2ascii"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.10.*"
|
||||
dotenvy = "0.15.*"
|
||||
log = "0.4.*"
|
||||
clap = { version = "4.1.*", features = ["derive"] }
|
||||
image = "0.24.*"
|
||||
termion = "2.0.*"
|
||||
|
|
@ -1,2 +1,6 @@
|
|||
# img2ascii
|
||||
CLI that converts images into ascii art and prints them to the console
|
||||
|
||||
|
||||
|
||||
//todo: Update this
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
use image::{DynamicImage, GenericImageView, Rgba};
|
||||
|
||||
//todo: consider how to take care of the a channel => do we want to render that as background?
|
||||
fn get_color(pixel: (u32, u32, Rgba<u8>)) -> u8 {
|
||||
let vec = pixel.2.0;
|
||||
//luminosity method of getting lightness
|
||||
(vec[0] as f32 * 0.3 + vec[1] as f32 * 0.59 + vec[2] as f32 * 0.11) as u8
|
||||
}
|
||||
|
||||
fn to_ascii(char_map: String, image: DynamicImage) -> Vec<String> {
|
||||
//todo: add color support
|
||||
let l = char_map.len() as f32;
|
||||
let mut str = String::new();
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
|
||||
for pixel in image.pixels() {
|
||||
let ch = char_map.as_bytes()[((get_color(pixel) as f32-1.0)/255f32 * l) as usize]; //fixme: might break with non-ASCII char_map (ie braille chars, possibly)
|
||||
str.push(char::from(ch)); //fixme: this is finicky and also very unsafe
|
||||
|
||||
if pixel.0 == image.width()-1 {
|
||||
out.push(str);
|
||||
str = String::new();
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn to_simple_ascii(image: DynamicImage) -> Vec<String> {
|
||||
to_ascii(" .:-=+*#%@".to_owned(), image)
|
||||
}
|
||||
|
||||
pub fn to_complex_ascii(image: DynamicImage) -> Vec<String> {
|
||||
to_ascii(" .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$".to_owned(), image)
|
||||
}
|
||||
|
||||
pub fn to_braille_ascii(image: DynamicImage) -> Vec<String> {
|
||||
//todo: figure out braille symbols
|
||||
vec!["not implemented".to_owned()]
|
||||
}
|
||||
|
||||
pub fn to_custom_ascii(char_map: String, image: DynamicImage) -> Vec<String> {
|
||||
//todo: this
|
||||
vec!["not implemented".to_owned()]
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use clap::Parser;
|
||||
use log::{debug, error};
|
||||
use image;
|
||||
|
||||
/// Convert an image to ASCII art and print it to the terminal.
|
||||
///
|
||||
/// Configuration is done using flags.
|
||||
#[derive(Parser)]
|
||||
pub struct Cli {
|
||||
/// Use a larger range of characters
|
||||
#[arg(short = 'c', long)]
|
||||
pub complex: bool,
|
||||
/// Display ASCII art in full colour
|
||||
#[arg(short = 'C', long)]
|
||||
pub colour: bool,
|
||||
/// Use braille characters instead of ASCII
|
||||
#[arg(short = 'b', long)]
|
||||
pub braille: bool,
|
||||
/// Print debugging information
|
||||
#[arg(short = 'd', long)]
|
||||
pub debug: bool,
|
||||
/// Image path
|
||||
pub image: PathBuf,
|
||||
#[arg(short = 'w', long)]
|
||||
/// Set the width of the output, instead of using terminal width
|
||||
pub width: Option<u16>,
|
||||
/// Save the output to a file, instead of printing to terminal
|
||||
#[arg(short = 'o', long = "output")]
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
||||
impl Cli {
|
||||
pub fn debug_print(&self) {
|
||||
debug!("complex: {}", self.complex);
|
||||
debug!("colour: {}", self.colour);
|
||||
debug!("braille: {}", self.braille);
|
||||
debug!("debug: {}", self.debug);
|
||||
debug!("width: {}", self.width.unwrap_or(u16::MAX));
|
||||
debug!("image: {}", self.image.display());
|
||||
if let Some(output) = self.output.clone() {
|
||||
debug!("output: {}", output.display());
|
||||
} else {
|
||||
debug!("output: None");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) {
|
||||
if !file_exists(self.image.clone()) {
|
||||
error!("Input file \"{}\" does not exist!", self.image.display());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if !is_image(self.image.clone()) {
|
||||
error!("Input file \"{}\" is not an image!", self.image.display());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if let Some(output) = self.output.clone() {
|
||||
if file_exists(output.clone()) {
|
||||
error!("Output file \"{}\" already exists!", output.display());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self) {
|
||||
if self.debug {std::env::set_var("RUST_LOG", "trace")}
|
||||
env_logger::init();
|
||||
self.debug_print();
|
||||
}
|
||||
}
|
||||
|
||||
/// This function checks if a given file is an image
|
||||
///
|
||||
/// arguments:
|
||||
/// path: PathBuf - path to the file to check
|
||||
///
|
||||
/// returns:
|
||||
/// boolean indicating if file is an image or not
|
||||
fn is_image(path: PathBuf) -> bool {
|
||||
let ext = path.extension();
|
||||
match ext {
|
||||
Some(ext) => {
|
||||
vec![OsString::from("png"),
|
||||
OsString::from("jpg"),
|
||||
OsString::from("jpeg"),
|
||||
OsString::from("webp")]
|
||||
.contains(&ext.to_ascii_lowercase())
|
||||
}
|
||||
None => false
|
||||
}
|
||||
}
|
||||
|
||||
/// This function checks if a given file exists
|
||||
///
|
||||
/// arguments:
|
||||
/// path: PathBuf - path to the file to check
|
||||
///
|
||||
/// returns:
|
||||
/// boolean indicating if the file exists or not
|
||||
fn file_exists(path: PathBuf) -> bool {
|
||||
match path.try_exists() {
|
||||
Ok(bool) => bool,
|
||||
Err(e) => {
|
||||
error!("{}", e.to_string());
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
use std::cmp::min;
|
||||
use termion::terminal_size;
|
||||
use log::error;
|
||||
use std::process::exit;
|
||||
use std::path::PathBuf;
|
||||
use image::DynamicImage;
|
||||
|
||||
fn get_terminal_size() -> u32 {
|
||||
let size = terminal_size();
|
||||
match size {
|
||||
Ok(size) => {
|
||||
size.0 as u32
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get terminal size: {}", e.to_string());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_size(w: Option<u16>, img_w: u32) -> u16 {
|
||||
if None == w {
|
||||
min(get_terminal_size(), img_w) as u16
|
||||
} else {
|
||||
w.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_image(path: PathBuf) -> DynamicImage {
|
||||
let img = image::open(path);
|
||||
match img {
|
||||
Ok(img) => img,
|
||||
Err(e) => {
|
||||
error!("Failed to open image: {}", e.to_string());
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
use clap::Parser;
|
||||
use image::imageops::FilterType;
|
||||
use crate::ascii_manipulation::to_simple_ascii;
|
||||
use crate::cli::Cli;
|
||||
use crate::output::print_terminal;
|
||||
|
||||
mod cli;
|
||||
mod image_manipulation;
|
||||
mod ascii_manipulation;
|
||||
mod output;
|
||||
|
||||
//todo: general
|
||||
/* https://stackoverflow.com/questions/69981449/how-do-i-print-colored-text-to-the-terminal-in-rust
|
||||
*/
|
||||
|
||||
fn main() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
|
||||
//parse flags
|
||||
let cli: Cli = Cli::parse();
|
||||
cli.init();
|
||||
cli.validate();
|
||||
|
||||
let mut img = image_manipulation::open_image(cli.image);
|
||||
let w = image_manipulation::get_size(cli.width, img.width());
|
||||
|
||||
//todo: change logic to include -full flag for max width, otherwise use max height?
|
||||
let h: u32 = (img.height() as f32 * w as f32 / img.width() as f32 * 0.5) as u32;
|
||||
img = img.resize_exact(w as u32, h, FilterType::CatmullRom);
|
||||
|
||||
let out = to_simple_ascii(img);
|
||||
|
||||
print_terminal(out);
|
||||
|
||||
//todo:
|
||||
/* function that converts image to braille (if -b)
|
||||
* something about printing in colour
|
||||
* a function to let the user define a custom map
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
pub fn print_terminal(ascii: Vec<String>) {
|
||||
for line in ascii {
|
||||
println!("{}", line);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_file(ascii: Vec<String>, out: PathBuf) {
|
||||
//todo: output
|
||||
}
|
||||
Loading…
Reference in New Issue