Create the ascii_processor 🥰🦀
This commit is contained in:
parent
0ac98a32ac
commit
b885cc3fb7
8 changed files with 160 additions and 11 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,5 +11,3 @@ Cargo.lock
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
# testing files
|
|
||||||
image.png
|
|
||||||
|
|
BIN
images/anime.jpg
Normal file
BIN
images/anime.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
images/anime_2.jpg
Normal file
BIN
images/anime_2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
images/image.png
Normal file
BIN
images/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
BIN
images/money_mouth_face.png
Normal file
BIN
images/money_mouth_face.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
34
src/args.rs
34
src/args.rs
|
@ -1,5 +1,6 @@
|
||||||
pub mod args {
|
pub mod args {
|
||||||
use clap::{Parser, arg, ValueEnum, ColorChoice};
|
use clap::{Parser, arg, ColorChoice};
|
||||||
|
use super::enums::*;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about=None, color=ColorChoice::Always)]
|
#[command(author, version, about, long_about=None, color=ColorChoice::Always)]
|
||||||
|
@ -13,14 +14,38 @@ pub mod args {
|
||||||
pub image: String,
|
pub image: String,
|
||||||
/// The character to use for drawing the image (lighter to darker)
|
/// The character to use for drawing the image (lighter to darker)
|
||||||
/// You can user one character if you uses the color mode
|
/// You can user one character if you uses the color mode
|
||||||
#[arg(short, long, default_value=" .,-~:;=!*#$@")]
|
#[arg(short, long, default_value=" .,-~!;:=*&%$@#")]
|
||||||
pub characters: String,
|
pub characters: String,
|
||||||
|
/// The output scale (1 is the original size)
|
||||||
|
#[arg(short, long, default_value="4")]
|
||||||
|
pub scale: u32,
|
||||||
/// The output file to write to (if output_method is file)
|
/// The output file to write to (if output_method is file)
|
||||||
#[arg(short, long, default_value="ascii_image.txt")]
|
#[arg(short, long, default_value="ascii_image.txt")]
|
||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, ValueEnum, Debug, PartialOrd, Eq, PartialEq)]
|
impl Arguments {
|
||||||
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
if self.characters.len() == 0 {
|
||||||
|
return Err("No characters provided".to_string());
|
||||||
|
} else if self.characters.len() == 1 {
|
||||||
|
if self.mode == Mode::NormalAscii {
|
||||||
|
return Err("One character provided but mode is normal-ascii".to_string());
|
||||||
|
}
|
||||||
|
} else if self.characters.len() > 32 {
|
||||||
|
return Err("Too many characters provided, max is 32".to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod enums {
|
||||||
|
use clap::ValueEnum;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, ValueEnum, Debug, PartialOrd, Eq, PartialEq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
/// Normal ASCII art
|
/// Normal ASCII art
|
||||||
#[clap(alias = "n")]
|
#[clap(alias = "n")]
|
||||||
|
@ -35,9 +60,6 @@ pub mod args {
|
||||||
/// Save the ascii art to a file
|
/// Save the ascii art to a file
|
||||||
#[clap(alias = "f")]
|
#[clap(alias = "f")]
|
||||||
File,
|
File,
|
||||||
#[clap(alias = "c")]
|
|
||||||
/// Copy the ascii art to the clipboard
|
|
||||||
Clipboard,
|
|
||||||
/// Print the ascii art to the terminal
|
/// Print the ascii art to the terminal
|
||||||
#[clap(alias = "s")]
|
#[clap(alias = "s")]
|
||||||
Stdout,
|
Stdout,
|
||||||
|
|
74
src/ascii_processor.rs
Normal file
74
src/ascii_processor.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use clap::arg;
|
||||||
|
use image::{GenericImageView, DynamicImage};
|
||||||
|
use colored::{ColoredString, Colorize};
|
||||||
|
use crate::args::{
|
||||||
|
args::Arguments,
|
||||||
|
enums::Mode
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate_ascii(image: DynamicImage, args: &Arguments) -> Result<Vec<ColoredString>, error::ASCIIProcessingError> {
|
||||||
|
let characters = args.characters.chars().collect::<Vec<char>>();
|
||||||
|
trace!("Characters: {:?}, length: {}", characters, characters.len());
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let (width, height) = image.dimensions();
|
||||||
|
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
if y % (args.scale * 2) == 0 && x % args.scale == 0 {
|
||||||
|
output.push(get_character(image.get_pixel(x, y), &characters, args.mode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add a new line at the end of each row
|
||||||
|
if y % (args.scale * 2) == 0 {
|
||||||
|
output.push("\n".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_character(pixel: image::Rgba<u8>, characters: &Vec<char>, mode: Mode) -> ColoredString {
|
||||||
|
let intent = if pixel[3] == 0 { 0 } else { pixel[0] / 3 + pixel[1] / 3 + pixel[2] / 3 };
|
||||||
|
|
||||||
|
let ch = characters[(intent / (32 + 7 - (7 + (characters.len() - 7)) as u8)) as usize];
|
||||||
|
|
||||||
|
let ch = String::from(ch);
|
||||||
|
|
||||||
|
match mode {
|
||||||
|
Mode::NormalAscii => ColoredString::from(&*ch),
|
||||||
|
Mode::COLORED => {
|
||||||
|
ch.to_string()
|
||||||
|
.truecolor(pixel[0], pixel[1], pixel[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod error {
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ASCIIProcessingError {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ASCIIProcessingError {
|
||||||
|
pub fn new(message: String) -> Self {
|
||||||
|
ASCIIProcessingError {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ASCIIProcessingError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ASCIIProcessingError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
&self.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
src/main.rs
61
src/main.rs
|
@ -1,12 +1,21 @@
|
||||||
|
use std::io::Write;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
extern crate pretty_env_logger;
|
extern crate pretty_env_logger;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
mod ascii_processor;
|
||||||
|
|
||||||
use crate::args::args::Arguments;
|
use crate::args::{
|
||||||
|
args::Arguments,
|
||||||
|
enums::{Mode, OutputMethod},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::ascii_processor::generate_ascii;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Initialize the logger
|
// Initialize the logger
|
||||||
|
@ -18,9 +27,20 @@ fn main() {
|
||||||
info!("Successfully parsed arguments");
|
info!("Successfully parsed arguments");
|
||||||
trace!("Arguments: {:?}", arguments);
|
trace!("Arguments: {:?}", arguments);
|
||||||
|
|
||||||
|
// Validate the arguments
|
||||||
|
info!("Validating arguments");
|
||||||
|
match arguments.validate() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to validate arguments: {}", e);
|
||||||
|
eprintln!("Failed to validate arguments: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open the image
|
// Open the image
|
||||||
info!("Opening image: {}", arguments.image);
|
info!("Opening image: {}", arguments.image);
|
||||||
let image = match image::open(arguments.image) {
|
let image = match image::open(arguments.image.clone()) {
|
||||||
Ok(image) => image,
|
Ok(image) => image,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to open image: {:?}", e);
|
error!("Failed to open image: {:?}", e);
|
||||||
|
@ -31,4 +51,39 @@ fn main() {
|
||||||
info!("Successfully opened image");
|
info!("Successfully opened image");
|
||||||
trace!("Image dimensions: {:?}", image.dimensions());
|
trace!("Image dimensions: {:?}", image.dimensions());
|
||||||
|
|
||||||
|
// Process the image
|
||||||
|
let output = match generate_ascii(image, &arguments) {
|
||||||
|
Ok(out) => {
|
||||||
|
info!("Successfully processed image");
|
||||||
|
out
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to process image: {:?}", e);
|
||||||
|
eprintln!("Failed to process image: {:?}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Output the image
|
||||||
|
info!("Outputting image");
|
||||||
|
match arguments.output_method {
|
||||||
|
OutputMethod::File => {
|
||||||
|
match std::fs::write(arguments.output.clone(),
|
||||||
|
output.iter().map(|s| s.to_string()).collect::<String>()) {
|
||||||
|
Ok(_) => info!("Successfully outputted image: {}", arguments.output),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to output image: {:?}", e);
|
||||||
|
eprintln!("Failed to output image: {:?}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutputMethod::Stdout => {
|
||||||
|
for char in output {
|
||||||
|
print!("{}", char);
|
||||||
|
std::io::stdout().flush().unwrap();
|
||||||
|
}
|
||||||
|
info!("Successfully outputted image");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue