///|
/// Generates a 1D Gaussian kernel with given radius and standard deviation sigma.
pub fn generate_gaussian_kernel_1d(
radius : Int,
sigma : Double,
) -> Array[Double] {
let size = 2 * radius + 1
let kernel = Array::make(size, 0.0)
let two_sigma_sq = 2.0 * sigma * sigma
let mut sum = 0.0
for i in 0.. 1.0e-12 {
for i in 0.. Image {
let w = img.width
let h = img.height
if w == 0 || h == 0 || radius <= 0 {
return img.clone()
}
let kernel = generate_gaussian_kernel_1d(radius, sigma)
let k_len = kernel.length()
// Horizontal pass
let temp = Image::new(w, h)
for y in 0..= w { w - 1 } else { ix }
sum += img.get(cx, y).to_double() * kernel[k]
}
temp.set(x, y, sum.round().to_int())
}
}
// Vertical pass
let res = Image::new(w, h)
for y in 0..= h { h - 1 } else { iy }
sum += temp.get(x, cy).to_double() * kernel[k]
}
res.set(x, y, sum.round().to_int())
}
}
res
}
///|
/// Applies fast box blur with given kernel radius using integral image.
pub fn filter_box_blur(img : Image, radius : Int) -> Image {
let w = img.width
let h = img.height
let res = Image::new(w, h)
if w == 0 || h == 0 || radius <= 0 {
return img.clone()
}
let integral = compute_integral_image(img)
for y in 0..= w { w - 1 } else { x + radius }
let y1 = if y + radius >= h { h - 1 } else { y + radius }
let count = (x1 - x0 + 1) * (y1 - y0 + 1)
let sum_val = query_integral_sum(integral, w, h, x0, y0, x1, y1)
let avg = sum_val / count
res.set(x, y, avg)
}
}
res
}
///|
/// Applies 3x3 or 5x5 median filter for salt-and-pepper noise reduction.
pub fn filter_median(img : Image, radius : Int) -> Image {
let w = img.width
let h = img.height
let res = Image::new(w, h)
if w == 0 || h == 0 || radius <= 0 {
return img.clone()
}
let r = radius
let size = (2 * r + 1) * (2 * r + 1)
let window = Array::make(size, 0)
let mid = size / 2
for y in 0..= w { w - 1 } else { ix }
let cy = if iy < 0 { 0 } else if iy >= h { h - 1 } else { iy }
window[count] = img.get(cx, cy)
count += 1
}
}
// Simple insertion sort on small window
for i in 1..= 0 && window[j] > key {
window[j + 1] = window[j]
j -= 1
}
window[j + 1] = key
}
res.set(x, y, window[mid])
}
}
res
}
///|
/// Edge-preserving bilateral filter using spatial Gaussian and photometric range Gaussian.
pub fn filter_bilateral(
img : Image,
radius : Int,
sigma_space : Double,
sigma_color : Double,
) -> Image {
let w = img.width
let h = img.height
let res = Image::new(w, h)
if w == 0 || h == 0 || radius <= 0 {
return img.clone()
}
let two_sigma_s_sq = 2.0 * sigma_space * sigma_space
let two_sigma_c_sq = 2.0 * sigma_color * sigma_color
for y in 0..= w { w - 1 } else { ix }
let cy = if iy < 0 { 0 } else if iy >= h { h - 1 } else { iy }
let neighbor_val = img.get(cx, cy).to_double()
let dist_spatial_sq = (dx * dx + dy * dy).to_double()
let dist_color_sq = (neighbor_val - center_val) *
(neighbor_val - center_val)
let w_s = @math.exp(-dist_spatial_sq / two_sigma_s_sq)
let w_c = @math.exp(-dist_color_sq / two_sigma_c_sq)
let weight = w_s * w_c
sum_filtered += neighbor_val * weight
sum_weights += weight
}
}
if sum_weights > 1.0e-12 {
res.set(x, y, (sum_filtered / sum_weights).round().to_int())
} else {
res.set(x, y, center_val.round().to_int())
}
}
}
res
}
///|
/// Computes Sobel 3x3 horizontal (Gx) and vertical (Gy) gradients.
/// Returns (Gx, Gy) images with signed double precision in flat arrays.
pub fn filter_sobel(img : Image) -> (Array[Double], Array[Double]) {
let w = img.width
let h = img.height
let size = w * h
let gx = Array::make(size, 0.0)
let gy = Array::make(size, 0.0)
for y in 0.. (Array[Double], Array[Double]) {
let w = img.width
let h = img.height
let size = w * h
let gx = Array::make(size, 0.0)
let gy = Array::make(size, 0.0)
for y in 0.. (Array[Double], Array[Double]) {
let size = gx.length()
let mag = Array::make(size, 0.0)
let angle = Array::make(size, 0.0)
for i in 0.. Image {
let w = img.width
let h = img.height
let res = Image::new(w, h)
for y in 0..