2023-01-21 08:55:19 +00:00
|
|
|
use std::{
|
|
|
|
fs::File,
|
|
|
|
io::Read,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
2023-01-21 08:03:11 +00:00
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
use anyhow::{bail, Context, Result};
|
2023-01-21 08:22:22 +00:00
|
|
|
use clap::{Parser, ValueEnum};
|
2023-01-21 08:55:19 +00:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use regex::Regex;
|
2023-01-21 08:22:22 +00:00
|
|
|
use turbulence::generate_turbulence;
|
2023-01-21 08:03:11 +00:00
|
|
|
|
|
|
|
use crate::value_noise::generate_noise;
|
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
mod image;
|
2023-01-21 08:22:22 +00:00
|
|
|
mod turbulence;
|
2023-01-21 08:03:11 +00:00
|
|
|
mod value_noise;
|
|
|
|
mod vec2;
|
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
/// Noise image generator.
|
2023-01-21 08:03:11 +00:00
|
|
|
#[derive(Parser)]
|
2023-01-21 08:55:19 +00:00
|
|
|
#[clap(author, version, about, long_about = None)]
|
2023-01-21 08:03:11 +00:00
|
|
|
struct Opt {
|
2023-01-21 08:55:19 +00:00
|
|
|
/// 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.
|
2023-01-21 08:22:22 +00:00
|
|
|
#[clap(long = "algorithm", default_value = "turbulence")]
|
|
|
|
algorithm: Algorithm,
|
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
/// The output image path.
|
2023-01-21 08:22:22 +00:00
|
|
|
#[clap(short = 'o', long = "out", default_value = "out.ppm")]
|
|
|
|
output_path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(ValueEnum, Clone)]
|
|
|
|
enum Algorithm {
|
2023-01-21 08:55:19 +00:00
|
|
|
/// Generates noise, which is linearly interpolated randomness.
|
2023-01-21 08:22:22 +00:00
|
|
|
Noise,
|
2023-01-21 08:55:19 +00:00
|
|
|
|
|
|
|
/// Fractal sum made with signed noise, used to resemble smoke.
|
2023-01-21 08:22:22 +00:00
|
|
|
Turbulence,
|
2023-01-21 08:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<()> {
|
2023-01-21 08:22:22 +00:00
|
|
|
let opt = Opt::parse();
|
2023-01-21 08:03:11 +00:00
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
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
|
2023-01-21 08:22:22 +00:00
|
|
|
let rng = rand::thread_rng();
|
2023-01-21 08:03:11 +00:00
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
// Pick the algorithm to use based on the option passed via command line
|
2023-01-21 08:22:22 +00:00
|
|
|
let ppm = match opt.algorithm {
|
2023-01-21 08:55:19 +00:00
|
|
|
Algorithm::Noise => generate_noise(width, height, rng),
|
|
|
|
Algorithm::Turbulence => generate_turbulence(width, height, rng),
|
2023-01-21 08:22:22 +00:00
|
|
|
};
|
2023-01-21 08:03:11 +00:00
|
|
|
|
2023-01-21 08:55:19 +00:00
|
|
|
// Write the PPM image to the output file specified
|
2023-01-21 08:22:22 +00:00
|
|
|
{
|
2023-01-21 08:55:19 +00:00
|
|
|
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)
|
|
|
|
})?;
|
2023-01-21 08:22:22 +00:00
|
|
|
}
|
2023-01-21 08:03:11 +00:00
|
|
|
|
2023-01-21 08:22:22 +00:00
|
|
|
Ok(())
|
2023-01-21 08:03:11 +00:00
|
|
|
}
|
2023-01-21 08:55:19 +00:00
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref INPUT_PATTERN: Regex =
|
|
|
|
Regex::new(r"imsize\s+(?P<width>\d+)\s+(?P<height>\d+)")
|
|
|
|
.expect("Failed to compile regex.");
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse the input file, returning the target image dimensions
|
|
|
|
fn parse_input_file(path: impl AsRef<Path>) -> 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))
|
|
|
|
}
|