1use super::{Demosaic, DemosaicError};
2use crate::core::image::{CfaPattern, RawImage};
3use rayon::prelude::*;
4
5pub struct Bilinear;
13
14impl Demosaic for Bilinear {
15 fn demosaic_into(&self, raw: &RawImage, output: &mut [u16]) -> Result<(), DemosaicError> {
16 let width = raw.active_area().size.width;
17 let height = raw.active_area().size.height;
18 let x_offset = raw.active_area().origin.x;
19 let y_offset = raw.active_area().origin.y;
20
21 let expected_size = (width as usize) * (height as usize) * 3;
22 if output.len() != expected_size {
23 return Err(DemosaicError::BufferSizeMismatch {
24 expected: expected_size,
25 actual: output.len(),
26 });
27 }
28
29 let raw_width = raw.width();
30 let raw_height = raw.height();
31 let raw_data = &raw.data;
32 let cfa_pattern = raw.cfa_pattern();
33 let row_stride = (width as usize) * 3;
34
35 let get_raw = |x: u32, y: u32| -> u16 {
37 if x < raw_width && y < raw_height {
38 raw_data[(y as usize) * (raw_width as usize) + (x as usize)]
39 } else {
40 0
41 }
42 };
43
44 output
46 .par_chunks_mut(row_stride)
47 .enumerate()
48 .for_each(|(y, row_output)| {
49 let abs_y = (y as u32) + y_offset;
50
51 for x in 0..width {
52 let abs_x = x + x_offset;
53 let (r, g, b) = demosaic_pixel_bilinear(abs_x, abs_y, cfa_pattern, &get_raw);
54
55 let idx = (x as usize) * 3;
56 row_output[idx] = r;
57 row_output[idx + 1] = g;
58 row_output[idx + 2] = b;
59 }
60 });
61
62 Ok(())
63 }
64}
65
66fn demosaic_pixel_bilinear<F>(x: u32, y: u32, pattern: CfaPattern, get_raw: &F) -> (u16, u16, u16)
69where
70 F: Fn(u32, u32) -> u16,
71{
72 let (is_red, is_blue) = match pattern {
74 CfaPattern::Rggb => (
75 x.is_multiple_of(2) && y.is_multiple_of(2),
76 (x % 2 == 1) && (y % 2 == 1),
77 ),
78 CfaPattern::Grbg => (
79 (x % 2 == 1) && y.is_multiple_of(2),
80 x.is_multiple_of(2) && (y % 2 == 1),
81 ),
82 CfaPattern::Gbrg => (
83 x.is_multiple_of(2) && (y % 2 == 1),
84 (x % 2 == 1) && y.is_multiple_of(2),
85 ),
86 CfaPattern::Bggr => (
87 (x % 2 == 1) && (y % 2 == 1),
88 x.is_multiple_of(2) && y.is_multiple_of(2),
89 ),
90 };
91
92 let is_green = !is_red && !is_blue;
94
95 if is_green {
96 let g = get_raw(x, y);
101
102 let (r, b) = match pattern {
103 CfaPattern::Rggb | CfaPattern::Bggr => {
109 if y.is_multiple_of(2) {
110 if matches!(pattern, CfaPattern::Rggb) {
115 (
117 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
118 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
119 )
120 } else {
121 (
124 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
125 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
126 )
127 }
128 } else {
129 if matches!(pattern, CfaPattern::Rggb) {
133 (
134 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
135 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
136 )
137 } else {
138 (
139 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
140 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
141 )
142 }
143 }
144 }
145 CfaPattern::Grbg | CfaPattern::Gbrg => {
146 if y.is_multiple_of(2) {
151 if matches!(pattern, CfaPattern::Grbg) {
153 (
154 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
155 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
156 )
157 } else {
158 (
160 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
161 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
162 )
163 }
164 } else {
165 if matches!(pattern, CfaPattern::Grbg) {
168 (
169 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
170 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
171 )
172 } else {
173 (
175 avg2(get_raw(x.saturating_sub(1), y), get_raw(x + 1, y)),
176 avg2(get_raw(x, y.saturating_sub(1)), get_raw(x, y + 1)),
177 )
178 }
179 }
180 }
181 };
182 (r, g, b)
183 } else if is_red {
184 let r = get_raw(x, y);
186 let g = avg4(
188 get_raw(x, y.saturating_sub(1)),
189 get_raw(x, y + 1),
190 get_raw(x.saturating_sub(1), y),
191 get_raw(x + 1, y),
192 );
193 let b = avg4(
195 get_raw(x.saturating_sub(1), y.saturating_sub(1)),
196 get_raw(x + 1, y.saturating_sub(1)),
197 get_raw(x.saturating_sub(1), y + 1),
198 get_raw(x + 1, y + 1),
199 );
200 (r, g, b)
201 } else {
202 let b = get_raw(x, y);
204 let g = avg4(
206 get_raw(x, y.saturating_sub(1)),
207 get_raw(x, y + 1),
208 get_raw(x.saturating_sub(1), y),
209 get_raw(x + 1, y),
210 );
211 let r = avg4(
213 get_raw(x.saturating_sub(1), y.saturating_sub(1)),
214 get_raw(x + 1, y.saturating_sub(1)),
215 get_raw(x.saturating_sub(1), y + 1),
216 get_raw(x + 1, y + 1),
217 );
218 (r, g, b)
219 }
220}
221
222#[inline(always)]
223fn avg2(a: u16, b: u16) -> u16 {
224 ((a as u32 + b as u32) / 2) as u16
225}
226
227#[inline(always)]
228fn avg4(a: u16, b: u16, c: u16, d: u16) -> u16 {
229 ((a as u32 + b as u32 + c as u32 + d as u32) / 4) as u16
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::core::image::{Point, Rect, Size};
236
237 fn create_test_raw(width: u32, height: u32, pattern: CfaPattern, value: u16) -> RawImage {
239 let size = Size::new(width, height);
240 let active_area = Rect::new(Point::ORIGIN, size);
241 let pixel_count = (width * height) as usize;
242 RawImage::builder(size, active_area, 14, pattern)
243 .white_level(16383)
244 .data(vec![value; pixel_count])
245 .build()
246 }
247
248 fn create_bayer_2x2(pattern: CfaPattern) -> RawImage {
250 let mut raw = create_test_raw(2, 2, pattern, 0);
252 raw.data[0] = 1000; raw.data[1] = 2000; raw.data[2] = 3000; raw.data[3] = 4000; raw
258 }
259
260 #[test]
261 fn test_demosaic_into_correct_size() {
262 let raw = create_test_raw(10, 10, CfaPattern::Rggb, 1000);
263 let mut output = vec![0u16; 10 * 10 * 3]; let result = Bilinear.demosaic_into(&raw, &mut output);
266 assert!(result.is_ok());
267 }
268
269 #[test]
270 fn test_demosaic_into_wrong_size() {
271 let raw = create_test_raw(10, 10, CfaPattern::Rggb, 1000);
272 let mut output = vec![0u16; 50]; let result = Bilinear.demosaic_into(&raw, &mut output);
275 assert!(matches!(
276 result,
277 Err(DemosaicError::BufferSizeMismatch { .. })
278 ));
279 }
280
281 #[test]
282 fn test_demosaic_solid_color() {
283 let raw = create_test_raw(10, 10, CfaPattern::Rggb, 5000);
285 let demosaic = Bilinear;
286 let rgb = demosaic.demosaic(&raw);
287
288 assert_eq!(rgb.width(), 10);
289 assert_eq!(rgb.height(), 10);
290 assert_eq!(rgb.data.len(), 10 * 10 * 3);
291
292 for y in 1..9 {
295 for x in 1..9 {
296 let idx = ((y * 10 + x) * 3) as usize;
297 for c in 0..3 {
298 let pixel = rgb.data[idx + c];
299 assert!(
300 (4000..=5500).contains(&pixel),
301 "Interior pixel at ({},{}) channel {} value {} out of range",
302 x,
303 y,
304 c,
305 pixel
306 );
307 }
308 }
309 }
310 }
311
312 #[test]
313 fn test_demosaic_all_cfa_patterns() {
314 let patterns = [
315 CfaPattern::Rggb,
316 CfaPattern::Grbg,
317 CfaPattern::Gbrg,
318 CfaPattern::Bggr,
319 ];
320
321 for pattern in patterns {
322 let raw = create_test_raw(8, 8, pattern, 2000);
323 let rgb = Bilinear.demosaic(&raw);
324
325 assert_eq!(rgb.width(), 8);
326 assert_eq!(rgb.height(), 8);
327 assert_eq!(rgb.data.len(), 8 * 8 * 3);
328
329 for pixel in &rgb.data {
331 assert!(
332 *pixel <= 3000,
333 "Pattern {:?}: pixel value {} too high",
334 pattern,
335 pixel
336 );
337 }
338 }
339 }
340
341 #[test]
342 fn test_demosaic_rggb_pattern() {
343 let raw = create_bayer_2x2(CfaPattern::Rggb);
345 let rgb = Bilinear.demosaic(&raw);
346
347 assert_eq!(rgb.width(), 2);
350 assert_eq!(rgb.height(), 2);
351
352 let r = rgb.data[0];
354 let g = rgb.data[1];
355 let _b = rgb.data[2];
356
357 assert_eq!(r, 1000, "Red at RGGB position (0,0) should be 1000");
359
360 assert!(g > 0, "Green should be interpolated");
363 }
364
365 #[test]
366 fn test_demosaic_with_active_area() {
367 let raw = {
369 let size = Size::new(10, 10);
370 let active_area = Rect::from_coords(3, 3, 4, 4);
371 RawImage::builder(size, active_area, 14, CfaPattern::Rggb)
372 .white_level(16383)
373 .data(vec![1000u16; 100])
374 .build()
375 };
376
377 let rgb = Bilinear.demosaic(&raw);
378
379 assert_eq!(rgb.width(), 4);
381 assert_eq!(rgb.height(), 4);
382 assert_eq!(rgb.data.len(), 4 * 4 * 3);
383 }
384
385 #[test]
386 fn test_avg2() {
387 assert_eq!(avg2(0, 0), 0);
388 assert_eq!(avg2(100, 100), 100);
389 assert_eq!(avg2(100, 200), 150);
390 assert_eq!(avg2(0, 65535), 32767);
391 }
392
393 #[test]
394 fn test_avg4() {
395 assert_eq!(avg4(0, 0, 0, 0), 0);
396 assert_eq!(avg4(100, 100, 100, 100), 100);
397 assert_eq!(avg4(0, 100, 200, 300), 150);
398 assert_eq!(avg4(0, 0, 0, 65535), 16383);
399 }
400
401 #[test]
402 fn test_bilinear_all_cfa_patterns() {
403 let patterns = [
405 CfaPattern::Rggb,
406 CfaPattern::Grbg,
407 CfaPattern::Gbrg,
408 CfaPattern::Bggr,
409 ];
410
411 for pattern in patterns {
412 let raw = create_test_raw(6, 6, pattern, 8000);
413 let rgb = Bilinear.demosaic(&raw);
414
415 assert_eq!(rgb.width(), 6, "width for {:?}", pattern);
416 assert_eq!(rgb.height(), 6, "height for {:?}", pattern);
417 assert_eq!(rgb.data.len(), 6 * 6 * 3, "data length for {:?}", pattern);
418
419 let non_zero = rgb.data.iter().any(|&v| v > 0);
422 assert!(
423 non_zero,
424 "Output for {:?} should have non-zero pixels",
425 pattern
426 );
427 }
428 }
429
430 #[test]
431 fn test_bilinear_with_active_area() {
432 let raw = {
434 let size = Size::new(12, 12);
435 let active_area = Rect::from_coords(2, 4, 6, 6);
436 RawImage::builder(size, active_area, 14, CfaPattern::Rggb)
437 .white_level(16383)
438 .data(vec![5000u16; 144])
439 .build()
440 };
441
442 let rgb = Bilinear.demosaic(&raw);
443
444 assert_eq!(
445 rgb.width(),
446 6,
447 "output width should match active area width"
448 );
449 assert_eq!(
450 rgb.height(),
451 6,
452 "output height should match active area height"
453 );
454 assert_eq!(
455 rgb.data.len(),
456 6 * 6 * 3,
457 "output should have correct data length"
458 );
459
460 assert!(!rgb.data.is_empty(), "output should have pixel data");
462 }
463
464 #[test]
465 fn test_bilinear_gradient_smooth() {
466 let width = 8u32;
469 let height = 4u32;
470 let size = Size::new(width, height);
471 let active_area = Rect::new(Point::ORIGIN, size);
472
473 let mut data = vec![0u16; (width * height) as usize];
475 for y in 0..height as usize {
476 for x in 0..width as usize {
477 data[y * width as usize + x] = (x as u16) * 1000;
478 }
479 }
480
481 let raw = RawImage::builder(size, active_area, 14, CfaPattern::Rggb)
482 .white_level(16383)
483 .data(data)
484 .build();
485
486 let rgb = Bilinear.demosaic(&raw);
487
488 let mid_row = 2usize;
491 let row_start = mid_row * width as usize * 3;
492
493 for x in 1..(width as usize - 2) {
496 let g_left = rgb.data[row_start + (x - 1) * 3 + 1];
497 let g_right = rgb.data[row_start + (x + 1) * 3 + 1];
498 assert!(
499 g_right >= g_left || (g_right as i32 - g_left as i32).abs() < 2000,
500 "gradient smoothness: g[{}]={} should not greatly exceed g[{}]={} in row {}",
501 x - 1,
502 g_left,
503 x + 1,
504 g_right,
505 mid_row
506 );
507 }
508 }
509}