This commit is contained in:
Michael Zhang 2023-01-21 02:55:19 -06:00
parent 8a68e44e3d
commit 38f5d4446e
10 changed files with 169 additions and 22 deletions

View file

@ -1,3 +1,4 @@
/target /target
input
out.ppm out.ppm

View file

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.68" version = "1.0.68"
@ -14,7 +23,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"lazy_static",
"rand", "rand",
"regex",
] ]
[[package]] [[package]]
@ -141,6 +152,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.139" version = "0.2.139"
@ -153,6 +170,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.0" version = "1.17.0"
@ -243,6 +266,23 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.7" version = "0.36.7"

View file

@ -1,11 +1,12 @@
[package] [package]
authors = ["Michael Zhang <zhan4854@umn.edu>"]
name = "assignment-0" name = "assignment-0"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.68" anyhow = "1.0.68"
clap = { version = "4.1.1", features = ["derive"] } clap = { version = "4.1.1", features = ["derive", "cargo"] }
lazy_static = "1.4.0"
rand = "0.8.5" rand = "0.8.5"
regex = "1.7.1"

31
assignment-0/README.md Normal file
View file

@ -0,0 +1,31 @@
Assignment 0: Generating turbulence with Rust!
===
This program generates PPM images.
Using the program
---
The binary provided has been built for Ubuntu, and has been tested on CSELabs.
Run `assignment-0 --help` to find out about all the options for using this
program.
Compiling the source
---
This project is a standard Rust program, and can be built with just a Rust
compiler. The version of Rust used is the current stable at the time of writing,
1.66.
Build the program using:
```
cargo build --release
```
The binary can be found in the `target` directory.
Contact
---
Author: Michael Zhang

View file

@ -1,14 +1,22 @@
use std::io::{Result, Write}; use std::io::{Result, Write};
/// A 24-bit pixel represented by a red, green, and blue value.
pub struct Pixel(pub u8, pub u8, pub u8); pub struct Pixel(pub u8, pub u8, pub u8);
pub struct Ppm { /// A representation of an image
pub struct Image {
/// Width in pixels
pub(crate) width: usize, pub(crate) width: usize,
/// Height in pixels
pub(crate) height: usize, pub(crate) height: usize,
/// Pixel data in row-major form.
pub(crate) data: Vec<Pixel>, pub(crate) data: Vec<Pixel>,
} }
impl Ppm { impl Image {
/// Write the image in PPM format to a file.
pub fn write(&self, mut w: impl Write) -> Result<()> { pub fn write(&self, mut w: impl Write) -> Result<()> {
// Header // Header
let header = format!("P3 {} {} 255\n", self.width, self.height); let header = format!("P3 {} {} 255\n", self.width, self.height);

View file

@ -1,48 +1,111 @@
use std::{fs::File, path::PathBuf}; use std::{
fs::File,
io::Read,
path::{Path, PathBuf},
};
use anyhow::Result; use anyhow::{bail, Context, Result};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use lazy_static::lazy_static;
use regex::Regex;
use turbulence::generate_turbulence; use turbulence::generate_turbulence;
use crate::value_noise::generate_noise; use crate::value_noise::generate_noise;
mod ppm; mod image;
mod turbulence; mod turbulence;
mod value_noise; mod value_noise;
mod vec2; mod vec2;
/// Noise image generator.
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Opt { 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")] #[clap(long = "algorithm", default_value = "turbulence")]
algorithm: Algorithm, algorithm: Algorithm,
#[clap(long = "size", default_value = "1024")] /// The output image path.
size: usize,
#[clap(short = 'o', long = "out", default_value = "out.ppm")] #[clap(short = 'o', long = "out", default_value = "out.ppm")]
output_path: PathBuf, output_path: PathBuf,
} }
#[derive(ValueEnum, Clone)] #[derive(ValueEnum, Clone)]
enum Algorithm { enum Algorithm {
/// Generates noise, which is linearly interpolated randomness.
Noise, Noise,
/// Fractal sum made with signed noise, used to resemble smoke.
Turbulence, Turbulence,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let opt = Opt::parse(); 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(); let rng = rand::thread_rng();
// Pick the algorithm to use based on the option passed via command line
let ppm = match opt.algorithm { let ppm = match opt.algorithm {
Algorithm::Noise => generate_noise(opt.size, opt.size, rng), Algorithm::Noise => generate_noise(width, height, rng),
Algorithm::Turbulence => generate_turbulence(opt.size, opt.size, rng), Algorithm::Turbulence => generate_turbulence(width, height, rng),
}; };
// Write the PPM image to the output file specified
{ {
let file = File::create(opt.output_path)?; let file = File::create(&opt.output_path).with_context(|| {
ppm.write(file)?; format!("Could not create file {:?}.", opt.output_path)
})?;
ppm.write(file).with_context(|| {
format!("Could not write to file {:?}.", opt.output_path)
})?;
} }
Ok(()) Ok(())
} }
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))
}

View file

@ -1,8 +1,9 @@
// Follows this algorithm:
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/simple-pattern-examples.html // https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/simple-pattern-examples.html
use rand::RngCore; use rand::RngCore;
use crate::ppm::{Pixel, Ppm}; use crate::image::{Image, Pixel};
use crate::value_noise::ValueNoise; use crate::value_noise::ValueNoise;
use crate::vec2::Vec2; use crate::vec2::Vec2;
@ -10,7 +11,7 @@ pub fn generate_turbulence(
width: usize, width: usize,
height: usize, height: usize,
rng: impl RngCore, rng: impl RngCore,
) -> Ppm { ) -> Image {
let mut noise_map = vec![0.0; width * height]; let mut noise_map = vec![0.0; width * height];
let noise = ValueNoise::new(rng); let noise = ValueNoise::new(rng);
@ -43,7 +44,7 @@ pub fn generate_turbulence(
Pixel(v, v, v) Pixel(v, v, v)
}) })
.collect(); .collect();
Ppm { Image {
width, width,
height, height,
data, data,

View file

@ -1,16 +1,17 @@
// Follows this algorithm:
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html // https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html
use std::mem; use std::mem;
use rand::{Rng, RngCore}; use rand::{Rng, RngCore};
use crate::ppm::{Pixel, Ppm}; use crate::image::{Pixel, Image};
use crate::vec2::Vec2; use crate::vec2::Vec2;
const MAX_TABLE_SIZE: usize = 256; const MAX_TABLE_SIZE: usize = 256;
const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1; const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1;
pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm { pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Image {
let mut noise_map = vec![0.0; width * height]; let mut noise_map = vec![0.0; width * height];
let noise = ValueNoise::new(rng); let noise = ValueNoise::new(rng);
let frequency = 0.05; let frequency = 0.05;
@ -29,7 +30,7 @@ pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm {
Pixel(v, v, v) Pixel(v, v, v)
}) })
.collect(); .collect();
Ppm { Image {
width, width,
height, height,
data, data,

View file

@ -1,5 +1,6 @@
use std::ops::Mul; use std::ops::Mul;
/// A generic 2-dimensional point class.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct Vec2<T = f64> { pub struct Vec2<T = f64> {
pub x: T, pub x: T,

View file

@ -9,7 +9,7 @@
overlays = [ fenix.overlays.default ]; overlays = [ fenix.overlays.default ];
}; };
toolchain = pkgs.fenix.default; toolchain = pkgs.fenix.stable;
in rec { in rec {
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ]) packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ])