use crate::{DitherFloat, DitherFloatConversion, SpatialRng};
#[cfg(not(feature = "nightly_f16"))]
use common_traits::{CastableFrom, Number};
#[cfg(feature = "nightly_f16")]
use common_traits_f16::{CastableFrom, Number};
use enum_dispatch::enum_dispatch;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InterleavedGradientNoise {
x_offset: u32,
y_offset: u32,
}
impl InterleavedGradientNoise {
pub fn new(seed: u32) -> Self {
Self {
x_offset: seed.wrapping_mul(5),
y_offset: seed.wrapping_mul(7),
}
}
}
impl SpatialRng for InterleavedGradientNoise {
#[inline(always)]
fn compute(&self, x: u32, y: u32) -> f32 {
let x_offset = x.wrapping_add(self.x_offset);
let y_offset = y.wrapping_add(self.y_offset);
let value = (52.982_918
* ((0.06711056 * x_offset as f32 + 0.00583715 * y_offset as f32)
.fract()))
.fract();
value * 2.0 - 1.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SpatialHash {
seed: u32,
}
impl SpatialHash {
pub fn new(seed: u32) -> Self {
Self { seed }
}
}
impl SpatialRng for SpatialHash {
#[inline(always)]
fn compute(&self, x: u32, y: u32) -> f32 {
let mut hash = x;
hash = hash.wrapping_mul(1664525).wrapping_add(y);
hash = hash.wrapping_mul(1664525).wrapping_add(self.seed);
hash ^= hash >> 16;
hash = hash.wrapping_mul(0x85ebca6b);
hash ^= hash >> 13;
hash = hash.wrapping_mul(0xc2b2ae35);
hash ^= hash >> 16;
(hash as f32 / u32::MAX as f32) * 2.0 - 1.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlueNoiseApprox {
ign: InterleavedGradientNoise,
spatial: SpatialHash,
}
impl BlueNoiseApprox {
pub fn new(seed: u32) -> Self {
Self {
ign: InterleavedGradientNoise::new(seed),
spatial: SpatialHash::new(seed.wrapping_add(1337)),
}
}
}
impl SpatialRng for BlueNoiseApprox {
#[inline(always)]
fn compute(&self, x: u32, y: u32) -> f32 {
let ign = self.ign.compute(x, y);
let hash = self.spatial.compute(x >> 1, y >> 1);
(ign * 0.75 + hash * 0.25).clamp(-1.0, 1.0)
}
}
#[cfg(feature = "blue-noise")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlueNoise {
x_offset: u32,
y_offset: u32,
channel: usize,
}
#[cfg(feature = "blue-noise")]
impl BlueNoise {
pub fn new(seed: u32) -> Self {
Self {
x_offset: seed.wrapping_mul(13),
y_offset: seed.wrapping_mul(17),
channel: ((seed >> 16) & 0x3) as usize,
}
}
}
#[cfg(feature = "blue-noise")]
impl SpatialRng for BlueNoise {
#[inline(always)]
fn compute(&self, x: u32, y: u32) -> f32 {
let x_offset = x.wrapping_add(self.x_offset);
let y_offset = y.wrapping_add(self.y_offset);
let table_x = (x_offset & 0xFF) as usize;
let table_y = (y_offset & 0xFF) as usize;
crate::blue_noise::BLUE_NOISE_TABLE[table_y][table_x][self.channel]
* 2.0
- 1.0
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[enum_dispatch(SpatialRng)]
pub enum SpatialDither {
InterleavedGradientNoise(InterleavedGradientNoise),
SpatialHash(SpatialHash),
BlueNoiseApprox(BlueNoiseApprox),
#[cfg(feature = "blue-noise")]
BlueNoise(BlueNoise),
}