aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacob janzen <jacob.a.s.janzen@gmail.com>2022-08-28 12:55:57 -0500
committerjacob janzen <jacob.a.s.janzen@gmail.com>2022-08-28 12:55:57 -0500
commita8cf2c052a457e743c2e9fc95ba3d4463f8511fa (patch)
tree476174344e44de7d2949808eefd2b265bebf8d13
parente8e391abb2789034138b430e6e129a20f6aa6d1a (diff)
refactor
-rw-r--r--output.gifbin17824097 -> 17079984 bytes
-rw-r--r--src/lib.rs102
-rw-r--r--src/main.rs36
3 files changed, 93 insertions, 45 deletions
diff --git a/output.gif b/output.gif
index f2c0388..115c935 100644
--- a/output.gif
+++ b/output.gif
Binary files differ
diff --git a/src/lib.rs b/src/lib.rs
index 0601ddf..8d784ba 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
}