1use crate::core::image::{RawImage, RgbImage};
11
12pub fn apply_white_balance_raw(image: &mut RawImage, coeffs: (f32, f32, f32)) {
22 let (r_scale, g_scale, b_scale) = coeffs;
23 let pattern = image.cfa_pattern().to_array();
24
25 let scale_for = |color: u8| -> f32 {
28 match color {
29 0 => r_scale,
30 2 => b_scale,
31 _ => g_scale,
32 }
33 };
34 let gains: [f32; 4] = [
35 scale_for(pattern[0]),
36 scale_for(pattern[1]),
37 scale_for(pattern[2]),
38 scale_for(pattern[3]),
39 ];
40
41 let width = image.width() as usize;
42 for (idx, pixel) in image.data.iter_mut().enumerate() {
43 let x = idx % width;
44 let y = idx / width;
45 let gain = gains[(y % 2) * 2 + (x % 2)];
46 let val = *pixel as f32 * gain;
47 *pixel = clamp_u16(val);
48 }
49}
50
51pub fn apply_white_balance(image: &mut RgbImage, coeffs: (f32, f32, f32)) {
66 let (r_scale, g_scale, b_scale) = coeffs;
67
68 for chunk in image.data.chunks_exact_mut(3) {
70 let r = chunk[0] as f32 * r_scale;
72 chunk[0] = clamp_u16(r);
73
74 let g = chunk[1] as f32 * g_scale;
76 chunk[1] = clamp_u16(g);
77
78 let b = chunk[2] as f32 * b_scale;
80 chunk[2] = clamp_u16(b);
81 }
82}
83
84pub fn apply_color_matrix(image: &mut RgbImage, matrix: &[f32; 9]) {
99 for chunk in image.data.chunks_exact_mut(3) {
100 let r = chunk[0] as f32;
101 let g = chunk[1] as f32;
102 let b = chunk[2] as f32;
103
104 let r_out = r * matrix[0] + g * matrix[1] + b * matrix[2];
105 let g_out = r * matrix[3] + g * matrix[4] + b * matrix[5];
106 let b_out = r * matrix[6] + g * matrix[7] + b * matrix[8];
107
108 chunk[0] = clamp_u16(r_out);
109 chunk[1] = clamp_u16(g_out);
110 chunk[2] = clamp_u16(b_out);
111 }
112}
113
114pub struct GammaLut {
126 table: Box<[u16; 65536]>,
127 gamma: f32,
128}
129
130impl GammaLut {
131 #[must_use]
136 pub fn new(gamma: f32) -> Self {
137 let mut table = Box::new([0u16; 65536]);
138 let inv_gamma = 1.0 / gamma;
139
140 for (i, v) in table.iter_mut().enumerate() {
141 let normalized = i as f32 / 65535.0;
142 let corrected = normalized.powf(inv_gamma);
143 *v = clamp_u16(corrected * 65535.0);
144 }
145
146 Self { table, gamma }
147 }
148
149 #[must_use]
151 pub fn gamma(&self) -> f32 {
152 self.gamma
153 }
154
155 pub fn apply(&self, image: &mut RgbImage) {
157 for pixel in &mut image.data {
158 *pixel = self.table[*pixel as usize];
159 }
160 }
161}
162
163pub fn apply_gamma(image: &mut RgbImage, gamma: f32) {
175 if (gamma - 1.0).abs() < 0.001 {
177 return;
178 }
179
180 let lut = GammaLut::new(gamma);
181 lut.apply(image);
182}
183
184#[inline(always)]
192pub fn clamp_u16(val: f32) -> u16 {
193 val.clamp(0.0, 65535.0) as u16
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::core::image::{CfaPattern, Rect, Size};
200
201 fn create_test_raw_image(width: u32, height: u32, pattern: CfaPattern) -> RawImage {
202 let size = Size::new(width, height);
203 let active = Rect::from_coords(0, 0, width, height);
204 RawImage::new(size, active, 14, pattern)
205 }
206
207 #[test]
208 fn test_white_balance_raw_identity() {
209 let mut image = create_test_raw_image(4, 4, CfaPattern::Rggb);
210 for pixel in &mut image.data {
212 *pixel = 1000;
213 }
214 apply_white_balance_raw(&mut image, (1.0, 1.0, 1.0));
215
216 for &pixel in &image.data {
217 assert_eq!(pixel, 1000);
218 }
219 }
220
221 #[test]
222 fn test_white_balance_raw_rggb() {
223 let mut image = create_test_raw_image(4, 4, CfaPattern::Rggb);
224 for pixel in &mut image.data {
225 *pixel = 1000;
226 }
227 apply_white_balance_raw(&mut image, (2.0, 1.0, 0.5));
228
229 assert_eq!(image.data[0], 2000);
232 assert_eq!(image.data[1], 1000);
234 assert_eq!(image.data[2], 2000);
236 assert_eq!(image.data[4], 1000);
238 assert_eq!(image.data[5], 500);
240 assert_eq!(image.data[6], 1000);
242 assert_eq!(image.data[7], 500);
244 }
245
246 #[test]
247 fn test_white_balance_raw_bggr() {
248 let mut image = create_test_raw_image(2, 2, CfaPattern::Bggr);
249 for pixel in &mut image.data {
250 *pixel = 1000;
251 }
252 apply_white_balance_raw(&mut image, (3.0, 1.0, 2.0));
253
254 assert_eq!(image.data[0], 2000); assert_eq!(image.data[1], 1000); assert_eq!(image.data[2], 1000); assert_eq!(image.data[3], 3000); }
260
261 #[test]
262 fn test_white_balance_raw_grbg() {
263 let mut image = create_test_raw_image(2, 2, CfaPattern::Grbg);
264 for pixel in &mut image.data {
265 *pixel = 1000;
266 }
267 apply_white_balance_raw(&mut image, (2.0, 1.0, 3.0));
268
269 assert_eq!(image.data[0], 1000); assert_eq!(image.data[1], 2000); assert_eq!(image.data[2], 3000); assert_eq!(image.data[3], 1000); }
275
276 #[test]
277 fn test_white_balance_raw_gbrg() {
278 let mut image = create_test_raw_image(2, 2, CfaPattern::Gbrg);
279 for pixel in &mut image.data {
280 *pixel = 1000;
281 }
282 apply_white_balance_raw(&mut image, (2.0, 1.0, 3.0));
283
284 assert_eq!(image.data[0], 1000); assert_eq!(image.data[1], 3000); assert_eq!(image.data[2], 2000); assert_eq!(image.data[3], 1000); }
290
291 #[test]
292 fn test_white_balance_raw_clamps() {
293 let mut image = create_test_raw_image(2, 2, CfaPattern::Rggb);
294 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));
299
300 assert_eq!(image.data[0], 65535); assert_eq!(image.data[1], 60000); }
303
304 fn create_test_image(width: u32, height: u32, r: u16, g: u16, b: u16) -> RgbImage {
305 let mut data = Vec::with_capacity((width * height * 3) as usize);
306 for _ in 0..(width * height) {
307 data.push(r);
308 data.push(g);
309 data.push(b);
310 }
311 RgbImage::new(width, height, data)
312 }
313
314 #[test]
315 fn test_clamp_u16() {
316 assert_eq!(clamp_u16(0.0), 0);
317 assert_eq!(clamp_u16(100.5), 100);
318 assert_eq!(clamp_u16(65535.0), 65535);
319 assert_eq!(clamp_u16(-100.0), 0);
320 assert_eq!(clamp_u16(100000.0), 65535);
321 }
322
323 #[test]
324 fn test_white_balance_identity() {
325 let mut image = create_test_image(2, 2, 1000, 2000, 3000);
326 apply_white_balance(&mut image, (1.0, 1.0, 1.0));
327
328 for i in 0..4 {
330 assert_eq!(image.data[i * 3], 1000);
331 assert_eq!(image.data[i * 3 + 1], 2000);
332 assert_eq!(image.data[i * 3 + 2], 3000);
333 }
334 }
335
336 #[test]
337 fn test_white_balance_scaling() {
338 let mut image = create_test_image(2, 2, 1000, 2000, 3000);
339 apply_white_balance(&mut image, (2.0, 1.0, 0.5));
340
341 for i in 0..4 {
342 assert_eq!(image.data[i * 3], 2000); assert_eq!(image.data[i * 3 + 1], 2000); assert_eq!(image.data[i * 3 + 2], 1500); }
346 }
347
348 #[test]
349 fn test_white_balance_clamps() {
350 let mut image = create_test_image(1, 1, 60000, 30000, 1000);
351 apply_white_balance(&mut image, (2.0, 2.0, 0.0));
352
353 assert_eq!(image.data[0], 65535); assert_eq!(image.data[1], 60000); assert_eq!(image.data[2], 0); }
357
358 #[test]
359 fn test_color_matrix_identity() {
360 let identity_matrix: [f32; 9] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
361 let mut image = create_test_image(2, 2, 1000, 2000, 3000);
362 apply_color_matrix(&mut image, &identity_matrix);
363
364 for i in 0..4 {
366 assert_eq!(image.data[i * 3], 1000);
367 assert_eq!(image.data[i * 3 + 1], 2000);
368 assert_eq!(image.data[i * 3 + 2], 3000);
369 }
370 }
371
372 #[test]
373 fn test_color_matrix_swap_channels() {
374 let swap_matrix: [f32; 9] = [0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0];
376 let mut image = create_test_image(1, 1, 1000, 2000, 3000);
377 apply_color_matrix(&mut image, &swap_matrix);
378
379 assert_eq!(image.data[0], 3000); assert_eq!(image.data[1], 2000); assert_eq!(image.data[2], 1000); }
383
384 #[test]
385 fn test_gamma_identity() {
386 let mut image = create_test_image(2, 2, 1000, 2000, 3000);
387 let original = image.data.clone();
388 apply_gamma(&mut image, 1.0);
389
390 assert_eq!(image.data, original);
392 }
393
394 #[test]
395 fn test_gamma_22() {
396 let mut image = create_test_image(1, 1, 0, 32768, 65535);
397 apply_gamma(&mut image, 2.2);
398
399 assert_eq!(image.data[0], 0);
401 assert_eq!(image.data[2], 65535);
403 assert!(
405 image.data[1] > 32768,
406 "Mid-tone {} should be > 32768",
407 image.data[1]
408 );
409 }
410
411 #[test]
412 fn test_gamma_lut_new() {
413 let lut = GammaLut::new(2.2);
414 assert_eq!(lut.gamma(), 2.2);
415 }
416
417 #[test]
418 fn test_gamma_lut_apply() {
419 let lut = GammaLut::new(2.2);
420 let mut image = create_test_image(1, 1, 0, 32768, 65535);
421 lut.apply(&mut image);
422
423 assert_eq!(image.data[0], 0);
424 assert_eq!(image.data[2], 65535);
425 assert!(
426 image.data[1] > 32768,
427 "Mid-tone should be brighter after gamma"
428 );
429 }
430
431 #[test]
432 fn test_gamma_lut_reuse() {
433 let lut = GammaLut::new(2.2);
435
436 let mut image1 = create_test_image(1, 1, 1000, 2000, 3000);
437 let mut image2 = create_test_image(1, 1, 1000, 2000, 3000);
438
439 lut.apply(&mut image1);
440 lut.apply(&mut image2);
441
442 assert_eq!(image1.data, image2.data);
443 }
444}