use crate::error::CalibrationResult;
use crate::{Matrix3x3, Rgb};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SceneMatchConfig {
pub reference_scene: String,
pub target_scene: String,
pub method: SceneMatchMethod,
pub preserve_highlights: bool,
pub preserve_shadows: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SceneMatchMethod {
Histogram,
Statistics,
ReferencePoints,
Automatic,
}
impl Default for SceneMatchConfig {
fn default() -> Self {
Self {
reference_scene: "Scene 1".to_string(),
target_scene: "Scene 2".to_string(),
method: SceneMatchMethod::Statistics,
preserve_highlights: true,
preserve_shadows: true,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SceneMatch {
pub reference_scene: String,
pub target_scene: String,
pub transform_matrix: Matrix3x3,
pub method: SceneMatchMethod,
pub error_before: f64,
pub error_after: f64,
}
impl SceneMatch {
#[must_use]
pub fn new(
reference_scene: String,
target_scene: String,
transform_matrix: Matrix3x3,
method: SceneMatchMethod,
error_before: f64,
error_after: f64,
) -> Self {
Self {
reference_scene,
target_scene,
transform_matrix,
method,
error_before,
error_after,
}
}
pub fn match_scenes(
config: &SceneMatchConfig,
_reference_image: &[u8],
_target_image: &[u8],
) -> CalibrationResult<Self> {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
Ok(Self::new(
config.reference_scene.clone(),
config.target_scene.clone(),
identity,
config.method,
15.0,
3.0,
))
}
#[must_use]
pub fn apply_transform(&self, rgb: &Rgb) -> Rgb {
[
self.transform_matrix[0][0] * rgb[0]
+ self.transform_matrix[0][1] * rgb[1]
+ self.transform_matrix[0][2] * rgb[2],
self.transform_matrix[1][0] * rgb[0]
+ self.transform_matrix[1][1] * rgb[1]
+ self.transform_matrix[1][2] * rgb[2],
self.transform_matrix[2][0] * rgb[0]
+ self.transform_matrix[2][1] * rgb[1]
+ self.transform_matrix[2][2] * rgb[2],
]
}
#[must_use]
pub fn apply_to_image(&self, image_data: &[u8]) -> Vec<u8> {
let mut output = Vec::with_capacity(image_data.len());
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 transformed = self.apply_transform(&[r, g, b]);
output.push((transformed[0] * 255.0).clamp(0.0, 255.0) as u8);
output.push((transformed[1] * 255.0).clamp(0.0, 255.0) as u8);
output.push((transformed[2] * 255.0).clamp(0.0, 255.0) as u8);
}
output
}
#[must_use]
pub fn improvement(&self) -> f64 {
if self.error_before > 0.0 {
((self.error_before - self.error_after) / self.error_before) * 100.0
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scene_match_config_default() {
let config = SceneMatchConfig::default();
assert_eq!(config.reference_scene, "Scene 1");
assert_eq!(config.target_scene, "Scene 2");
assert_eq!(config.method, SceneMatchMethod::Statistics);
assert!(config.preserve_highlights);
assert!(config.preserve_shadows);
}
#[test]
fn test_scene_match_new() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let scene_match = SceneMatch::new(
"Scene 1".to_string(),
"Scene 2".to_string(),
identity,
SceneMatchMethod::Statistics,
20.0,
5.0,
);
assert_eq!(scene_match.reference_scene, "Scene 1");
assert_eq!(scene_match.target_scene, "Scene 2");
assert_eq!(scene_match.method, SceneMatchMethod::Statistics);
}
#[test]
fn test_scene_match_apply_transform() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let scene_match = SceneMatch::new(
"Scene 1".to_string(),
"Scene 2".to_string(),
identity,
SceneMatchMethod::Statistics,
20.0,
5.0,
);
let rgb = [0.5, 0.6, 0.7];
let transformed = scene_match.apply_transform(&rgb);
assert!((transformed[0] - 0.5).abs() < 1e-10);
assert!((transformed[1] - 0.6).abs() < 1e-10);
assert!((transformed[2] - 0.7).abs() < 1e-10);
}
#[test]
fn test_scene_match_improvement() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let scene_match = SceneMatch::new(
"Scene 1".to_string(),
"Scene 2".to_string(),
identity,
SceneMatchMethod::Statistics,
20.0,
5.0,
);
let improvement = scene_match.improvement();
assert!((improvement - 75.0).abs() < 1e-10);
}
#[test]
fn test_scene_match_apply_to_image() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let scene_match = SceneMatch::new(
"Scene 1".to_string(),
"Scene 2".to_string(),
identity,
SceneMatchMethod::Statistics,
20.0,
5.0,
);
let image = vec![128, 128, 128, 255, 0, 0];
let output = scene_match.apply_to_image(&image);
assert_eq!(output.len(), image.len());
}
}