use crate::cfa::{CfaPattern, Channel};
pub fn demosaic(input: &[f32], width: usize, height: usize, cfa: &CfaPattern, output: &mut [f32]) {
let npix = width * height;
for y in 0..height {
for x in 0..width {
let idx = y * width + x;
let ch = cfa.color_at(y, x);
let r_off = 0;
let g_off = npix;
let b_off = 2 * npix;
output[ch as usize * npix + idx] = input[idx];
match ch {
Channel::Red => {
output[g_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_G_AT_RB, 8.0);
output[b_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_RB_AT_OPPOSITE, 16.0);
}
Channel::Blue => {
output[g_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_G_AT_RB, 8.0);
output[r_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_RB_AT_OPPOSITE, 16.0);
}
Channel::Green => {
let adj_x = x ^ 1;
let adj_ch = cfa.color_at(y, adj_x);
if adj_ch == Channel::Red {
output[r_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_RB_AT_G_SAME_ROW, 16.0);
output[b_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_RB_AT_G_SAME_COL, 16.0);
} else {
output[b_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_RB_AT_G_SAME_ROW, 16.0);
output[r_off + idx] =
apply_kernel(input, width, height, y, x, &KERNEL_RB_AT_G_SAME_COL, 16.0);
}
}
}
}
}
}
#[inline]
fn apply_kernel(
input: &[f32],
width: usize,
height: usize,
y: usize,
x: usize,
kernel: &[i32; 25],
divisor: f32,
) -> f32 {
let mut sum = 0.0f32;
let mut k = 0;
for ky in -2i32..=2 {
let ny = (y as i32 + ky).clamp(0, height as i32 - 1) as usize;
for kx in -2i32..=2 {
let nx = (x as i32 + kx).clamp(0, width as i32 - 1) as usize;
sum += kernel[k] as f32 * input[ny * width + nx];
k += 1;
}
}
sum / divisor
}
#[rustfmt::skip]
const KERNEL_G_AT_RB: [i32; 25] = [
0, 0, -1, 0, 0,
0, 0, 2, 0, 0,
-1, 2, 4, 2, -1,
0, 0, 2, 0, 0,
0, 0, -1, 0, 0,
];
#[rustfmt::skip]
const KERNEL_RB_AT_G_SAME_ROW: [i32; 25] = [
0, 0, 1, 0, 0,
0, -2, 0, -2, 0,
-2, 8, 10, 8, -2,
0, -2, 0, -2, 0,
0, 0, 1, 0, 0,
];
#[rustfmt::skip]
const KERNEL_RB_AT_G_SAME_COL: [i32; 25] = [
0, 0, -2, 0, 0,
0, -2, 8, -2, 0,
1, 0, 10, 0, 1,
0, -2, 8, -2, 0,
0, 0, -2, 0, 0,
];
#[rustfmt::skip]
const KERNEL_RB_AT_OPPOSITE: [i32; 25] = [
0, 0, -3, 0, 0,
0, 4, 0, 4, 0,
-3, 0, 12, 0, -3,
0, 4, 0, 4, 0,
0, 0, -3, 0, 0,
];
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
#[test]
fn known_channel_preserved() {
for cfa in &[
CfaPattern::bayer_rggb(),
CfaPattern::bayer_bggr(),
CfaPattern::bayer_grbg(),
CfaPattern::bayer_gbrg(),
] {
let w = 16;
let h = 16;
let mut input = vec![0.0f32; w * h];
for y in 0..h {
for x in 0..w {
input[y * w + x] = match cfa.color_at(y, x) {
Channel::Red => 0.8,
Channel::Green => 0.5,
Channel::Blue => 0.3,
};
}
}
let mut output = vec![0.0f32; 3 * w * h];
demosaic(&input, w, h, cfa, &mut output);
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
let ch = cfa.color_at(y, x) as usize;
assert_eq!(
output[ch * w * h + idx], input[idx],
"known channel mismatch at ({y},{x}) ch={ch}"
);
}
}
}
}
#[test]
fn uniform_color_reconstructed() {
for cfa in &[
CfaPattern::bayer_rggb(),
CfaPattern::bayer_bggr(),
CfaPattern::bayer_grbg(),
CfaPattern::bayer_gbrg(),
] {
let w = 16;
let h = 16;
let mut input = vec![0.0f32; w * h];
for y in 0..h {
for x in 0..w {
input[y * w + x] = match cfa.color_at(y, x) {
Channel::Red => 0.8,
Channel::Green => 0.5,
Channel::Blue => 0.3,
};
}
}
let mut output = vec![0.0f32; 3 * w * h];
demosaic(&input, w, h, cfa, &mut output);
for y in 3..h - 3 {
for x in 3..w - 3 {
let idx = y * w + x;
let r = output[idx];
let g = output[w * h + idx];
let b = output[2 * w * h + idx];
assert!((r - 0.8).abs() < 0.05, "R at ({y},{x}) = {r}");
assert!((g - 0.5).abs() < 0.05, "G at ({y},{x}) = {g}");
assert!((b - 0.3).abs() < 0.05, "B at ({y},{x}) = {b}");
}
}
}
}
}