1pub fn apply_gains_rgb(data: &mut [u16], gains: [f32; 3], max_value: u16) {
41 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
42 {
43 unsafe { apply_gains_rgb_avx2(data, gains, max_value) };
46 return;
47 }
48 #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
49 apply_gains_rgb_scalar(data, gains, max_value);
50}
51
52fn apply_gains_rgb_scalar(data: &mut [u16], gains: [f32; 3], max_value: u16) {
53 let max_f = max_value as f32;
54 for chunk in data.chunks_exact_mut(3) {
55 for (val, &g) in chunk.iter_mut().zip(gains.iter()) {
56 *val = ((*val as f32 * g).clamp(0.0, max_f)) as u16;
57 }
58 }
59}
60
61#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
62unsafe fn apply_gains_rgb_avx2(data: &mut [u16], gains: [f32; 3], max_value: u16) {
63 use std::arch::x86_64::*;
64
65 let _ = unsafe { _mm256_setzero_ps() }; apply_gains_rgb_scalar(data, gains, max_value);
73}
74
75pub fn apply_matrix_rgb(data: &mut [u16], matrix: &[[f64; 3]; 3], max_value: u16) {
93 let max_f = max_value as f64;
94 for chunk in data.chunks_exact_mut(3) {
95 let r = chunk[0] as f64;
96 let g = chunk[1] as f64;
97 let b = chunk[2] as f64;
98 chunk[0] =
99 (matrix[0][0] * r + matrix[0][1] * g + matrix[0][2] * b).clamp(0.0, max_f) as u16;
100 chunk[1] =
101 (matrix[1][0] * r + matrix[1][1] * g + matrix[1][2] * b).clamp(0.0, max_f) as u16;
102 chunk[2] =
103 (matrix[2][0] * r + matrix[2][1] * g + matrix[2][2] * b).clamp(0.0, max_f) as u16;
104 }
105}
106
107pub fn subtract_black_level_uniform(data: &mut [u16], black_level: u16) {
121 for val in data.iter_mut() {
122 *val = val.saturating_sub(black_level);
123 }
124}
125
126#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
135 fn test_apply_gains_rgb_identity() {
136 let mut data = vec![1000u16, 2000, 3000, 4000, 5000, 6000];
137 apply_gains_rgb(&mut data, [1.0, 1.0, 1.0], 65535);
138 assert_eq!(data, vec![1000, 2000, 3000, 4000, 5000, 6000]);
139 }
140
141 #[test]
142 fn test_apply_gains_rgb_double() {
143 let mut data = vec![100u16, 200, 300];
144 apply_gains_rgb(&mut data, [2.0, 2.0, 2.0], 65535);
145 assert_eq!(data, vec![200, 400, 600]);
146 }
147
148 #[test]
149 fn test_apply_gains_rgb_clamps() {
150 let mut data = vec![50000u16, 50000, 50000];
151 apply_gains_rgb(&mut data, [2.0, 2.0, 2.0], 65535);
152 assert_eq!(data, vec![65535, 65535, 65535]);
153 }
154
155 #[test]
156 fn test_apply_gains_rgb_clamps_to_max_value() {
157 let mut data = vec![1000u16, 2000, 3000];
158 apply_gains_rgb(&mut data, [10.0, 10.0, 10.0], 4095);
159 assert!(data.iter().all(|&v| v <= 4095));
160 }
161
162 #[test]
163 fn test_apply_gains_rgb_per_channel() {
164 let mut data = vec![100u16, 200, 300];
165 apply_gains_rgb(&mut data, [2.0, 1.0, 0.5], 65535);
166 assert_eq!(data[0], 200);
167 assert_eq!(data[1], 200);
168 assert_eq!(data[2], 150);
169 }
170
171 #[test]
172 fn test_apply_gains_rgb_empty() {
173 let mut data: Vec<u16> = vec![];
174 apply_gains_rgb(&mut data, [1.0, 1.0, 1.0], 65535);
175 assert!(data.is_empty());
176 }
177
178 #[test]
179 fn test_apply_gains_rgb_trailing_samples_ignored() {
180 let mut data = vec![100u16, 200, 300, 400, 500, 600, 700];
182 apply_gains_rgb(&mut data, [2.0, 2.0, 2.0], 65535);
183 assert_eq!(data[0], 200);
184 assert_eq!(data[1], 400);
185 assert_eq!(data[2], 600);
186 assert_eq!(data[3], 800);
187 assert_eq!(data[4], 1000);
188 assert_eq!(data[5], 1200);
189 assert_eq!(data[6], 700); }
191
192 #[test]
195 fn test_apply_matrix_rgb_identity() {
196 let identity = [[1.0f64, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
197 let mut data = vec![1000u16, 2000, 3000];
198 apply_matrix_rgb(&mut data, &identity, 65535);
199 assert_eq!(data, vec![1000, 2000, 3000]);
200 }
201
202 #[test]
203 fn test_apply_matrix_rgb_scale() {
204 let scale = [[2.0f64, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 2.0]];
205 let mut data = vec![100u16, 200, 300];
206 apply_matrix_rgb(&mut data, &scale, 65535);
207 assert_eq!(data, vec![200, 400, 600]);
208 }
209
210 #[test]
211 fn test_apply_matrix_rgb_clamps() {
212 let scale = [[10.0f64, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]];
213 let mut data = vec![10000u16, 10000, 10000];
214 apply_matrix_rgb(&mut data, &scale, 65535);
215 assert_eq!(data, vec![65535, 65535, 65535]);
216 }
217
218 #[test]
219 fn test_apply_matrix_rgb_channel_mix() {
220 let swap = [[0.0f64, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]];
222 let mut data = vec![100u16, 200, 300];
223 apply_matrix_rgb(&mut data, &swap, 65535);
224 assert_eq!(data, vec![300, 200, 100]);
225 }
226
227 #[test]
228 fn test_apply_matrix_rgb_empty() {
229 let identity = [[1.0f64, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
230 let mut data: Vec<u16> = vec![];
231 apply_matrix_rgb(&mut data, &identity, 65535);
232 assert!(data.is_empty());
233 }
234
235 #[test]
238 fn test_subtract_black_level_uniform() {
239 let mut data = vec![500u16, 1000, 200, 0];
240 subtract_black_level_uniform(&mut data, 300);
241 assert_eq!(data, vec![200, 700, 0, 0]); }
243
244 #[test]
245 fn test_subtract_black_level_uniform_zero() {
246 let mut data = vec![100u16, 200, 300];
247 subtract_black_level_uniform(&mut data, 0);
248 assert_eq!(data, vec![100, 200, 300]);
249 }
250
251 #[test]
252 fn test_subtract_black_level_uniform_all_saturate() {
253 let mut data = vec![50u16, 10, 0, 100];
254 subtract_black_level_uniform(&mut data, 200);
255 assert_eq!(data, vec![0, 0, 0, 0]);
256 }
257
258 #[test]
259 fn test_subtract_black_level_uniform_exact() {
260 let mut data = vec![300u16];
261 subtract_black_level_uniform(&mut data, 300);
262 assert_eq!(data, vec![0]);
263 }
264
265 #[test]
266 fn test_subtract_black_level_uniform_empty() {
267 let mut data: Vec<u16> = vec![];
268 subtract_black_level_uniform(&mut data, 100);
269 assert!(data.is_empty());
270 }
271}