aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock98
-rw-r--r--Cargo.toml10
-rw-r--r--output.gifbin0 -> 464257 bytes
-rw-r--r--src/lib.rs175
-rw-r--r--src/main.rs32
6 files changed, 316 insertions, 0 deletions
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
--- /dev/null
+++ b/output.gif
Binary files 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<Item = String>) -> Result<Self, &'static str> {
+ 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<u8>,
+ point_data: Vec<PointData>,
+ cross_distance: f64,
+ points: Vec<Point>,
+}
+
+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<Point> {
+ 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();
+}