diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..757b842 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,225 @@ +#![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 <c1,c2,c3,...> Sequences colors on the X-axis + -G --gradient <c1,c2,c3,...> Interpolates the colors on the X-axis + <PATTERN> <TRANSFORM> Apply TRANSFORM to PATTERN + <PRESET> + +TRANSFORM: + -r --rotate <angle> + -s --scale <factor> + -m --matrix <x> <y> <z> <w> + +PRESET: + -P --pride Pride flag + +COLOR: + <rrggbb> + black white red green blue yellow cyan magenta + +"; + +fn main() -> Result<()> { + let mut args = env::args().skip(1); + + let mut pat: Box<dyn Sample2D> = 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::<f64>() + .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::<Vec<_>>(), + )) + } + "-G" | "--gradient" => { + pat = box Extend(box Gradient( + arg_next() + .unwrap() + .split(",") + .map(|e| Color::parse(e).expect("color invalid")) + .collect::<Vec<_>>(), + )) + } + + /* 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::<Vec<String>>(); + + 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<dyn Sample2D>) -> 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<dyn Sample1D>); +impl Sample2D for Extend { + fn sample(&mut self, x: f64, _y: f64) -> Color { + self.0.sample(x) + } +} + +struct Transform { + pub inner: Box<dyn Sample2D>, + 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<Color>); +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<Color>); +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 + } +} |