rawshift_image/transforms/
black_level.rs1use crate::core::image::RawImage;
7
8pub fn apply_black_level(raw: &mut RawImage) {
15 let bl = *raw.black_levels();
16 if bl[0] == 0 && bl[1] == 0 && bl[2] == 0 && bl[3] == 0 {
17 return;
18 }
19
20 let width = raw.width() as usize;
21 for (i, pixel) in raw.data.iter_mut().enumerate() {
22 let x = i % width;
23 let y = i / width;
24 let bl_idx = (y % 2) * 2 + (x % 2);
25 *pixel = pixel.saturating_sub(bl[bl_idx]);
26 }
27}
28
29#[cfg(test)]
30mod tests {
31 use super::*;
32 use crate::core::image::{CfaPattern, Rect, Size};
33
34 fn make_raw(width: u32, height: u32, data: Vec<u16>, black_levels: [u16; 4]) -> RawImage {
35 let size = Size::new(width, height);
36 let active = Rect::from_coords(0, 0, width, height);
37 let mut raw = RawImage::new(size, active, 14, CfaPattern::Rggb);
38 raw.data = data;
39 raw.set_black_levels(black_levels);
40 raw
41 }
42
43 #[test]
44 fn no_op_when_all_zero() {
45 let original = vec![100, 200, 300, 400];
46 let mut raw = make_raw(2, 2, original.clone(), [0, 0, 0, 0]);
47 apply_black_level(&mut raw);
48 assert_eq!(raw.data, original);
49 }
50
51 #[test]
52 fn uniform_black_level() {
53 let mut raw = make_raw(2, 2, vec![100, 200, 300, 400], [50, 50, 50, 50]);
54 apply_black_level(&mut raw);
55 assert_eq!(raw.data, vec![50, 150, 250, 350]);
56 }
57
58 #[test]
59 fn per_channel_subtraction() {
60 let mut raw = make_raw(2, 2, vec![1000, 1000, 1000, 1000], [10, 20, 30, 40]);
63 apply_black_level(&mut raw);
64 assert_eq!(raw.data, vec![990, 980, 970, 960]);
65 }
66
67 #[test]
68 fn saturating_subtraction_does_not_underflow() {
69 let mut raw = make_raw(2, 1, vec![5, 10], [100, 100, 100, 100]);
70 apply_black_level(&mut raw);
71 assert_eq!(raw.data, vec![0, 0]);
72 }
73
74 #[test]
75 fn test_black_level_all_channels() {
76 let data = vec![1000u16, 2000, 3000, 4000];
79 let black_levels = [100u16, 200, 300, 400];
80 let mut raw = make_raw(2, 2, data, black_levels);
81 apply_black_level(&mut raw);
82 assert_eq!(raw.data[0], 900, "(0,0) R channel");
83 assert_eq!(raw.data[1], 1800, "(1,0) G channel");
84 assert_eq!(raw.data[2], 2700, "(0,1) G channel");
85 assert_eq!(raw.data[3], 3600, "(1,1) B channel");
86 }
87
88 #[test]
89 fn test_black_level_clamps_at_zero() {
90 let data = vec![50u16, 100, 150, 200];
92 let black_levels = [100u16, 100, 100, 100];
93 let mut raw = make_raw(2, 2, data, black_levels);
94 apply_black_level(&mut raw);
95 assert_eq!(raw.data[0], 0, "50 - 100 should clamp to 0");
96 assert_eq!(raw.data[1], 0, "100 - 100 should equal 0");
97 assert_eq!(raw.data[2], 50, "150 - 100 should be 50");
98 assert_eq!(raw.data[3], 100, "200 - 100 should be 100");
99 }
100
101 #[test]
102 fn test_black_level_preserves_above_white() {
103 let data = vec![65000u16, 65535, 50000, 40000];
106 let black_levels = [100u16, 100, 100, 100];
107 let mut raw = make_raw(2, 2, data, black_levels);
108 apply_black_level(&mut raw);
109 assert_eq!(
110 raw.data[0], 64900,
111 "high value should have black subtracted"
112 );
113 assert_eq!(raw.data[1], 65435, "max value minus black level");
114 assert_eq!(raw.data[2], 49900);
115 assert_eq!(raw.data[3], 39900);
116 }
117}