use std::{ fs::File, io::Read, path::{Path, PathBuf}, }; use anyhow::{bail, Context, Result}; use clap::{Parser, ValueEnum}; use lazy_static::lazy_static; use regex::Regex; use turbulence::generate_turbulence; use crate::value_noise::generate_noise; mod image; mod turbulence; mod value_noise; mod vec2; /// Noise image generator. #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Opt { /// Path to the input file to use. /// /// The input file should follow this format: /// /// imsize [width] [height] /// /// Where `imsize' is a keyword, and `width' and `height' are integer values /// denoting the desired size of the image to be generated. #[clap()] input_path: PathBuf, /// The algorithm to use for pattern generation. #[clap(long = "algorithm", default_value = "turbulence")] algorithm: Algorithm, /// The output image path. #[clap(short = 'o', long = "out", default_value = "out.ppm")] output_path: PathBuf, } #[derive(ValueEnum, Clone)] enum Algorithm { /// Generates noise, which is linearly interpolated randomness. Noise, /// Fractal sum made with signed noise, used to resemble smoke. Turbulence, } fn main() -> Result<()> { let opt = Opt::parse(); let (width, height) = parse_input_file(&opt.input_path)?; // We will use the default thread-local rng. Future work could include // allowing the rng to be seedable from the command line let rng = rand::thread_rng(); // Pick the algorithm to use based on the option passed via command line let ppm = match opt.algorithm { Algorithm::Noise => generate_noise(width, height, rng), Algorithm::Turbulence => generate_turbulence(width, height, rng), }; // Write the PPM image to the output file specified { let file = File::create(&opt.output_path).with_context(|| { format!("Could not create file {:?}.", opt.output_path) })?; ppm.write(file).with_context(|| { format!("Could not write to file {:?}.", opt.output_path) })?; } Ok(()) } lazy_static! { static ref INPUT_PATTERN: Regex = Regex::new(r"imsize\s+(?P\d+)\s+(?P\d+)") .expect("Failed to compile regex."); } /// Parse the input file, returning the target image dimensions fn parse_input_file(path: impl AsRef) -> Result<(usize, usize)> { let path = path.as_ref(); // Read the contents from the file let contents = { let mut contents = String::new(); let mut file = File::open(path)?; file.read_to_string(&mut contents)?; contents }; // Parse the contents using regex let matches = match INPUT_PATTERN.captures(&contents) { Some(v) => v, None => bail!("Invalid input file syntax."), }; // Extract the parsed captures and convert to int let width = matches.name("width").unwrap().as_str().parse()?; let height = matches.name("height").unwrap().as_str().parse()?; Ok((width, height)) }