2022-10-10 20:17:21 +00:00
|
|
|
use crate::args::{args::Arguments, enums::Mode};
|
2022-10-03 18:13:59 +00:00
|
|
|
use colored::{ColoredString, Colorize};
|
2022-10-10 20:17:21 +00:00
|
|
|
use image::{DynamicImage, GenericImageView};
|
2022-10-03 18:13:59 +00:00
|
|
|
|
2022-10-10 20:17:21 +00:00
|
|
|
use std::io::{self, BufWriter, Write};
|
|
|
|
|
|
|
|
pub fn generate_ascii<W: Write>(
|
|
|
|
image: DynamicImage,
|
|
|
|
args: &Arguments,
|
|
|
|
mut buffer: BufWriter<W>,
|
|
|
|
) -> io::Result<()> {
|
2022-10-03 18:13:59 +00:00
|
|
|
let characters = args.characters.chars().collect::<Vec<char>>();
|
|
|
|
let (width, height) = image.dimensions();
|
2022-10-25 19:07:22 +00:00
|
|
|
let actual_scale = calculate_scale(args, (width, height));
|
2022-10-03 18:13:59 +00:00
|
|
|
|
|
|
|
for y in 0..height {
|
|
|
|
for x in 0..width {
|
2022-10-25 19:07:22 +00:00
|
|
|
if y % (actual_scale * 2) == 0 && x % actual_scale == 0 {
|
2022-10-10 20:17:21 +00:00
|
|
|
let element = get_character(
|
2022-10-03 18:50:48 +00:00
|
|
|
image.get_pixel(x, y),
|
2022-10-10 20:17:21 +00:00
|
|
|
&characters,
|
|
|
|
args.mode,
|
2022-10-03 19:00:39 +00:00
|
|
|
&args.background,
|
2022-10-10 20:17:21 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
buffer.write_all(format!("{element}").as_bytes())?;
|
2022-10-03 18:13:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add a new line at the end of each row
|
2022-10-25 19:07:22 +00:00
|
|
|
if y % (actual_scale * 2) == 0 {
|
2022-10-10 20:17:21 +00:00
|
|
|
buffer.write_all("\n".as_bytes())?;
|
2022-10-03 18:13:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-10 20:17:21 +00:00
|
|
|
Ok(())
|
2022-10-03 18:13:59 +00:00
|
|
|
}
|
|
|
|
|
2022-10-03 18:50:48 +00:00
|
|
|
fn get_character(
|
|
|
|
pixel: image::Rgba<u8>,
|
2022-10-10 20:17:21 +00:00
|
|
|
characters: &Vec<char>,
|
|
|
|
mode: Mode,
|
2022-10-03 19:00:39 +00:00
|
|
|
background: &Option<String>,
|
2022-10-03 18:50:48 +00:00
|
|
|
) -> ColoredString {
|
2022-10-10 20:17:21 +00:00
|
|
|
let intent = if pixel[3] == 0 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
pixel[0] / 3 + pixel[1] / 3 + pixel[2] / 3
|
|
|
|
};
|
2022-10-03 18:13:59 +00:00
|
|
|
|
|
|
|
let ch = characters[(intent / (32 + 7 - (7 + (characters.len() - 7)) as u8)) as usize];
|
|
|
|
|
|
|
|
let ch = String::from(ch);
|
|
|
|
|
2022-10-03 18:50:48 +00:00
|
|
|
let ch = match mode {
|
2022-10-03 18:13:59 +00:00
|
|
|
Mode::NormalAscii => ColoredString::from(&*ch),
|
2022-10-10 20:17:21 +00:00
|
|
|
Mode::Colored => ch.truecolor(pixel[0], pixel[1], pixel[2]),
|
2022-10-03 18:50:48 +00:00
|
|
|
};
|
2022-10-03 18:13:59 +00:00
|
|
|
|
2022-10-03 18:50:48 +00:00
|
|
|
match background {
|
|
|
|
Some(bg) => ch.on_color(bg.to_string()),
|
2022-10-10 20:17:21 +00:00
|
|
|
None => ch,
|
2022-10-03 18:13:59 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-25 19:07:22 +00:00
|
|
|
|
|
|
|
///
|
2022-10-25 19:11:53 +00:00
|
|
|
/// Determine how much scale to use in presence of `width` parameters,
|
|
|
|
/// otherwise returns regular `scale` parameter per default behaviour
|
2022-10-25 19:07:22 +00:00
|
|
|
///
|
2022-10-26 17:52:58 +00:00
|
|
|
#[inline]
|
2022-10-25 19:07:22 +00:00
|
|
|
fn calculate_scale(args: &Arguments, dimensions: (u32, u32)) -> u32 {
|
|
|
|
args.width.map_or_else(|| args.scale, |v| dimensions.0 / v)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use crate::args::{
|
|
|
|
args::Arguments,
|
|
|
|
enums::{Mode, OutputMethod},
|
|
|
|
};
|
|
|
|
|
|
|
|
use super::calculate_scale;
|
|
|
|
|
|
|
|
const DIMENSIONS: (u32, u32) = (100, 100);
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_scale() {
|
|
|
|
let args = Arguments {
|
|
|
|
mode: Mode::NormalAscii,
|
|
|
|
output_method: OutputMethod::Stdout,
|
|
|
|
image: "".into(),
|
|
|
|
characters: "".into(),
|
|
|
|
scale: 4,
|
|
|
|
width: Some(10),
|
|
|
|
background: None,
|
|
|
|
output: "".into(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let scale = calculate_scale(&args, DIMENSIONS);
|
|
|
|
assert_eq!(scale, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_default_scale() {
|
|
|
|
let args = Arguments {
|
|
|
|
mode: Mode::NormalAscii,
|
|
|
|
output_method: OutputMethod::Stdout,
|
|
|
|
image: "".into(),
|
|
|
|
characters: "".into(),
|
|
|
|
scale: 4,
|
|
|
|
width: None,
|
|
|
|
background: None,
|
|
|
|
output: "".into(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let scale = calculate_scale(&args, DIMENSIONS);
|
|
|
|
assert_eq!(scale, 4);
|
|
|
|
}
|
|
|
|
}
|