///|
/// Post-processing effects: bloom, vignette, color grading, bilateral denoising.
pub fn apply_vignette(pixels : Array[Vec3], width : Int, height : Int, strength~ : Double) -> Array[Vec3] {
let result = Array::new(capacity=pixels.length())
let cx = width.to_double() / 2.0
let cy = height.to_double() / 2.0
let max_dist = (cx * cx + cy * cy).sqrt()
for j in 0.. Array[Vec3] {
let bloom = Array::new(capacity=pixels.length())
for j in 0.. threshold {
bloom.push(pixels[idx].mul_scalar(intensity * (lum - threshold) / lum.max(0.001)))
} else {
bloom.push({ x: 0.0, y: 0.0, z: 0.0 })
}
}
}
let kernel_size = 5
let bloom_blurred = Array::new(capacity=bloom.length())
for j in 0..= 0 && nx < width && ny >= 0 && ny < height {
let den = dx.to_double() * dx.to_double() + dy.to_double() * dy.to_double()
let weight = gaussian_filter(den.sqrt(), 3.0)
accum = accum + bloom[ny * width + nx].mul_scalar(weight)
count = count + weight
}
}
}
if count > 0.0 {
accum = accum.div_scalar(count)
}
bloom_blurred.push(accum)
}
}
let result = Array::new(capacity=pixels.length())
for j in 0.. Array[Vec3] {
let result = Array::new(capacity=pixels.length())
let lum_weights = { x: 0.2126, y: 0.7152, z: 0.0722 }
for j in 0.. Array[Vec3] {
let result = Array::new(capacity=pixels.length())
let mut rng = default_rng()
for j in 0.. Array[Vec3] {
let r = radius.max(0)
let sigma_s = sigma_spatial.max(0.001)
let sigma_c = sigma_color.max(0.001)
let window = 2 * r + 1
let spatial_inv = 1.0 / (2.0 * sigma_s * sigma_s)
let color_inv = 1.0 / (2.0 * sigma_c * sigma_c)
// Precompute spatial Gaussian weights for the filter window.
let spatial = Array::new(capacity=window * window)
for dy in -r..=r {
for dx in -r..=r {
let d2 = (dx * dx + dy * dy).to_double()
spatial.push(@math.exp(-d2 * spatial_inv))
}
}
let result = Array::new(capacity=pixels.length())
for j in 0..= height {
wi = wi + window
continue
}
for dx in -r..=r {
let nx = i + dx
if nx >= 0 && nx < width {
let neighbor = pixels[ny * width + nx]
let diff = neighbor - center
let dist2 = diff.length_squared()
let weight = spatial[wi] * @math.exp(-dist2 * color_inv)
if weight > 0.0001 {
accum = accum + neighbor.mul_scalar(weight)
total_weight = total_weight + weight
}
}
wi = wi + 1
}
}
if total_weight > 0.0 {
result.push(accum.div_scalar(total_weight))
} else {
result.push(center)
}
}
}
result
}
/// Backward-compatible alias of `bilateral_denoise` (default kernel).
pub fn edge_aware_denoise_stub(pixels : Array[Vec3], width : Int, height : Int) -> Array[Vec3] {
bilateral_denoise(pixels, width, height)
}