polish
This commit is contained in:
parent
8a68e44e3d
commit
38f5d4446e
10 changed files with 169 additions and 22 deletions
1
assignment-0/.gitignore
vendored
1
assignment-0/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
input
|
||||
out.ppm
|
||||
|
||||
|
|
40
assignment-0/Cargo.lock
generated
40
assignment-0/Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "anyhow"
|
||||
version = "1.0.68"
|
||||
|
@ -14,7 +23,9 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -141,6 +152,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
|
@ -153,6 +170,12 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
|
@ -243,6 +266,23 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rustix"
|
||||
version = "0.36.7"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
[package]
|
||||
authors = ["Michael Zhang <zhan4854@umn.edu>"]
|
||||
name = "assignment-0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
regex = "1.7.1"
|
||||
|
|
31
assignment-0/README.md
Normal file
31
assignment-0/README.md
Normal 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
|
|
@ -1,14 +1,22 @@
|
|||
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 Ppm {
|
||||
/// A representation of an image
|
||||
pub struct Image {
|
||||
/// Width in pixels
|
||||
pub(crate) width: usize,
|
||||
|
||||
/// Height in pixels
|
||||
pub(crate) height: usize,
|
||||
|
||||
/// Pixel data in row-major form.
|
||||
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<()> {
|
||||
// Header
|
||||
let header = format!("P3 {} {} 255\n", self.width, self.height);
|
|
@ -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 lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use turbulence::generate_turbulence;
|
||||
|
||||
use crate::value_noise::generate_noise;
|
||||
|
||||
mod ppm;
|
||||
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,
|
||||
|
||||
#[clap(long = "size", default_value = "1024")]
|
||||
size: usize,
|
||||
|
||||
/// 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(opt.size, opt.size, rng),
|
||||
Algorithm::Turbulence => generate_turbulence(opt.size, opt.size, rng),
|
||||
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)?;
|
||||
ppm.write(file)?;
|
||||
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<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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
use rand::RngCore;
|
||||
|
||||
use crate::ppm::{Pixel, Ppm};
|
||||
use crate::image::{Image, Pixel};
|
||||
use crate::value_noise::ValueNoise;
|
||||
use crate::vec2::Vec2;
|
||||
|
||||
|
@ -10,7 +11,7 @@ pub fn generate_turbulence(
|
|||
width: usize,
|
||||
height: usize,
|
||||
rng: impl RngCore,
|
||||
) -> Ppm {
|
||||
) -> Image {
|
||||
let mut noise_map = vec![0.0; width * height];
|
||||
let noise = ValueNoise::new(rng);
|
||||
|
||||
|
@ -43,7 +44,7 @@ pub fn generate_turbulence(
|
|||
Pixel(v, v, v)
|
||||
})
|
||||
.collect();
|
||||
Ppm {
|
||||
Image {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
|
|
|
@ -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
|
||||
|
||||
use std::mem;
|
||||
|
||||
use rand::{Rng, RngCore};
|
||||
|
||||
use crate::ppm::{Pixel, Ppm};
|
||||
use crate::image::{Pixel, Image};
|
||||
use crate::vec2::Vec2;
|
||||
|
||||
const MAX_TABLE_SIZE: usize = 256;
|
||||
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 noise = ValueNoise::new(rng);
|
||||
let frequency = 0.05;
|
||||
|
@ -29,7 +30,7 @@ pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm {
|
|||
Pixel(v, v, v)
|
||||
})
|
||||
.collect();
|
||||
Ppm {
|
||||
Image {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::ops::Mul;
|
||||
|
||||
/// A generic 2-dimensional point class.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Vec2<T = f64> {
|
||||
pub x: T,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
overlays = [ fenix.overlays.default ];
|
||||
};
|
||||
|
||||
toolchain = pkgs.fenix.default;
|
||||
toolchain = pkgs.fenix.stable;
|
||||
in rec {
|
||||
devShell = pkgs.mkShell {
|
||||
packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ])
|
||||
|
|
Loading…
Reference in a new issue