#![feature(box_syntax)] pub mod color; use anyhow::Result; use color::Color; use std::{ env, f64::consts::PI, fs::File, io::{stdin, BufRead, BufReader, Read}, process::exit, }; static HELP: &str = " Usage: blubcat PATTERN [FILE]... Concatenate FILE(s) to standard output, colorizing the output according to OPTION(s). With no FILE, or when FILE is -, the standard input is read. If PATTERN is repeated only the last one will be used. PATTERN: -R --rainbow Rainbow -S --sequence Sequences colors on the X-axis -G --gradient Interpolates the colors on the X-axis Apply TRANSFORM to PATTERN TRANSFORM: -r --rotate -s --scale -m --matrix PRESET: -P --pride Pride flag COLOR: black white red green blue yellow cyan magenta "; fn main() -> Result<()> { let mut args = env::args().skip(1); let mut pat: Box = box Solid(Color::WHITE); 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 == "-" { break; } match a.as_str() { "-?" | "--help" => { println!("{}", HELP); exit(0) } /* PRESETS */ "-P" | "--pride" => { pat = box Transform { matrix: ((1.0 / 12.0, 1.0 / 12.0), (0.0, 0.0)), inner: box Extend(box Sequence(vec![ Color::parse("e50000").unwrap(), Color::parse("ff8d00").unwrap(), Color::parse("ffee00").unwrap(), Color::parse("028121").unwrap(), Color::parse("004cff").unwrap(), Color::parse("770088").unwrap(), ])), } } /* PATTERNS */ "-R" | "--rainbow" => pat = box Extend(box Rainbow), "-S" | "--sequence" => { pat = box Extend(box Sequence( arg_next() .unwrap() .split(",") .map(|e| Color::parse(e).expect("color invalid")) .collect::>(), )) } "-G" | "--gradient" => { pat = box Extend(box Gradient( arg_next() .unwrap() .split(",") .map(|e| Color::parse(e).expect("color invalid")) .collect::>(), )) } /* TRANSFORMS */ "-s" | "--scale" => { let fac = arg_num(); pat = box Transform { inner: pat, matrix: ((fac, 0.0), (0.0, fac)), } } "-r" | "--rotate" => { let angle = arg_num() * PI * 2.0; pat = box Transform { inner: pat, matrix: ((angle.cos(), -angle.sin()), (angle.sin(), angle.cos())), } } "-m" | "--matrix" => { pat = box Transform { inner: pat, matrix: ((arg_num(), arg_num()), (arg_num(), arg_num())), } } _ => panic!("unknown option {}", &a), } } else { break; } } let inputs = args.collect::>(); if inputs.len() == 0 { colorize(stdin(), &mut pat)? } else { for f in inputs { let file = File::open(f)?; colorize(file, &mut pat)?; } } Ok(()) } 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(()) } trait Sample2D { fn sample(&mut self, x: f64, y: f64) -> Color; } trait Sample1D { fn sample(&mut self, x: f64) -> Color; } struct Extend(Box); impl Sample2D for Extend { fn sample(&mut self, x: f64, _y: f64) -> Color { self.0.sample(x) } } struct Transform { pub inner: Box, pub matrix: ((f64, f64), (f64, f64)), } impl Sample2D for Transform { fn sample(&mut self, x: f64, y: f64) -> Color { let (x, y) = ( x * self.matrix.0 .0 + y * self.matrix.0 .1, x * self.matrix.1 .0 + y * self.matrix.1 .1, ); self.inner.sample(x, y) } } struct Rainbow; impl Sample1D for Rainbow { fn sample(&mut self, x: f64) -> Color { static PI: f64 = 3.14; let x = x * PI * 2.0; return Color { r: (x + PI / 3.0 * 0.0).sin() * 0.5 + 0.5, g: (x + PI / 3.0 * 2.0).sin() * 0.5 + 0.5, b: (x + PI / 3.0 * 4.0).sin() * 0.5 + 0.5, }; } } struct Sequence(pub Vec); impl Sample1D for Sequence { fn sample(&mut self, x: f64) -> Color { self.0[(x * self.0.len() as f64).floor() as usize % self.0.len()] } } struct Gradient(pub Vec); impl Sample1D for Gradient { fn sample(&mut self, x: f64) -> Color { let index = x * self.0.len() as f64; let index_int = index.floor() as usize; let index_error = index % 1.0; let a = self.0[index_int % self.0.len()]; let b = self.0[(index_int + 1) % self.0.len()]; Color::mix(a, b, index_error) } } struct Solid(Color); impl Sample2D for Solid { fn sample(&mut self, _x: f64, _y: f64) -> Color { self.0 } }