233 lines
6.4 KiB
Rust
233 lines
6.4 KiB
Rust
// 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::<Vec<_>>();
|
|
|
|
// 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::<f32>();
|
|
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<f32>,
|
|
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]
|
|
}
|
|
} |