Added a blue noise mask generator as a sub-crate.

This commit is contained in:
Nathan Vegdahl 2017-05-13 03:23:55 -07:00
parent 172e2f19ef
commit d8a33c7bfa
6 changed files with 257 additions and 1 deletions

5
Cargo.lock generated
View File

@ -22,6 +22,10 @@ name = "bitflags"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blue_noise_mask"
version = "0.1.0"
[[package]]
name = "c_vec"
version = "1.0.12"
@ -153,6 +157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "psychopath"
version = "0.1.0"
dependencies = [
"blue_noise_mask 0.1.0",
"clap 2.23.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"float4 0.1.0",

View File

@ -1,5 +1,6 @@
[workspace]
members = [
"sub_crates/blue_noise_mask",
"sub_crates/float4",
"sub_crates/halton",
"sub_crates/math3d",
@ -36,6 +37,9 @@ lazy_static = "0.2"
openexr = { git = "https://github.com/cessen/openexr-rs", rev = "612fc6c81c031970ffddcab15509236711613de8" }
# Local crate dependencies
[dependencies.blue_noise_mask]
path = "sub_crates/blue_noise_mask"
[dependencies.float4]
path = "sub_crates/float4"

View File

@ -1,4 +1,4 @@
extern crate blue_noise_mask;
extern crate float4;
extern crate halton;
extern crate math3d;

View File

@ -0,0 +1,10 @@
[package]
name = "blue_noise_mask"
version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"]
license = "MIT"
build = "build.rs"
[lib]
name = "blue_noise_mask"
path = "src/lib.rs"

View File

@ -0,0 +1,233 @@
// 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]
}
}

View File

@ -0,0 +1,4 @@
#![allow(dead_code)]
// Include the file generated by the build.rs script
include!(concat!(env!("OUT_DIR"), "/blue_noise_masks.rs"));