// Generate Blue Noise Mask tables. use std::cmp::Ordering; use std::env; use std::fs::File; use std::io::Write; use std::ops::{Index, IndexMut}; use std::path::Path; const WINDOW_RADIUS: isize = 6; const FILTER_WIDTH: f32 = 1.0; // These are specified in powers of two (2^N) for fast wrapping // in the generated Rust code. const NUM_MASKS_POW: usize = 7; // 128 const MASK_SIZE_POW: usize = 7; // 128 fn main() { let out_dir = env::var("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("blue_noise_masks.rs"); let mut f = File::create(&dest_path).unwrap(); // Generate masks let masks = (0..(1 << NUM_MASKS_POW) as u32).map(|i| blue_noise_mask(1 << MASK_SIZE_POW, i)).collect::>(); // Write the beginning bits of the file f.write_all(format!(r#" // This file is automatically generated. // Compute points of the Halton sequence with with Faure-permutations for different bases. pub const NUM_MASKS: u32 = {}; pub const MASK_SIZE: u32 = {}; const NUM_MASKS_WRAP_BITMASK: u32 = {}; const MASK_SIZE_WRAP_BITMASK: u32 = {}; const MASK_POINTS: u32 = {}; pub fn get_point(mask_i: u32, x: u32, y: u32) -> f32 {{ let mask_i = mask_i & NUM_MASKS_WRAP_BITMASK; let x = x & MASK_SIZE_WRAP_BITMASK; let y = y & MASK_SIZE_WRAP_BITMASK; unsafe {{ *MASKS.get_unchecked(((mask_i * MASK_POINTS) + (y * MASK_SIZE) + x) as usize) }} }} "#, 1 << NUM_MASKS_POW, 1 << MASK_SIZE_POW, (1 << NUM_MASKS_POW) - 1, (1 << MASK_SIZE_POW) - 1, (1 << MASK_SIZE_POW) * (1 << MASK_SIZE_POW), ).as_bytes()).unwrap(); // Write the mask data f.write_all(format!(r#" const MASKS: [f32; {}] = [ "#, (1 << MASK_SIZE_POW) * (1 << MASK_SIZE_POW) * (1 << NUM_MASKS_POW)).as_bytes()).unwrap(); for mask in masks.iter() { for v in mask.data.iter() { f.write_all(format!(r#" {:.8}, "#, v).as_bytes()).unwrap(); } } f.write_all(format!(r#" ]; "#).as_bytes()).unwrap(); } /// Creates a blue noise mask fn blue_noise_mask(tile_size: usize, seed: u32) -> Image { let mut image = Image::new(tile_size, tile_size); for (i, v) in image.data.iter_mut().enumerate() { *v = hash_u32_to_f32(i as u32, seed); } // High pass and remap for _ in 0..2 { high_pass_filter(&mut image, WINDOW_RADIUS, FILTER_WIDTH); remap_values(&mut image); } image } /// High pass filter for an Image fn high_pass_filter(image: &mut Image, window_radius: isize, filter_width: f32) { // Precompute filter convolution matrix let conv = { let mut conv = Image::new(window_radius as usize * 2 + 1, window_radius as usize * 2 + 1); for j in (-window_radius)..window_radius { for i in (-window_radius)..window_radius { let n = (((j*j) + (i*i)) as f32).sqrt(); //let n = (j.abs() + i.abs()) as f32; conv.set_wrapped(gauss(n, filter_width), i,j); } } let inv_total = 1.0f32 / conv.data.iter().sum::(); for v in conv.data.iter_mut() { *v *= inv_total; } conv }; // Compute the low-pass image let mut low_pass_img = Image::new(image.width, image.height); for y in 0..image.height { for x in 0..image.width { for j in (-window_radius as isize)..window_radius { for i in (-window_radius as isize)..window_radius { let b = y as isize + j; let a = x as isize + i; let alpha = conv.get_wrapped(i, j); low_pass_img[(x as isize, y as isize)] += image.get_wrapped(a, b) * alpha; } } } } // Subtract low pass from original for i in 0..image.data.len() { image.data[i] -= low_pass_img.data[i] - 0.5; } } /// Remaps the values in an Image to be linearly distributed within [0, 1] fn remap_values(image: &mut Image) { let mut vals = Vec::with_capacity(image.width * image.height); for y in 0..image.height { for x in 0..image.width { vals.push((image[(x as isize, y as isize)], x, y)); } } vals.sort_by(|a, b| { if a < b { Ordering::Less } else { Ordering::Greater } }); let inc = 1.0 / (image.data.len() - 1) as f32; let mut nor_v = 0.0; for v in vals.iter() { image[(v.1 as isize, v.2 as isize)] = nor_v; nor_v += inc; } } /// Normal distribution fn gauss(x: f32, sd: f32) -> f32 { let norm = 1.0 / (2.0 * std::f32::consts::PI * sd * sd).sqrt(); let dist = ((-x * x) / (2.0 * sd * sd)).exp(); norm * dist } /// Returns a random float in [0, 1] based on 'n' and a seed. /// Generally use n for getting a bunch of different random /// numbers, and use seed to vary between runs. pub fn hash_u32_to_f32(n: u32, seed: u32) -> f32 { let mut hash = n; for _ in 0..8 { hash = hash.wrapping_mul(1936502639); hash ^= hash.wrapping_shr(16); hash = hash.wrapping_add(seed); } const INV_MAX: f32 = 1.0 / std::u32::MAX as f32; return hash as f32 * INV_MAX; } struct Image { data: Vec, width: usize, height: usize, } impl Image { fn new(width: usize, height: usize) -> Image { Image { data: vec![0.0; width * height], width: width, height: height, } } fn get_wrapped(&self, mut ix: isize, mut iy: isize) -> f32 { while ix < 0 { ix += self.width as isize; } while iy < 1 { iy += self.height as isize; } let x = ix as usize % self.width; let y = iy as usize % self.height; self.data[y * self.width + x] } fn set_wrapped(&mut self, v: f32, mut ix: isize, mut iy: isize){ while ix < 0 { ix += self.width as isize; } while iy < 1 { iy += self.height as isize; } let x = ix as usize % self.width; let y = iy as usize % self.height; self.data[y * self.width + x] = v } } impl Index<(isize, isize)> for Image { type Output = f32; fn index(&self, index: (isize, isize)) -> &f32 { &self.data[index.1 as usize * self.width + index.0 as usize] } } impl IndexMut<(isize, isize)> for Image { fn index_mut(&mut self, index: (isize, isize)) -> &mut f32 { &mut self.data[index.1 as usize * self.width + index.0 as usize] } }