diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 102 | ||||
-rw-r--r-- | src/main.rs | 36 |
2 files changed, 93 insertions, 45 deletions
@@ -13,6 +13,9 @@ with this program. If not, see https://www.gnu.org/licenses/. */ use rand::Rng; +use std::fs::File; +use std::sync::mpsc; +use std::thread; use std::{f64::consts::PI, vec}; use clap::Parser; @@ -74,12 +77,15 @@ pub struct Args { } impl Args { + /// Read the command line arguments pub fn read() -> Self { Args::parse() } } impl Image { + /// Create the basic info for the image from + /// the command line arguments pub fn create_from_args(args: &Args) -> Self { Image { height: args.height, @@ -101,13 +107,11 @@ impl Image { } } - pub fn fill_canvas(&mut self, frame: u16) { - self.generate_noise(frame); - } - + /// Generate the noise for a given frame fn generate_noise(&mut self, frame: u16) { let mut max_dist = 0.0; + // set the point on each ellipse to use as the centre of each cell let pos = 2.0 * PI * frame as f64 / self.frames as f64; for ellipse in &mut self.ellipses { let sin_theta = ((ellipse.angle + pos) * ellipse.direction).sin(); @@ -149,7 +153,6 @@ impl Image { for y in 0..self.height { for x in 0..self.width { let index = y as usize * self.width as usize + x as usize; - //let val = 0xFF - (0xFF as f64 * self.point_data[index].min_dist) as u8; let red; let green; let blue; @@ -176,6 +179,7 @@ impl Image { } } + /// Set the colour at a given pixel fn set_pixel(&mut self, r: u8, g: u8, b: u8, p: Point) { self.pixels[3 * (self.width as usize * p.y as usize + p.x as usize)] = r; self.pixels[3 * (self.width as usize * p.y as usize + p.x as usize) + 1] = g; @@ -184,12 +188,15 @@ impl Image { } impl PointData { + /// get closest point and distance to said point + /// from a given pixel on the frame fn get_point_data(image: &Image, p: Point) -> Self { let mut pd = PointData { min_dist: image.cross_distance, closest_point: Point { x: 0, y: 0 }, }; + // find closest point for ellipse in &image.ellipses { let d = p.distance(&ellipse.curr_point); if d < pd.min_dist { @@ -203,6 +210,7 @@ impl PointData { } impl Point { + /// calculates distance between two points fn distance(&self, other: &Point) -> f64 { let x_dist: f64 = other.x as f64 - self.x as f64; let y_dist: f64 = other.y as f64 - self.y as f64; @@ -211,7 +219,36 @@ impl Point { } } +/// Creates a random ellipse on the frame +fn create_random_ellipse(width: u16, height: u16) -> Ellipse { + Ellipse { + // random point on the frame is the centre point + centre: Point { + x: rand::thread_rng().gen_range(0..width) as i64, + y: rand::thread_rng().gen_range(0..height) as i64, + }, + // height and width of ellipse are capped at 1/5 of the + // respective dimension of the frame + height: rand::thread_rng().gen_range(1.0..height as f64 / 5.0), + width: rand::thread_rng().gen_range(1.0..width as f64 / 5.0), + // angle determines rotation of the ellipse + angle: rand::thread_rng().gen_range(0.0..PI), + // curr point is just set to 0 for now + curr_point: Point { x: 0, y: 0 }, + // determines clockwise or counter-clockwise direction + // of points on ellipse + direction: if rand::thread_rng().gen_range(0..=1) == 1 { + 1.0 + } else { + -1.0 + }, + } +} + +/// Generates a vector of `num_cells` `Ellipse`s that are used to +/// determine the points where cells should be calculated from fn generate_points(width: u16, height: u16, num_cells: usize) -> Vec<Ellipse> { + // create vector of 0'd ellipses let mut ellipses = vec![ Ellipse { centre: Point { x: 0, y: 0 }, @@ -224,19 +261,52 @@ fn generate_points(width: u16, height: u16, num_cells: usize) -> Vec<Ellipse> { num_cells ]; + // assign the ellipses values for ellipse in &mut ellipses { - ellipse.centre.x = rand::thread_rng().gen_range(0..width) as i64; - ellipse.centre.y = rand::thread_rng().gen_range(0..height) as i64; - ellipse.height = rand::thread_rng().gen_range(1.0..height as f64 / 5.0); - ellipse.width = rand::thread_rng().gen_range(1.0..width as f64 / 5.0); - ellipse.angle = rand::thread_rng().gen_range(0.0..2.0 * PI); - let direction = rand::thread_rng().gen_range(0..=1); - if direction == 1 { - ellipse.direction = 1.0; - } else { - ellipse.direction = -1.0; - } + *ellipse = create_random_ellipse(width, height); } ellipses } + +/// Show a progress bar and percent complete. +/// Should be run in a separate thread from the main +/// to prevent it from blocking the main thread +fn progress_bar(width: u16, max: u16, rx: mpsc::Receiver<u16>) { + for received in rx { + let percent_done = received as f64 / max as f64; + + for i in 0..(width as i32 - 6) { + if i < (percent_done * width as f64) as i32 { + print!("="); + } else { + print!("-"); + } + } + print!("[{}%]\r", (percent_done * 100.0) as i32); + } +} + +/// Generate the image itself +pub fn create_image(image: &mut Image, encoder: &mut gif::Encoder<&mut File>) { + let (tx, rx) = mpsc::channel(); // message passing variables + let width = termsize::get().unwrap().cols; // the width of the terminal + + // start progress bar thread + let frames = image.frames; + let t = thread::spawn(move || progress_bar(width, frames, rx)); + + for i in 0..image.frames { + tx.send(i).unwrap(); + // Create pixel array + image.generate_noise(i); + let frame = gif::Frame::from_rgb(image.width, image.height, &mut image.pixels); + + // Write frame to file + encoder.write_frame(&frame).unwrap(); + } + + // close the progress bar thread + drop(tx); + t.join().unwrap(); +} diff --git a/src/main.rs b/src/main.rs index b904808..49eee78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +//! # Writhing Mass of Flesh +//! +//! This program procedurally generates a GIF that resembles a writhing mass +//! of flesh. Originally, it was intended to be bubbles, but so it goes. /* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free @@ -14,16 +18,12 @@ */ use std::fs::File; use std::process; -use std::sync::mpsc; -use std::thread; use bubbles::Args; use bubbles::Image; fn main() { - let args = Args::read(); - let (tx, rx) = mpsc::channel(); - let width = termsize::get().unwrap().cols; + let args = Args::read(); // the arguments passed into the program // create Gif data let mut data = Image::create_from_args(&args); @@ -37,28 +37,6 @@ fn main() { process::exit(1); } - thread::spawn(move || { - for received in rx { - let percent_done = received as f64 / data.frames as f64; - - for i in 0..(width as i32 - 5) as i32 { - if i < (percent_done * width as f64) as i32 { - print!("="); - } else { - print!("-"); - } - } - print!("[{}%]\r", (percent_done * 100.0) as i32); - } - }); - - for i in 0..data.frames { - tx.send(i).unwrap(); - // Create pixel array - data.fill_canvas(i); - let frame = gif::Frame::from_rgb(data.width, data.height, &mut data.pixels); - - // Write frame to file - encoder.write_frame(&frame).unwrap(); - } + // create the image + bubbles::create_image(&mut data, &mut encoder); } |