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"
|
||||
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",
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
extern crate blue_noise_mask;
|
||||
extern crate float4;
|
||||
extern crate halton;
|
||||
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