#![feature(box_syntax)] use anyhow::Result; use blubcat::{ color::Color, pattern::{Gradient, Rainbow, Sequence, Solid}, transform::{Composite, CompositeOperation, Matrix, Polar, Translate, Transpose}, Sample, }; use std::{ env, f64::consts::PI, fs::File, io::{stdin, BufRead, BufReader, Read}, process::exit, }; static HELP: &str = " Usage: blubcat [OPTION...] [FILE...] Concatenate FILE(s) to standard output, colorizing the output according to OPTION. With no FILE, or when FILE is -, the standard input is read. OPTION can be PATTERN, TRANSFORM or PRESET. Patterns are stored on a stack. , indicates the number of patters popped from the stack and how many are pushed back on. PATTERN: 0,1 -R --rainbow Sinebow 0,1 -K --const Solid color 0,1 -S --sequence Sequences colors on the X-axis 0,1 -G --gradient Interpolates the colors on the X-axis TRANSFORM: 1,1 -r --rotate 1,1 -s --scale 1,1 -m --matrix 1,1 -t --translate 1,1 -T --transpose 1,1 -p --polar 2,1 --mix 2,1 --add 2,1 --subtract 2,1 --multiply PRESET: 0,1 --zebra 0,1 --pride --trans 0,1 --ukraine ofc this is needed COLOR: black white red green blue yellow cyan magenta "; fn main() -> Result<()> { let mut args = env::args().skip(1); let mut arg_extra = None; let mut pat: Vec> = vec![]; loop { let a = args.next(); let mut arg_next = || args.next(); let mut arg_num = || { arg_next() .expect("value expected") .parse::() .expect("not a number") }; if let Some(a) = a { if !a.starts_with("-") || a == "--" || a == "-" { arg_extra = Some(a); break; } let mut push_queue: Vec> = vec![]; match a.as_str() { "-?" | "--help" => { println!("{}", HELP); exit(0) } /* PRESETS */ "--pride" => pat.push(flag_helper(&[ "e50000", "ff8d00", "ffee00", "028121", "004cff", "770088", ])), "--zebra" => pat.push(flag_helper(&["000000", "ffffff"])), "--trans" => pat.push(flag_helper(&[ "5bcffb", "f5abb9", "ffffff", "f5abb9", "5bcffb", ])), "--ukraine" => pat.push(flag_helper(&["0057B8", "FFD700"])), /* PATTERNS */ "-R" | "--rainbow" => pat.push(box Rainbow), "-S" | "--sequence" => pat.push(box Sequence( arg_next() .unwrap() .split(",") .map(|e| Color::parse(e).expect("color invalid")) .collect::>(), )), "-G" | "--gradient" => pat.push(box Gradient( arg_next() .unwrap() .split(",") .map(|e| Color::parse(e).expect("color invalid")) .collect::>(), )), "-K" | "--const" => pat.push(box Solid( Color::parse(arg_next().expect("color expected").as_str()) .expect("invalid color"), )), /* TRANSFORMS */ "-s" | "--scale" => { let fac = arg_num(); push_queue.push(box Matrix { inner: pat.pop().unwrap(), matrix: ((fac, 0.0), (0.0, fac)), }) } "-r" | "--rotate" => { let angle = arg_num() * PI * 2.0; push_queue.push(box Matrix { inner: pat.pop().unwrap(), matrix: ((angle.cos(), -angle.sin()), (angle.sin(), angle.cos())), }) } "-m" | "--matrix" => push_queue.push(box Matrix { inner: pat.pop().unwrap(), matrix: ((arg_num(), arg_num()), (arg_num(), arg_num())), }), "-p" | "--polar" => push_queue.push(box Polar(pat.pop().unwrap())), "-t" | "--translate" => { let (x, y) = (arg_num(), arg_num()); push_queue.push(box Translate { offset: (x, y), inner: pat.pop().unwrap(), }) } "-T" | "--transpose" => push_queue.push(box Transpose(pat.pop().unwrap())), "--add" => push_queue.push(box Composite { a: pat.pop().unwrap(), b: pat.pop().unwrap(), mode: CompositeOperation::Add, }), "--subtract" => push_queue.push(box Composite { a: pat.pop().unwrap(), b: pat.pop().unwrap(), mode: CompositeOperation::Subtract, }), "--multiply" => push_queue.push(box Composite { a: pat.pop().unwrap(), b: pat.pop().unwrap(), mode: CompositeOperation::Multiply, }), "--mix" => push_queue.push(box Composite { a: pat.pop().unwrap(), b: pat.pop().unwrap(), mode: CompositeOperation::Mix(arg_num()), }), _ => panic!("unknown option {}", &a), } pat.extend(push_queue.drain(..)) } else { break; } } let mut inputs = args.collect::>(); let mut pat = pat.pop().unwrap_or_else(|| box Solid(Color::WHITE)); if let Some(a) = arg_extra { inputs.insert(0, a) } if inputs.len() == 0 { colorize(stdin(), &mut pat)? } else { for f in inputs { let file = File::open(f)?; colorize(file, &mut pat)?; } } Ok(()) } fn flag_helper(colors: &[&str]) -> Box { box Matrix { matrix: ((1.0 / 6.0, 1.0 / 6.0), (0.0, 0.0)), inner: box Sequence( colors .into_iter() .map(|e| Color::parse(e).unwrap()) .collect(), ), } } fn colorize(file: impl Read, sampler: &mut Box) -> Result<()> { let mut file = BufReader::new(file); let mut line = String::new(); for y in 0.. { if file.read_line(&mut line)? == 0 { break; } for (x, c) in line.chars().enumerate() { let (r, g, b) = sampler.sample(x as f64, y as f64).as_rgb888(); print!("\x1b[38;2;{r};{g};{b}m{c}") } line.clear(); } Ok(()) }