use crate::core::image::{RawImage, RgbImage};
pub fn apply_white_balance_raw(image: &mut RawImage, coeffs: (f32, f32, f32)) {
let (r_scale, g_scale, b_scale) = coeffs;
let pattern = image.cfa_pattern().to_array();
let scale_for = |color: u8| -> f32 {
match color {
0 => r_scale,
2 => b_scale,
_ => g_scale,
}
};
let gains: [f32; 4] = [
scale_for(pattern[0]),
scale_for(pattern[1]),
scale_for(pattern[2]),
scale_for(pattern[3]),
];
let width = image.width() as usize;
for (idx, pixel) in image.data.iter_mut().enumerate() {
let x = idx % width;
let y = idx / width;
let gain = gains[(y % 2) * 2 + (x % 2)];
let val = *pixel as f32 * gain;
*pixel = clamp_u16(val);
}
}
pub fn apply_white_balance(image: &mut RgbImage, coeffs: (f32, f32, f32)) {
let (r_scale, g_scale, b_scale) = coeffs;
for chunk in image.data.chunks_exact_mut(3) {
let r = chunk[0] as f32 * r_scale;
chunk[0] = clamp_u16(r);
let g = chunk[1] as f32 * g_scale;
chunk[1] = clamp_u16(g);
let b = chunk[2] as f32 * b_scale;
chunk[2] = clamp_u16(b);
}
}
pub fn apply_color_matrix(image: &mut RgbImage, matrix: &[f32; 9]) {
for chunk in image.data.chunks_exact_mut(3) {
let r = chunk[0] as f32;
let g = chunk[1] as f32;
let b = chunk[2] as f32;
let r_out = r * matrix[0] + g * matrix[1] + b * matrix[2];
let g_out = r * matrix[3] + g * matrix[4] + b * matrix[5];
let b_out = r * matrix[6] + g * matrix[7] + b * matrix[8];
chunk[0] = clamp_u16(r_out);
chunk[1] = clamp_u16(g_out);
chunk[2] = clamp_u16(b_out);
}
}
pub struct GammaLut {
table: Box<[u16; 65536]>,
gamma: f32,
}
impl GammaLut {
#[must_use]
pub fn new(gamma: f32) -> Self {
let mut table = Box::new([0u16; 65536]);
let inv_gamma = 1.0 / gamma;
for (i, v) in table.iter_mut().enumerate() {
let normalized = i as f32 / 65535.0;
let corrected = normalized.powf(inv_gamma);
*v = clamp_u16(corrected * 65535.0);
}
Self { table, gamma }
}
#[must_use]
pub fn gamma(&self) -> f32 {
self.gamma
}
pub fn apply(&self, image: &mut RgbImage) {
for pixel in &mut image.data {
*pixel = self.table[*pixel as usize];
}
}
}
pub fn apply_gamma(image: &mut RgbImage, gamma: f32) {
if (gamma - 1.0).abs() < 0.001 {
return;
}
let lut = GammaLut::new(gamma);
lut.apply(image);
}
#[inline(always)]
pub fn clamp_u16(val: f32) -> u16 {
val.clamp(0.0, 65535.0) as u16
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::image::{CfaPattern, Rect, Size};
fn create_test_raw_image(width: u32, height: u32, pattern: CfaPattern) -> RawImage {
let size = Size::new(width, height);
let active = Rect::from_coords(0, 0, width, height);
RawImage::new(size, active, 14, pattern)
}
#[test]
fn test_white_balance_raw_identity() {
let mut image = create_test_raw_image(4, 4, CfaPattern::Rggb);
for pixel in &mut image.data {
*pixel = 1000;
}
apply_white_balance_raw(&mut image, (1.0, 1.0, 1.0));
for &pixel in &image.data {
assert_eq!(pixel, 1000);
}
}
#[test]
fn test_white_balance_raw_rggb() {
let mut image = create_test_raw_image(4, 4, CfaPattern::Rggb);
for pixel in &mut image.data {
*pixel = 1000;
}
apply_white_balance_raw(&mut image, (2.0, 1.0, 0.5));
assert_eq!(image.data[0], 2000);
assert_eq!(image.data[1], 1000);
assert_eq!(image.data[2], 2000);
assert_eq!(image.data[4], 1000);
assert_eq!(image.data[5], 500);
assert_eq!(image.data[6], 1000);
assert_eq!(image.data[7], 500);
}
#[test]
fn test_white_balance_raw_bggr() {
let mut image = create_test_raw_image(2, 2, CfaPattern::Bggr);
for pixel in &mut image.data {
*pixel = 1000;
}
apply_white_balance_raw(&mut image, (3.0, 1.0, 2.0));
assert_eq!(image.data[0], 2000); assert_eq!(image.data[1], 1000); assert_eq!(image.data[2], 1000); assert_eq!(image.data[3], 3000); }
#[test]
fn test_white_balance_raw_grbg() {
let mut image = create_test_raw_image(2, 2, CfaPattern::Grbg);
for pixel in &mut image.data {
*pixel = 1000;
}
apply_white_balance_raw(&mut image, (2.0, 1.0, 3.0));
assert_eq!(image.data[0], 1000); assert_eq!(image.data[1], 2000); assert_eq!(image.data[2], 3000); assert_eq!(image.data[3], 1000); }
#[test]
fn test_white_balance_raw_gbrg() {
let mut image = create_test_raw_image(2, 2, CfaPattern::Gbrg);
for pixel in &mut image.data {
*pixel = 1000;
}
apply_white_balance_raw(&mut image, (2.0, 1.0, 3.0));
assert_eq!(image.data[0], 1000); assert_eq!(image.data[1], 3000); assert_eq!(image.data[2], 2000); assert_eq!(image.data[3], 1000); }
#[test]
fn test_white_balance_raw_clamps() {
let mut image = create_test_raw_image(2, 2, CfaPattern::Rggb);
image.data[0] = 60000; image.data[1] = 30000; image.data[2] = 60000; image.data[3] = 30000; apply_white_balance_raw(&mut image, (2.0, 2.0, 2.0));
assert_eq!(image.data[0], 65535); assert_eq!(image.data[1], 60000); }
fn create_test_image(width: u32, height: u32, r: u16, g: u16, b: u16) -> RgbImage {
let mut data = Vec::with_capacity((width * height * 3) as usize);
for _ in 0..(width * height) {
data.push(r);
data.push(g);
data.push(b);
}
RgbImage::new(width, height, data)
}
#[test]
fn test_clamp_u16() {
assert_eq!(clamp_u16(0.0), 0);
assert_eq!(clamp_u16(100.5), 100);
assert_eq!(clamp_u16(65535.0), 65535);
assert_eq!(clamp_u16(-100.0), 0);
assert_eq!(clamp_u16(100000.0), 65535);
}
#[test]
fn test_white_balance_identity() {
let mut image = create_test_image(2, 2, 1000, 2000, 3000);
apply_white_balance(&mut image, (1.0, 1.0, 1.0));
for i in 0..4 {
assert_eq!(image.data[i * 3], 1000);
assert_eq!(image.data[i * 3 + 1], 2000);
assert_eq!(image.data[i * 3 + 2], 3000);
}
}
#[test]
fn test_white_balance_scaling() {
let mut image = create_test_image(2, 2, 1000, 2000, 3000);
apply_white_balance(&mut image, (2.0, 1.0, 0.5));
for i in 0..4 {
assert_eq!(image.data[i * 3], 2000); assert_eq!(image.data[i * 3 + 1], 2000); assert_eq!(image.data[i * 3 + 2], 1500); }
}
#[test]
fn test_white_balance_clamps() {
let mut image = create_test_image(1, 1, 60000, 30000, 1000);
apply_white_balance(&mut image, (2.0, 2.0, 0.0));
assert_eq!(image.data[0], 65535); assert_eq!(image.data[1], 60000); assert_eq!(image.data[2], 0); }
#[test]
fn test_color_matrix_identity() {
let identity_matrix: [f32; 9] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let mut image = create_test_image(2, 2, 1000, 2000, 3000);
apply_color_matrix(&mut image, &identity_matrix);
for i in 0..4 {
assert_eq!(image.data[i * 3], 1000);
assert_eq!(image.data[i * 3 + 1], 2000);
assert_eq!(image.data[i * 3 + 2], 3000);
}
}
#[test]
fn test_color_matrix_swap_channels() {
let swap_matrix: [f32; 9] = [0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0];
let mut image = create_test_image(1, 1, 1000, 2000, 3000);
apply_color_matrix(&mut image, &swap_matrix);
assert_eq!(image.data[0], 3000); assert_eq!(image.data[1], 2000); assert_eq!(image.data[2], 1000); }
#[test]
fn test_gamma_identity() {
let mut image = create_test_image(2, 2, 1000, 2000, 3000);
let original = image.data.clone();
apply_gamma(&mut image, 1.0);
assert_eq!(image.data, original);
}
#[test]
fn test_gamma_22() {
let mut image = create_test_image(1, 1, 0, 32768, 65535);
apply_gamma(&mut image, 2.2);
assert_eq!(image.data[0], 0);
assert_eq!(image.data[2], 65535);
assert!(
image.data[1] > 32768,
"Mid-tone {} should be > 32768",
image.data[1]
);
}
#[test]
fn test_gamma_lut_new() {
let lut = GammaLut::new(2.2);
assert_eq!(lut.gamma(), 2.2);
}
#[test]
fn test_gamma_lut_apply() {
let lut = GammaLut::new(2.2);
let mut image = create_test_image(1, 1, 0, 32768, 65535);
lut.apply(&mut image);
assert_eq!(image.data[0], 0);
assert_eq!(image.data[2], 65535);
assert!(
image.data[1] > 32768,
"Mid-tone should be brighter after gamma"
);
}
#[test]
fn test_gamma_lut_reuse() {
let lut = GammaLut::new(2.2);
let mut image1 = create_test_image(1, 1, 1000, 2000, 3000);
let mut image2 = create_test_image(1, 1, 1000, 2000, 3000);
lut.apply(&mut image1);
lut.apply(&mut image2);
assert_eq!(image1.data, image2.data);
}
}