From 0c2158a408570f1f75c14c6031d4d815c09c5d35 Mon Sep 17 00:00:00 2001 From: mindv0rtex Date: Tue, 2 Mar 2021 16:18:04 -0500 Subject: [PATCH] Add colors --- src/blur.rs | 2 +- src/lib.rs | 3 +- src/model.rs | 51 ++++++++++++++++++++------- src/palette.rs | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 src/palette.rs diff --git a/src/blur.rs b/src/blur.rs index 37ab8b7..fd56d8e 100644 --- a/src/blur.rs +++ b/src/blur.rs @@ -13,7 +13,7 @@ impl Blur { } } - /// Blur an image with 3 box filter passes. The result will be written to the src slice, while + /// Blur an image with 2 box filter passes. The result will be written to the src slice, while /// the buf slice is used as a scratch space. pub fn run( &mut self, diff --git a/src/lib.rs b/src/lib.rs index c132ed6..a2f9cd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod blur; mod grid; pub mod model; -pub mod trig; +mod palette; +pub mod trig; // for benchmarking mod util; diff --git a/src/model.rs b/src/model.rs index 2df6762..d6b80f5 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,9 +1,13 @@ -use crate::grid::{combine, Grid, PopulationConfig}; +use crate::{ + grid::{combine, Grid, PopulationConfig}, + palette::{random_palette, Palette}, +}; use rand::{seq::SliceRandom, Rng}; use rand_distr::{Distribution, Normal}; use rayon::prelude::*; +use itertools::multizip; use std::f32::consts::TAU; /// A single Physarum agent. The x and y positions are continuous, hence we use floating point @@ -46,7 +50,6 @@ impl Agent { } /// Top-level simulation class. -#[derive(Debug)] pub struct Model { // Physarum agents. agents: Vec, @@ -62,6 +65,8 @@ pub struct Model { // Current model iteration. iteration: i32, + + palette: Palette, } impl Model { @@ -117,6 +122,7 @@ impl Model { attraction_table, diffusivity, iteration: 0, + palette: random_palette(), } } @@ -138,8 +144,7 @@ impl Model { pub fn step(&mut self) { // Combine grids let grids = &mut self.grids; - let attraction_table = &self.attraction_table; - combine(grids, attraction_table); + combine(grids, &self.attraction_table); self.agents.par_iter_mut().for_each(|agent| { let grid = &grids[agent.population_id]; @@ -159,7 +164,8 @@ impl Model { let xr = agent.x + (agent.angle + sensor_angle).cos() * sensor_distance; let yr = agent.y + (agent.angle + sensor_angle).sin() * sensor_distance; - // Sense + // Sense. We sense from the buffer because this is where we previously combined data + // from all the grid. let trail_c = grid.get_buf(xc, yc); let trail_l = grid.get_buf(xl, yl); let trail_r = grid.get_buf(xr, yr); @@ -185,16 +191,35 @@ impl Model { /// Output the current trail layer as a grayscale image. pub fn save_to_image(&self, name: &str) { - let mut img = - image::GrayImage::new(self.grids[0].width as u32, self.grids[0].height as u32); - let max_value = self.grids[0].quantile(0.999); + let (width, height) = (self.grids[0].width, self.grids[0].height); + let mut img = image::RgbImage::new(width as u32, height as u32); - for (i, value) in self.grids[0].data().iter().enumerate() { - let x = (i % self.grids[0].width) as u32; - let y = (i / self.grids[0].width) as u32; - let c = (value / max_value).clamp(0.0, 1.0) * 255.0; - img.put_pixel(x, y, image::Luma([c as u8])); + let max_values: Vec<_> = self + .grids + .iter() + .map(|grid| grid.quantile(0.999) * 1.5) + .collect(); + + for y in 0..height { + for x in 0..width { + let i = y * width + x; + let (mut r, mut g, mut b) = (0.0_f32, 0.0_f32, 0.0_f32); + for (grid, max_value, color) in + multizip((&self.grids, &max_values, &self.palette.colors)) + { + let mut t = (grid.data()[i] / max_value).clamp(0.0, 1.0); + t = t.powf(1.0 / 2.2); // gamma correction + r += color.0[0] as f32 * t; + g += color.0[1] as f32 * t; + b += color.0[2] as f32 * t; + } + r = r.clamp(0.0, 255.0); + g = g.clamp(0.0, 255.0); + b = b.clamp(0.0, 255.0); + img.put_pixel(x as u32, y as u32, image::Rgb([r as u8, g as u8, b as u8])); + } } + img.save(name).unwrap(); } } diff --git a/src/palette.rs b/src/palette.rs new file mode 100644 index 0000000..8c6006c --- /dev/null +++ b/src/palette.rs @@ -0,0 +1,95 @@ +use rand::{seq::SliceRandom, thread_rng, Rng}; + +#[derive(Clone, Copy)] +pub struct Palette { + pub colors: [image::Rgb; 5], +} + +pub fn random_palette() -> Palette { + let mut rng = thread_rng(); + let mut palette = PALETTES[rng.gen_range(0..PALETTES.len())]; + palette.colors.shuffle(&mut rng); + palette +} + +const fn hex_to_color(c: usize) -> image::Rgb { + let r = (c >> 16) & 0xff; + let g = (c >> 8) & 0xff; + let b = (c >> 0) & 0xff; + image::Rgb::([r as u8, g as u8, b as u8]) +} + +const PALETTES: [Palette; 8] = [ + Palette { + colors: [ + hex_to_color(0xFA2B31), + hex_to_color(0xFFBF1F), + hex_to_color(0xFFF146), + hex_to_color(0xABE319), + hex_to_color(0x00C481), + ], + }, + Palette { + colors: [ + hex_to_color(0x004358), + hex_to_color(0x1F8A70), + hex_to_color(0xBEDB39), + hex_to_color(0xFFE11A), + hex_to_color(0xFD7400), + ], + }, + Palette { + colors: [ + hex_to_color(0x334D5C), + hex_to_color(0x45B29D), + hex_to_color(0xEFC94C), + hex_to_color(0xE27A3F), + hex_to_color(0xDF5A49), + ], + }, + Palette { + colors: [ + hex_to_color(0xFF8000), + hex_to_color(0xFFD933), + hex_to_color(0xCCCC52), + hex_to_color(0x8FB359), + hex_to_color(0x192B33), + ], + }, + Palette { + colors: [ + hex_to_color(0x730046), + hex_to_color(0xBFBB11), + hex_to_color(0xFFC200), + hex_to_color(0xE88801), + hex_to_color(0xC93C00), + ], + }, + Palette { + colors: [ + hex_to_color(0xE6DD00), + hex_to_color(0x8CB302), + hex_to_color(0x008C74), + hex_to_color(0x004C66), + hex_to_color(0x332B40), + ], + }, + Palette { + colors: [ + hex_to_color(0xF15A5A), + hex_to_color(0xF0C419), + hex_to_color(0x4EBA6F), + hex_to_color(0x2D95BF), + hex_to_color(0x955BA5), + ], + }, + Palette { + colors: [ + hex_to_color(0xF41C54), + hex_to_color(0xFF9F00), + hex_to_color(0xFBD506), + hex_to_color(0xA8BF12), + hex_to_color(0x00AAB5), + ], + }, +];