use crate::error::CalibrationResult;
use crate::Rgb;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum WhiteBalancePreset {
Daylight,
Cloudy,
Shade,
Tungsten,
Fluorescent,
Flash,
Custom(f64, f64, f64),
}
impl WhiteBalancePreset {
#[must_use]
pub fn multipliers(&self) -> [f64; 3] {
match self {
Self::Daylight => [1.0, 1.0, 1.0],
Self::Cloudy => [1.0, 1.0, 1.15],
Self::Shade => [1.0, 1.0, 1.25],
Self::Tungsten => [1.0, 0.85, 0.65],
Self::Fluorescent => [1.0, 0.95, 1.05],
Self::Flash => [1.0, 1.0, 1.0],
Self::Custom(r, g, b) => [*r, *g, *b],
}
}
#[must_use]
pub fn color_temperature(&self) -> u32 {
match self {
Self::Daylight => 6000,
Self::Cloudy => 7000,
Self::Shade => 7500,
Self::Tungsten => 3000,
Self::Fluorescent => 4500,
Self::Flash => 5500,
Self::Custom(_, _, _) => 5500,
}
}
#[must_use]
pub fn apply(&self, rgb: &Rgb) -> Rgb {
let mult = self.multipliers();
[
(rgb[0] * mult[0]).clamp(0.0, 1.0),
(rgb[1] * mult[1]).clamp(0.0, 1.0),
(rgb[2] * mult[2]).clamp(0.0, 1.0),
]
}
pub fn apply_to_image(&self, image_data: &[u8]) -> CalibrationResult<Vec<u8>> {
let mut output = Vec::with_capacity(image_data.len());
let mult = self.multipliers();
for chunk in image_data.chunks_exact(3) {
let r = f64::from(chunk[0]) / 255.0;
let g = f64::from(chunk[1]) / 255.0;
let b = f64::from(chunk[2]) / 255.0;
let corrected = [
(r * mult[0]).clamp(0.0, 1.0),
(g * mult[1]).clamp(0.0, 1.0),
(b * mult[2]).clamp(0.0, 1.0),
];
output.push((corrected[0] * 255.0).round() as u8);
output.push((corrected[1] * 255.0).round() as u8);
output.push((corrected[2] * 255.0).round() as u8);
}
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_daylight_multipliers() {
let preset = WhiteBalancePreset::Daylight;
let mult = preset.multipliers();
assert!((mult[0] - 1.0).abs() < 1e-10);
assert!((mult[1] - 1.0).abs() < 1e-10);
assert!((mult[2] - 1.0).abs() < 1e-10);
}
#[test]
fn test_tungsten_multipliers() {
let preset = WhiteBalancePreset::Tungsten;
let mult = preset.multipliers();
assert!((mult[0] - 1.0).abs() < 1e-10);
assert!((mult[1] - 0.85).abs() < 1e-10);
assert!((mult[2] - 0.65).abs() < 1e-10);
}
#[test]
fn test_custom_multipliers() {
let preset = WhiteBalancePreset::Custom(1.2, 1.0, 0.8);
let mult = preset.multipliers();
assert!((mult[0] - 1.2).abs() < 1e-10);
assert!((mult[1] - 1.0).abs() < 1e-10);
assert!((mult[2] - 0.8).abs() < 1e-10);
}
#[test]
fn test_color_temperatures() {
assert_eq!(WhiteBalancePreset::Daylight.color_temperature(), 6000);
assert_eq!(WhiteBalancePreset::Tungsten.color_temperature(), 3000);
assert_eq!(WhiteBalancePreset::Fluorescent.color_temperature(), 4500);
}
#[test]
fn test_apply() {
let preset = WhiteBalancePreset::Daylight;
let rgb = [0.5, 0.5, 0.5];
let result = preset.apply(&rgb);
assert!((result[0] - 0.5).abs() < 1e-10);
assert!((result[1] - 0.5).abs() < 1e-10);
assert!((result[2] - 0.5).abs() < 1e-10);
}
#[test]
fn test_apply_tungsten() {
let preset = WhiteBalancePreset::Tungsten;
let rgb = [0.5, 0.5, 0.5];
let result = preset.apply(&rgb);
assert!((result[0] - 0.5).abs() < 1e-10);
assert!((result[1] - 0.425).abs() < 1e-10);
assert!((result[2] - 0.325).abs() < 1e-10);
}
#[test]
fn test_apply_to_image() {
let preset = WhiteBalancePreset::Daylight;
let image = vec![128, 128, 128, 255, 0, 0];
let result = preset.apply_to_image(&image);
assert!(result.is_ok());
let output = result.expect("expected successful result");
assert_eq!(output.len(), image.len());
}
}