From 0bfec05ee0a40af937ec4f8aa812245dcd90bf64 Mon Sep 17 00:00:00 2001 From: Jacob Janzen Date: Tue, 23 Aug 2022 11:31:41 -0500 Subject: created project --- .gitignore | 1 + Cargo.lock | 98 ++++++++++++++++++++++++++++++++++ Cargo.toml | 10 ++++ output.gif | Bin 0 -> 464257 bytes src/lib.rs | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 32 +++++++++++ 6 files changed, 316 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 output.gif create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6c4f14a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,98 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bubbles" +version = "0.1.0" +dependencies = [ + "gif", + "rand", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9432a77 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bubbles" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gif = "0.11.4" +rand = "0.8.5" diff --git a/output.gif b/output.gif new file mode 100644 index 0000000..2beec7d Binary files /dev/null and b/output.gif differ diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..53cd599 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,175 @@ +use rand::Rng; +use std::vec; + +pub struct Config { + pub height: u16, + pub width: u16, + pub frames: u16, + pub out_file: String, +} + +impl Config { + pub fn build(mut args: impl Iterator) -> Result { + args.next(); + + let width: u16 = match args.next() { + Some(arg) => match arg.parse() { + Ok(num) => num, + Err(_) => return Err("Width is not a number"), + }, + None => return Err("Didn't get a width"), + }; + + let height: u16 = match args.next() { + Some(arg) => match arg.parse() { + Ok(num) => num, + Err(_) => return Err("Height is not a number"), + }, + None => return Err("Didn't get a height"), + }; + + let frames: u16 = match args.next() { + Some(arg) => match arg.parse() { + Ok(num) => num, + Err(_) => return Err("Frames is not a number"), + }, + None => return Err("Didn't get a frame count"), + }; + + let out_file = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get an output file"), + }; + + Ok(Config { + height, + width, + frames, + out_file, + }) + } +} + +#[derive(Clone)] +struct PointData { + min_dist: f64, + closest_point: Point, +} + +impl PointData { + fn get_point_data(gif: &Gif, p: Point) -> Self { + let mut pd = PointData { + min_dist: gif.cross_distance, + closest_point: Point { x: 0, y: 0 }, + }; + + for point in &gif.points { + let d = distance(&p, point); + if d < pd.min_dist { + pd.min_dist = d; + pd.closest_point = point.clone(); + } + } + + pd + } +} + +pub struct Gif { + pub height: u16, + pub width: u16, + pub frames: u16, + pub pixels: Vec, + point_data: Vec, + cross_distance: f64, + points: Vec, +} + +impl Gif { + pub fn create_from_config(config: &Config, num_cells: usize) -> Self { + Gif { + height: config.height, + width: config.width, + frames: config.frames, + pixels: vec![0; config.height as usize * config.width as usize * 3], + point_data: vec![ + PointData { + min_dist: 0.0, + closest_point: Point { x: 0, y: 0 } + }; + config.height as usize * config.width as usize + ], + cross_distance: distance( + &Point { x: 0, y: 0 }, + &Point { + x: config.width - 1, + y: config.height - 1, + }, + ), + points: generate_points(config.width, config.height, num_cells), + } + } +} + +#[derive(Clone)] +struct Point { + pub x: u16, + pub y: u16, +} + +pub fn fill_canvas(gif: &mut Gif) { + generate_noise(gif); +} + +fn set_pixel(gif: &mut Gif, r: u8, g: u8, b: u8, x: u16, y: u16) { + gif.pixels[3 * (gif.width as usize * y as usize + x as usize)] = r; + gif.pixels[3 * (gif.width as usize * y as usize + x as usize) + 1] = g; + gif.pixels[3 * (gif.width as usize * y as usize + x as usize) + 2] = b; +} + +fn generate_noise(gif: &mut Gif) { + let mut max_dist = 0.0; + + // Get distance and nearest point for each point on the canvas + for y in 0..gif.height { + for x in 0..gif.width { + let index = y as usize * gif.width as usize + x as usize; + gif.point_data[index] = PointData::get_point_data(gif, Point { x, y }); + max_dist = f64::max(max_dist, gif.point_data[index].min_dist); + } + } + + // normalize distances to [0,1] + for y in 0..gif.height { + for x in 0..gif.width { + let index = y as usize * gif.width as usize + x as usize; + gif.point_data[index].min_dist /= max_dist; + } + } + + for y in 0..gif.height { + for x in 0..gif.width { + let index = y as usize * gif.width as usize + x as usize; + let val = 0xFF - (0xFF as f64 * gif.point_data[index].min_dist) as u8; + set_pixel(gif, val, val, val, x, y) + } + } +} + +fn generate_points(width: u16, height: u16, num_cells: usize) -> Vec { + let mut points = vec![Point { x: 0, y: 0 }; num_cells]; + + for p in &mut points { + p.x = rand::thread_rng().gen_range(0..width); + p.y = rand::thread_rng().gen_range(0..height); + } + + points +} + +fn distance(p1: &Point, p2: &Point) -> f64 { + let x_dist: f64 = p2.x as f64 - p1.x as f64; + let y_dist: f64 = p2.y as f64 - p1.y as f64; + + (x_dist * x_dist + y_dist * y_dist).sqrt() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..39c6117 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +use std::env; +use std::fs::File; +use std::process; + +use bubbles::Config; +use bubbles::Gif; + +fn main() { + let config = Config::build(env::args()).unwrap_or_else(|err| { + eprintln!("Error printing arguments: {err}"); + process::exit(1); + }); + + // create Gif data + let mut gif = Gif::create_from_config(&config, 100); + + // Create encoder + let mut image = File::create(config.out_file).unwrap(); + let mut encoder = gif::Encoder::new(&mut image, config.width, config.height, &[]).unwrap(); + + // Repeat infinitely + if let Err(_) = encoder.set_repeat(gif::Repeat::Infinite) { + process::exit(1); + } + + // Create pixel array + bubbles::fill_canvas(&mut gif); + let frame = gif::Frame::from_rgb(gif.width, gif.height, &mut gif.pixels); + + // Write frame to file + encoder.write_frame(&frame).unwrap(); +} -- cgit v1.2.3