///|
/// Deterministic noise generators for testing and denoise demos. All
/// randomness comes from a 64-bit LCG seeded by the caller, so a given
/// seed produces the identical image on every backend and every run —
/// keeping the library's reproducibility promise even for "random" ops.
///|
/// 64-bit linear congruential step (Knuth MMIX constants). UInt64
/// arithmetic wraps, which is exactly what an LCG needs.
fn lcg_next(state : UInt64) -> UInt64 {
state * 6364136223846793005UL + 1442695040888963407UL
}
///|
/// Extracts a uniform value in [0, 1) from the high bits of the state
/// (the low bits of an LCG are weak; the top 24 give a clean mantissa).
fn lcg_uniform(state : UInt64) -> Double {
((state >> 40) & 0xFFFFFFUL).to_uint().reinterpret_as_int().to_double() /
16777216.0
}
///|
/// Adds zero-mean Gaussian noise with standard deviation `sigma` (in luma
/// steps) to R, G and B; alpha is untouched. The normal deviate is the
/// classic CLT approximation (sum of 12 uniforms minus 6), which is
/// branch-free and deterministic. `sigma <= 0` returns a copy.
pub fn Image::add_gaussian_noise(
self : Image,
seed : UInt64,
sigma : Double,
) -> Image {
if sigma <= 0.0 {
return self.copy()
}
let out = self.copy()
let n = self.pixel_count()
let mut state = lcg_next(seed ^ 0x9E3779B97F4A7C15UL)
for p in 0..= 0.0 { scaled + 0.5 } else { scaled - 0.5 }
let delta = rounded.to_int()
for c in 0..<3 {
out.data[base + c] = clamp_byte(self.data[base + c].to_int() + delta)
}
}
out
}
///|
/// Salt-and-pepper noise: each pixel independently becomes pure white
/// ("salt") or pure black ("pepper") with probability `density / 2` each.
/// `density` is clamped into [0, 1]; alpha is preserved. Deterministic
/// for a given seed.
pub fn Image::add_salt_pepper(
self : Image,
seed : UInt64,
density : Double,
) -> Image {
let d = if density < 0.0 {
0.0
} else if density > 1.0 {
1.0
} else {
density
}
let out = self.copy()
if d == 0.0 {
return out
}
let n = self.pixel_count()
let mut state = lcg_next(seed ^ 0xD1B54A32D192ED03UL)
for p in 0..