Added a blue noise mask generator as a sub-crate.
This commit is contained in:
parent
172e2f19ef
commit
d8a33c7bfa
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -22,6 +22,10 @@ name = "bitflags"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blue_noise_mask"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "c_vec"
|
name = "c_vec"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
@ -153,6 +157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
name = "psychopath"
|
name = "psychopath"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"blue_noise_mask 0.1.0",
|
||||||
"clap 2.23.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"float4 0.1.0",
|
"float4 0.1.0",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"sub_crates/blue_noise_mask",
|
||||||
"sub_crates/float4",
|
"sub_crates/float4",
|
||||||
"sub_crates/halton",
|
"sub_crates/halton",
|
||||||
"sub_crates/math3d",
|
"sub_crates/math3d",
|
||||||
|
@ -36,6 +37,9 @@ lazy_static = "0.2"
|
||||||
openexr = { git = "https://github.com/cessen/openexr-rs", rev = "612fc6c81c031970ffddcab15509236711613de8" }
|
openexr = { git = "https://github.com/cessen/openexr-rs", rev = "612fc6c81c031970ffddcab15509236711613de8" }
|
||||||
|
|
||||||
# Local crate dependencies
|
# Local crate dependencies
|
||||||
|
[dependencies.blue_noise_mask]
|
||||||
|
path = "sub_crates/blue_noise_mask"
|
||||||
|
|
||||||
[dependencies.float4]
|
[dependencies.float4]
|
||||||
path = "sub_crates/float4"
|
path = "sub_crates/float4"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
|
extern crate blue_noise_mask;
|
||||||
extern crate float4;
|
extern crate float4;
|
||||||
extern crate halton;
|
extern crate halton;
|
||||||
extern crate math3d;
|
extern crate math3d;
|
||||||
|
|
10
sub_crates/blue_noise_mask/Cargo.toml
Normal file
10
sub_crates/blue_noise_mask/Cargo.toml
Normal 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"
|
233
sub_crates/blue_noise_mask/build.rs
Normal file
233
sub_crates/blue_noise_mask/build.rs
Normal 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]
|
||||||
|
}
|
||||||
|
}
|
4
sub_crates/blue_noise_mask/src/lib.rs
Normal file
4
sub_crates/blue_noise_mask/src/lib.rs
Normal 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"));
|
Loading…
Reference in New Issue
Block a user