mirror of
https://github.com/eRgo35/ascii.git
synced 2026-02-04 04:46:09 +01:00
camera read
This commit is contained in:
1019
Cargo.lock
generated
1019
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,5 +6,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
atty = "0.2.14"
|
||||
clap = { version = "4.5.8", features = ["derive"] }
|
||||
clearscreen = "3.0.0"
|
||||
colored = "2.1.0"
|
||||
image = "0.24.9"
|
||||
nokhwa = { path = "/home/mike/Software/nokhwa", version = "0.10.4", features = ["input-native"] }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod args;
|
||||
pub mod image;
|
||||
pub mod ascii;
|
||||
pub mod gen_handler;
|
||||
pub mod cam_handler;
|
||||
@@ -1,29 +1,75 @@
|
||||
use clap::Parser;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Subcommands>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Subcommands {
|
||||
Gen {
|
||||
#[arg(short, long, default_value_t = String::from(""))]
|
||||
pub image: String,
|
||||
image: String,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub invert: bool,
|
||||
invert: bool,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub colorful: bool,
|
||||
colorful: bool,
|
||||
|
||||
#[arg(long, default_value_t = 80)]
|
||||
pub width: usize,
|
||||
width: usize,
|
||||
|
||||
#[arg(long, default_value_t = 25)]
|
||||
pub height: usize,
|
||||
height: usize,
|
||||
|
||||
#[arg(long, default_value_t = 2)]
|
||||
pub pixel: usize,
|
||||
pixel: usize,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub noresize: bool,
|
||||
noresize: bool,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub matrix: bool,
|
||||
matrix: bool,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
nofill: bool,
|
||||
|
||||
#[arg(long, default_value_t = String::from(""))]
|
||||
output: String,
|
||||
|
||||
#[arg(long, default_value_t = String::from(""))]
|
||||
lightmap: String,
|
||||
},
|
||||
|
||||
Cam {
|
||||
#[arg(long, default_value_t = false)]
|
||||
invert: bool,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
colorful: bool,
|
||||
|
||||
#[arg(long, default_value_t = 80)]
|
||||
width: usize,
|
||||
|
||||
#[arg(long, default_value_t = 25)]
|
||||
height: usize,
|
||||
|
||||
#[arg(long, default_value_t = 2)]
|
||||
pixel: usize,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
noresize: bool,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
matrix: bool,
|
||||
|
||||
#[arg(long, default_value_t = false)]
|
||||
nofill: bool,
|
||||
|
||||
#[arg(long, default_value_t = String::from(""))]
|
||||
lightmap: String,
|
||||
},
|
||||
}
|
||||
@@ -1,8 +1,21 @@
|
||||
use crate::libs::args;
|
||||
use colored::Colorize;
|
||||
use image;
|
||||
use image::GenericImageView;
|
||||
|
||||
fn calculate_brightness((red, green, blue): (u8, u8, u8), mapping: &str) -> u8 {
|
||||
let pixel_iterator: Vec<u16> = vec![red.into(), green.into(), blue.into()];
|
||||
let darkest_color: &u16 = pixel_iterator.iter().min().unwrap();
|
||||
let brightest_color: &u16 = pixel_iterator.iter().max().unwrap();
|
||||
|
||||
let mapping: u8 = match mapping {
|
||||
"fullbright" => 255,
|
||||
"luminosity" => (0.21 * red as f32 + 0.72 * green as f32 + 0.07 * blue as f32) as u8,
|
||||
"minmax" | "average" | _ => ((darkest_color + brightest_color) / 2) as u8,
|
||||
};
|
||||
|
||||
mapping
|
||||
}
|
||||
|
||||
fn select_char(brightness: &u8) -> String {
|
||||
let char = match brightness {
|
||||
0..=25 => " ",
|
||||
@@ -26,7 +39,14 @@ fn select_dominant_color(pixel: (u8, u8, u8), char_pixel: String) -> String {
|
||||
char_pixel.truecolor(red, green, blue).to_string()
|
||||
}
|
||||
|
||||
pub fn to_ascii(img: &image::DynamicImage, args: &args::Args) -> String {
|
||||
pub fn to_ascii(
|
||||
img: &image::DynamicImage,
|
||||
lightmap: &str,
|
||||
pixel_format: usize,
|
||||
invert: bool,
|
||||
colorful: bool,
|
||||
matrix: bool,
|
||||
) -> String {
|
||||
let mut ascii_img = String::new();
|
||||
let (width, height) = img.dimensions();
|
||||
|
||||
@@ -35,27 +55,22 @@ pub fn to_ascii(img: &image::DynamicImage, args: &args::Args) -> String {
|
||||
let pixel = img.get_pixel(x, y);
|
||||
let (mut red, mut green, mut blue) = (pixel[0], pixel[1], pixel[2]);
|
||||
|
||||
let pixel_iterator: Vec<u16> = vec![red.into(), green.into(), blue.into()];
|
||||
let darkest_color: &u16 = pixel_iterator.iter().min().unwrap();
|
||||
let brightest_color: &u16 = pixel_iterator.iter().max().unwrap();
|
||||
let mut brightness = calculate_brightness((red, green, blue), lightmap);
|
||||
|
||||
let mut brightness = ((darkest_color + brightest_color) / 2) as u8;
|
||||
|
||||
if args.invert {
|
||||
if invert {
|
||||
red = 255 - red;
|
||||
green = 255 - green;
|
||||
blue = 255 - blue;
|
||||
|
||||
brightness = 255 - brightness;
|
||||
}
|
||||
|
||||
let mut char_pixel = select_char(&brightness).repeat(args.pixel);
|
||||
let mut char_pixel = select_char(&brightness).repeat(pixel_format);
|
||||
|
||||
if args.colorful {
|
||||
if colorful {
|
||||
char_pixel = select_dominant_color((red, green, blue), char_pixel);
|
||||
}
|
||||
|
||||
if args.matrix {
|
||||
if matrix {
|
||||
char_pixel = char_pixel.bright_green().to_string();
|
||||
}
|
||||
|
||||
|
||||
53
src/libs/cam_handler.rs
Normal file
53
src/libs/cam_handler.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use crate::libs;
|
||||
use image::DynamicImage;
|
||||
use nokhwa::{
|
||||
pixel_format::RgbFormat,
|
||||
utils::{CameraIndex, RequestedFormat, RequestedFormatType},
|
||||
Camera,
|
||||
};
|
||||
|
||||
pub fn video(
|
||||
invert: bool,
|
||||
colorful: bool,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixel: usize,
|
||||
noresize: bool,
|
||||
matrix: bool,
|
||||
nofill: bool,
|
||||
lightmap: String,
|
||||
) {
|
||||
let index = CameraIndex::Index(0);
|
||||
|
||||
let requested =
|
||||
RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);
|
||||
|
||||
let mut camera = Camera::new(index, requested).unwrap();
|
||||
|
||||
camera.open_stream().unwrap();
|
||||
|
||||
loop {
|
||||
let frame = camera.frame().unwrap();
|
||||
|
||||
let decoded = frame.decode_image::<RgbFormat>().unwrap();
|
||||
let mut img: DynamicImage = decoded.into();
|
||||
|
||||
if !noresize && !nofill {
|
||||
img = libs::image::resize_image(&img, width, height);
|
||||
eprintln!("Image resized");
|
||||
libs::image::print_size(&img);
|
||||
}
|
||||
|
||||
if !noresize && nofill {
|
||||
img = libs::image::resize_image_no_fill(&img, width, height);
|
||||
eprintln!("Image resized exact");
|
||||
libs::image::print_size(&img);
|
||||
}
|
||||
|
||||
let ascii_img = libs::ascii::to_ascii(&img, &lightmap, pixel, invert, colorful, matrix);
|
||||
eprintln!("ASCII image created");
|
||||
|
||||
clearscreen::clear().expect("Failed to clear screen");
|
||||
print!("{ascii_img}");
|
||||
}
|
||||
}
|
||||
53
src/libs/gen_handler.rs
Normal file
53
src/libs/gen_handler.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use crate::libs;
|
||||
|
||||
pub fn generator(
|
||||
image_path: String,
|
||||
invert: bool,
|
||||
colorful: bool,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixel: usize,
|
||||
noresize: bool,
|
||||
matrix: bool,
|
||||
nofill: bool,
|
||||
output: String,
|
||||
lightmap: String,
|
||||
) {
|
||||
let path = image_path.clone();
|
||||
|
||||
let mut img = if !path.is_empty() {
|
||||
libs::image::load_image(&path)
|
||||
} else {
|
||||
match libs::image::load_image_from_stdin() {
|
||||
Ok(img) => img,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
libs::image::print_size(&img);
|
||||
|
||||
if !noresize && !nofill {
|
||||
img = libs::image::resize_image(&img, width, height);
|
||||
eprintln!("Image resized");
|
||||
libs::image::print_size(&img);
|
||||
}
|
||||
|
||||
if !noresize && nofill {
|
||||
img = libs::image::resize_image_no_fill(&img, width, height);
|
||||
eprintln!("Image resized exact");
|
||||
libs::image::print_size(&img);
|
||||
}
|
||||
|
||||
let ascii_img = libs::ascii::to_ascii(&img, &lightmap, pixel, invert, colorful, matrix);
|
||||
eprintln!("ASCII image created");
|
||||
|
||||
if !output.is_empty() {
|
||||
libs::image::save_image(&ascii_img, &output);
|
||||
return;
|
||||
}
|
||||
|
||||
print!("{ascii_img}");
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use image;
|
||||
use image::GenericImageView;
|
||||
use std::io::{self, BufReader, BufRead, Cursor};
|
||||
use atty::Stream;
|
||||
use std::fs;
|
||||
|
||||
pub fn load_image_from_stdin() -> Result<image::DynamicImage, image::ImageError> {
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
@@ -19,23 +20,32 @@ pub fn load_image_from_stdin() -> Result<image::DynamicImage, image::ImageError>
|
||||
.with_guessed_format()
|
||||
.expect("Failed to read image format");
|
||||
|
||||
println!("Image loaded: stdin");
|
||||
eprintln!("Image loaded: stdin");
|
||||
|
||||
reader.decode()
|
||||
}
|
||||
|
||||
pub fn load_image(file_name: &str) -> image::DynamicImage {
|
||||
let img = image::open(file_name).expect("File not found!");
|
||||
println!("Image loaded: {file_name}");
|
||||
eprintln!("Image loaded: {file_name}");
|
||||
|
||||
img
|
||||
}
|
||||
|
||||
pub fn save_image(img: &String, file_name: &str) {
|
||||
fs::write(file_name, img).expect("Unable to write file");
|
||||
eprintln!("Image saved: {file_name}");
|
||||
}
|
||||
|
||||
pub fn print_size(img: &image::DynamicImage) {
|
||||
let (width, height) = img.dimensions();
|
||||
println!("Image dimensions: {width}x{height}");
|
||||
eprintln!("Image dimensions: {width}x{height}");
|
||||
}
|
||||
|
||||
pub fn resize_image(img: &image::DynamicImage, nwidth: usize, nheight: usize) -> image::DynamicImage {
|
||||
img.resize(nwidth as u32, nheight as u32, image::imageops::FilterType::Lanczos3)
|
||||
}
|
||||
|
||||
pub fn resize_image_no_fill(img: &image::DynamicImage, nwidth: usize, nheight: usize) -> image::DynamicImage {
|
||||
img.resize_exact(nwidth as u32, nheight as u32, image::imageops::FilterType::Lanczos3)
|
||||
}
|
||||
61
src/main.rs
61
src/main.rs
@@ -1,34 +1,49 @@
|
||||
use clap::Parser;
|
||||
|
||||
mod libs;
|
||||
|
||||
fn main() {
|
||||
println!("ASCII Generator\n----------------");
|
||||
eprintln!("ASCII Generator\n----------------");
|
||||
|
||||
let args = libs::args::Args::parse();
|
||||
let path = args.image.clone();
|
||||
|
||||
let mut img = if !path.is_empty() {
|
||||
libs::image::load_image(&path)
|
||||
} else {
|
||||
match libs::image::load_image_from_stdin() {
|
||||
Ok(img) => img,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
match args.command {
|
||||
Some(libs::args::Subcommands::Gen {
|
||||
image,
|
||||
invert,
|
||||
colorful,
|
||||
width,
|
||||
height,
|
||||
pixel,
|
||||
noresize,
|
||||
matrix,
|
||||
nofill,
|
||||
output,
|
||||
lightmap,
|
||||
}) => {
|
||||
libs::gen_handler::generator(
|
||||
image, invert, colorful, width, height, pixel, noresize, matrix, nofill, output,
|
||||
lightmap,
|
||||
);
|
||||
}
|
||||
Some(libs::args::Subcommands::Cam {
|
||||
invert,
|
||||
colorful,
|
||||
width,
|
||||
height,
|
||||
pixel,
|
||||
noresize,
|
||||
matrix,
|
||||
nofill,
|
||||
lightmap,
|
||||
}) => {
|
||||
libs::cam_handler::video(
|
||||
invert, colorful, width, height, pixel, noresize, matrix, nofill, lightmap,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
eprintln!("No subcommand provided. Available subcommands: `gen`, `cam`");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
libs::image::print_size(&img);
|
||||
|
||||
if !args.noresize {
|
||||
img = libs::image::resize_image(&img, args.width, args.height);
|
||||
println!("Image resized");
|
||||
libs::image::print_size(&img);
|
||||
}
|
||||
|
||||
let ascii_img = libs::ascii::to_ascii(&img, &args);
|
||||
println!("ASCII image created");
|
||||
|
||||
println!("{ascii_img}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user