1use super::{Demosaic, DemosaicError};
7use crate::core::image::{RawImage, XTransPattern};
8
9pub struct Markesteijn;
22
23impl Demosaic for Markesteijn {
24 fn demosaic_into(&self, raw: &RawImage, output: &mut [u16]) -> Result<(), DemosaicError> {
25 let width = raw.active_area().size.width as usize;
26 let height = raw.active_area().size.height as usize;
27 let x_off = raw.active_area().origin.x as usize;
28 let y_off = raw.active_area().origin.y as usize;
29 let raw_w = raw.width() as usize;
30
31 let expected = width * height * 3;
32 if output.len() != expected {
33 return Err(DemosaicError::BufferSizeMismatch {
34 expected,
35 actual: output.len(),
36 });
37 }
38
39 if width < 6 || height < 6 {
40 return Err(DemosaicError::InvalidDimensions);
41 }
42
43 let pattern = raw
45 .xtrans_pattern()
46 .copied()
47 .unwrap_or_else(XTransPattern::standard);
48
49 let fc = |x: usize, y: usize| -> u8 { pattern.color_at(x + x_off, y + y_off) };
51
52 let get = |x: isize, y: isize| -> f32 {
54 let cx = x.clamp(0, (width as isize) - 1) as usize;
55 let cy = y.clamp(0, (height as isize) - 1) as usize;
56 raw.data[(cy + y_off) * raw_w + (cx + x_off)] as f32
57 };
58
59 let mut green = vec![0.0f32; width * height];
61 for y in 0..height {
62 for x in 0..width {
63 if fc(x, y) == 1 {
64 green[y * width + x] = get(x as isize, y as isize);
66 } else {
67 let mut sum = 0.0f32;
69 let mut weight = 0.0f32;
70 for dy in -2i32..=2 {
71 for dx in -2i32..=2 {
72 let nx = x as i32 + dx;
73 let ny = y as i32 + dy;
74 if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
75 continue;
76 }
77 if fc(nx as usize, ny as usize) == 1 {
78 let dist2 = (dx * dx + dy * dy) as f32;
80 let w = 1.0 / (dist2 + 0.1);
81 sum += get(nx as isize, ny as isize) * w;
82 weight += w;
83 }
84 }
85 }
86 green[y * width + x] = if weight > 0.0 {
87 sum / weight
88 } else {
89 get(x as isize, y as isize)
90 };
91 }
92 }
93 }
94
95 let mut r_diff = vec![0.0f32; width * height];
98 let mut b_diff = vec![0.0f32; width * height];
99
100 for y in 0..height {
101 for x in 0..width {
102 let g = green[y * width + x];
103 match fc(x, y) {
104 0 => r_diff[y * width + x] = get(x as isize, y as isize) - g,
105 2 => b_diff[y * width + x] = get(x as isize, y as isize) - g,
106 _ => {}
107 }
108 }
109 }
110
111 let mut r_diff_interp = vec![0.0f32; width * height];
113 let mut b_diff_interp = vec![0.0f32; width * height];
114
115 for y in 0..height {
116 for x in 0..width {
117 if fc(x, y) == 0 {
119 r_diff_interp[y * width + x] = r_diff[y * width + x]; } else {
121 let mut sum = 0.0f32;
122 let mut w_total = 0.0f32;
123 for dy in -3i32..=3 {
124 for dx in -3i32..=3 {
125 let nx = x as i32 + dx;
126 let ny = y as i32 + dy;
127 if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
128 continue;
129 }
130 if fc(nx as usize, ny as usize) == 0 {
131 let dist2 = (dx * dx + dy * dy) as f32;
132 let w = 1.0 / (dist2 + 0.1);
133 sum += r_diff[(ny as usize) * width + (nx as usize)] * w;
134 w_total += w;
135 }
136 }
137 }
138 r_diff_interp[y * width + x] = if w_total > 0.0 { sum / w_total } else { 0.0 };
139 }
140
141 if fc(x, y) == 2 {
143 b_diff_interp[y * width + x] = b_diff[y * width + x]; } else {
145 let mut sum = 0.0f32;
146 let mut w_total = 0.0f32;
147 for dy in -3i32..=3 {
148 for dx in -3i32..=3 {
149 let nx = x as i32 + dx;
150 let ny = y as i32 + dy;
151 if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
152 continue;
153 }
154 if fc(nx as usize, ny as usize) == 2 {
155 let dist2 = (dx * dx + dy * dy) as f32;
156 let w = 1.0 / (dist2 + 0.1);
157 sum += b_diff[(ny as usize) * width + (nx as usize)] * w;
158 w_total += w;
159 }
160 }
161 }
162 b_diff_interp[y * width + x] = if w_total > 0.0 { sum / w_total } else { 0.0 };
163 }
164 }
165 }
166
167 let wl = raw.white_level() as f32;
169 for y in 0..height {
170 for x in 0..width {
171 let idx = (y * width + x) * 3;
172 let g = green[y * width + x];
173 let r = (g + r_diff_interp[y * width + x]).clamp(0.0, wl);
174 let b = (g + b_diff_interp[y * width + x]).clamp(0.0, wl);
175 let g = g.clamp(0.0, wl);
176 output[idx] = r as u16;
177 output[idx + 1] = g as u16;
178 output[idx + 2] = b as u16;
179 }
180 }
181
182 Ok(())
183 }
184}
185
186pub struct Markesteijn3Pass;
196
197impl Demosaic for Markesteijn3Pass {
198 fn demosaic_into(&self, raw: &RawImage, output: &mut [u16]) -> Result<(), DemosaicError> {
199 let width = raw.active_area().size.width as usize;
200 let height = raw.active_area().size.height as usize;
201 let x_off = raw.active_area().origin.x as usize;
202 let y_off = raw.active_area().origin.y as usize;
203 let raw_w = raw.width() as usize;
204
205 let expected = width * height * 3;
206 if output.len() != expected {
207 return Err(DemosaicError::BufferSizeMismatch {
208 expected,
209 actual: output.len(),
210 });
211 }
212 if width < 6 || height < 6 {
213 return Err(DemosaicError::InvalidDimensions);
214 }
215
216 let pattern = raw
217 .xtrans_pattern()
218 .copied()
219 .unwrap_or_else(XTransPattern::standard);
220 let fc = |x: usize, y: usize| -> u8 { pattern.color_at(x + x_off, y + y_off) };
221 let get = |x: isize, y: isize| -> f32 {
222 let cx = x.clamp(0, (width as isize) - 1) as usize;
223 let cy = y.clamp(0, (height as isize) - 1) as usize;
224 raw.data[(cy + y_off) * raw_w + (cx + x_off)] as f32
225 };
226
227 let interp_diff = |plane: &[f32], x: usize, y: usize, color: u8, radius: i32| -> f32 {
230 if fc(x, y) == color {
231 return plane[y * width + x];
232 }
233 let mut sum = 0.0f32;
234 let mut w_total = 0.0f32;
235 for dy in -radius..=radius {
236 for dx in -radius..=radius {
237 let nx = x as i32 + dx;
238 let ny = y as i32 + dy;
239 if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
240 continue;
241 }
242 if fc(nx as usize, ny as usize) == color {
243 let dist2 = (dx * dx + dy * dy) as f32;
244 let w = 1.0 / (dist2 + 0.1);
245 sum += plane[(ny as usize) * width + (nx as usize)] * w;
246 w_total += w;
247 }
248 }
249 }
250 if w_total > 0.0 { sum / w_total } else { 0.0 }
251 };
252
253 let mut green = vec![0.0f32; width * height];
255 for y in 0..height {
256 for x in 0..width {
257 if fc(x, y) == 1 {
258 green[y * width + x] = get(x as isize, y as isize);
259 } else {
260 let mut sum = 0.0f32;
261 let mut w_total = 0.0f32;
262 for dy in -2i32..=2 {
263 for dx in -2i32..=2 {
264 let nx = x as i32 + dx;
265 let ny = y as i32 + dy;
266 if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
267 continue;
268 }
269 if fc(nx as usize, ny as usize) == 1 {
270 let dist2 = (dx * dx + dy * dy) as f32;
271 let w = 1.0 / (dist2 + 0.1);
272 sum += get(nx as isize, ny as isize) * w;
273 w_total += w;
274 }
275 }
276 }
277 green[y * width + x] = if w_total > 0.0 {
278 sum / w_total
279 } else {
280 get(x as isize, y as isize)
281 };
282 }
283 }
284 }
285
286 let build_diffs = |green: &[f32]| -> (Vec<f32>, Vec<f32>) {
288 let mut r_diff = vec![0.0f32; width * height];
289 let mut b_diff = vec![0.0f32; width * height];
290 for y in 0..height {
291 for x in 0..width {
292 let g = green[y * width + x];
293 match fc(x, y) {
294 0 => r_diff[y * width + x] = get(x as isize, y as isize) - g,
295 2 => b_diff[y * width + x] = get(x as isize, y as isize) - g,
296 _ => {}
297 }
298 }
299 }
300 (r_diff, b_diff)
301 };
302
303 let (mut r_diff, mut b_diff) = build_diffs(&green);
304
305 let interp_plane = |diff: &[f32], color: u8| -> Vec<f32> {
306 (0..width * height)
307 .map(|i| interp_diff(diff, i % width, i / width, color, 3))
308 .collect()
309 };
310
311 let mut r_diff_i = interp_plane(&r_diff, 0);
312 let mut b_diff_i = interp_plane(&b_diff, 2);
313
314 for y in 0..height {
316 for x in 0..width {
317 match fc(x, y) {
318 0 => {
319 let g_new = get(x as isize, y as isize) - r_diff_i[y * width + x];
320 green[y * width + x] = (green[y * width + x] + g_new) * 0.5;
321 }
322 2 => {
323 let g_new = get(x as isize, y as isize) - b_diff_i[y * width + x];
324 green[y * width + x] = (green[y * width + x] + g_new) * 0.5;
325 }
326 _ => {}
327 }
328 }
329 }
330
331 (r_diff, b_diff) = build_diffs(&green);
333 r_diff_i = interp_plane(&r_diff, 0);
334 b_diff_i = interp_plane(&b_diff, 2);
335
336 let wl = raw.white_level() as f32;
338 for y in 0..height {
339 for x in 0..width {
340 let idx = (y * width + x) * 3;
341 let g = green[y * width + x];
342 let r = (g + r_diff_i[y * width + x]).clamp(0.0, wl);
343 let b = (g + b_diff_i[y * width + x]).clamp(0.0, wl);
344 let g = g.clamp(0.0, wl);
345 output[idx] = r as u16;
346 output[idx + 1] = g as u16;
347 output[idx + 2] = b as u16;
348 }
349 }
350
351 Ok(())
352 }
353}
354
355pub struct XTransFast;
361
362impl Demosaic for XTransFast {
363 fn demosaic_into(&self, raw: &RawImage, output: &mut [u16]) -> Result<(), DemosaicError> {
364 let width = raw.active_area().size.width as usize;
365 let height = raw.active_area().size.height as usize;
366 let x_off = raw.active_area().origin.x as usize;
367 let y_off = raw.active_area().origin.y as usize;
368 let raw_w = raw.width() as usize;
369
370 let expected = width * height * 3;
371 if output.len() != expected {
372 return Err(DemosaicError::BufferSizeMismatch {
373 expected,
374 actual: output.len(),
375 });
376 }
377 if width < 6 || height < 6 {
378 return Err(DemosaicError::InvalidDimensions);
379 }
380
381 let pattern = raw
382 .xtrans_pattern()
383 .copied()
384 .unwrap_or_else(XTransPattern::standard);
385 let fc = |x: usize, y: usize| -> u8 { pattern.color_at(x + x_off, y + y_off) };
386 let get = |x: isize, y: isize| -> f32 {
387 let cx = x.clamp(0, (width as isize) - 1) as usize;
388 let cy = y.clamp(0, (height as isize) - 1) as usize;
389 raw.data[(cy + y_off) * raw_w + (cx + x_off)] as f32
390 };
391
392 let wl = raw.white_level() as f32;
395
396 for y in 0..height {
397 for x in 0..width {
398 let idx = (y * width + x) * 3;
399 let own_color = fc(x, y);
400
401 let interp = |target_color: u8| -> f32 {
402 if own_color == target_color {
403 return get(x as isize, y as isize);
404 }
405 for radius in [1i32, 2, 3] {
407 let mut sum = 0.0f32;
408 let mut count = 0u32;
409 for dy in -radius..=radius {
410 for dx in -radius..=radius {
411 let nx = x as i32 + dx;
412 let ny = y as i32 + dy;
413 if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
414 continue;
415 }
416 if fc(nx as usize, ny as usize) == target_color {
417 sum += get(nx as isize, ny as isize);
418 count += 1;
419 }
420 }
421 }
422 if count > 0 {
423 return sum / count as f32;
424 }
425 }
426 get(x as isize, y as isize)
427 };
428
429 output[idx] = interp(0).clamp(0.0, wl) as u16; output[idx + 1] = interp(1).clamp(0.0, wl) as u16; output[idx + 2] = interp(2).clamp(0.0, wl) as u16; }
433 }
434
435 Ok(())
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::core::image::{CfaPattern, Point, Rect, Size};
443
444 fn create_xtrans_raw(width: u32, height: u32, value: u16) -> RawImage {
450 let size = Size::new(width, height);
451 let active_area = Rect::new(Point::ORIGIN, size);
452 let pixel_count = (width * height) as usize;
453 RawImage::builder(size, active_area, 14, CfaPattern::Rggb)
454 .xtrans_pattern(XTransPattern::standard())
455 .white_level(16383)
456 .data(vec![value; pixel_count])
457 .build()
458 }
459
460 fn max_deviation(output: &[u16], expected: u16) -> u16 {
465 output
466 .iter()
467 .map(|&v| (v as i32 - expected as i32).unsigned_abs() as u16)
468 .max()
469 .unwrap_or(0)
470 }
471
472 #[test]
475 fn correct_output_size() {
476 let raw = create_xtrans_raw(12, 12, 1000);
477 let mut output = vec![0u16; 12 * 12 * 3];
478 assert!(Markesteijn.demosaic_into(&raw, &mut output).is_ok());
479 }
480
481 #[test]
482 fn wrong_buffer_size_returns_error() {
483 let raw = create_xtrans_raw(12, 12, 1000);
484 let mut output = vec![0u16; 100];
486 let result = Markesteijn.demosaic_into(&raw, &mut output);
487 assert!(
488 matches!(result, Err(DemosaicError::BufferSizeMismatch { .. })),
489 "expected BufferSizeMismatch, got {:?}",
490 result
491 );
492 }
493
494 #[test]
495 fn too_small_image_returns_invalid_dimensions() {
496 let raw = create_xtrans_raw(5, 5, 1000);
498 let mut output = vec![0u16; 5 * 5 * 3];
499 let result = Markesteijn.demosaic_into(&raw, &mut output);
500 assert!(
501 matches!(result, Err(DemosaicError::InvalidDimensions)),
502 "expected InvalidDimensions, got {:?}",
503 result
504 );
505 }
506
507 #[test]
508 fn uniform_input_produces_near_uniform_output() {
509 let v: u16 = 4096;
512 let raw = create_xtrans_raw(24, 24, v);
513 let mut output = vec![0u16; 24 * 24 * 3];
514 Markesteijn
515 .demosaic_into(&raw, &mut output)
516 .expect("demosaic failed");
517
518 let dev = max_deviation(&output, v);
520 assert!(
521 dev <= 5,
522 "max deviation from uniform value {v} is {dev} (expected ≤ 5)"
523 );
524 }
525
526 #[test]
527 fn output_respects_white_level_clamp() {
528 let wl: u16 = 16383;
530 let raw = create_xtrans_raw(12, 12, wl);
531 let mut output = vec![0u16; 12 * 12 * 3];
532 Markesteijn
533 .demosaic_into(&raw, &mut output)
534 .expect("demosaic failed");
535 let max_out = *output.iter().max().unwrap_or(&0);
536 assert!(
537 max_out <= wl,
538 "output value {max_out} exceeds white level {wl}"
539 );
540 }
541
542 #[test]
543 fn zero_input_produces_zero_output() {
544 let raw = create_xtrans_raw(12, 12, 0);
545 let mut output = vec![1u16; 12 * 12 * 3]; Markesteijn
547 .demosaic_into(&raw, &mut output)
548 .expect("demosaic failed");
549 assert!(
550 output.iter().all(|&v| v == 0),
551 "expected all-zero output for all-zero input"
552 );
553 }
554
555 #[test]
556 fn xtrans_pattern_standard_color_at() {
557 let p = XTransPattern::standard();
558 for y in 0..12 {
560 for x in 0..12 {
561 assert_eq!(
562 p.color_at(x, y),
563 p.color_at(x % 6, y % 6),
564 "color_at wrapping failed at ({x},{y})"
565 );
566 }
567 }
568 for row in &p.cells {
570 for &c in row {
571 assert!(c <= 2, "invalid colour index {c}");
572 }
573 }
574 }
575
576 #[test]
577 fn markesteijn_uses_default_pattern_when_xtrans_is_none() {
578 let size = Size::new(12, 12);
581 let active_area = Rect::new(Point::ORIGIN, size);
582 let raw = RawImage::builder(size, active_area, 14, CfaPattern::Rggb)
584 .white_level(16383)
585 .data(vec![2000u16; 12 * 12])
586 .build();
587 let mut output = vec![0u16; 12 * 12 * 3];
588 assert!(Markesteijn.demosaic_into(&raw, &mut output).is_ok());
589 }
590
591 #[test]
594 fn markesteijn3_correct_output_size() {
595 let raw = create_xtrans_raw(12, 12, 1000);
596 let mut output = vec![0u16; 12 * 12 * 3];
597 assert!(Markesteijn3Pass.demosaic_into(&raw, &mut output).is_ok());
598 }
599
600 #[test]
601 fn markesteijn3_wrong_buffer_returns_error() {
602 let raw = create_xtrans_raw(12, 12, 1000);
603 let mut output = vec![0u16; 100];
604 assert!(matches!(
605 Markesteijn3Pass.demosaic_into(&raw, &mut output),
606 Err(DemosaicError::BufferSizeMismatch { .. })
607 ));
608 }
609
610 #[test]
611 fn markesteijn3_too_small_returns_invalid_dimensions() {
612 let raw = create_xtrans_raw(5, 5, 1000);
613 let mut output = vec![0u16; 5 * 5 * 3];
614 assert!(matches!(
615 Markesteijn3Pass.demosaic_into(&raw, &mut output),
616 Err(DemosaicError::InvalidDimensions)
617 ));
618 }
619
620 #[test]
621 fn markesteijn3_uniform_produces_near_uniform_output() {
622 let v: u16 = 4096;
623 let raw = create_xtrans_raw(24, 24, v);
624 let mut output = vec![0u16; 24 * 24 * 3];
625 Markesteijn3Pass.demosaic_into(&raw, &mut output).unwrap();
626 let dev = max_deviation(&output, v);
627 assert!(dev <= 5, "max deviation from {v} is {dev} (expected ≤ 5)");
628 }
629
630 #[test]
631 fn markesteijn3_respects_white_level() {
632 let wl: u16 = 16383;
633 let raw = create_xtrans_raw(12, 12, wl);
634 let mut output = vec![0u16; 12 * 12 * 3];
635 Markesteijn3Pass.demosaic_into(&raw, &mut output).unwrap();
636 assert!(*output.iter().max().unwrap() <= wl);
637 }
638
639 #[test]
642 fn xtrans_fast_correct_output_size() {
643 let raw = create_xtrans_raw(12, 12, 1000);
644 let mut output = vec![0u16; 12 * 12 * 3];
645 assert!(XTransFast.demosaic_into(&raw, &mut output).is_ok());
646 }
647
648 #[test]
649 fn xtrans_fast_wrong_buffer_returns_error() {
650 let raw = create_xtrans_raw(12, 12, 1000);
651 let mut output = vec![0u16; 100];
652 assert!(matches!(
653 XTransFast.demosaic_into(&raw, &mut output),
654 Err(DemosaicError::BufferSizeMismatch { .. })
655 ));
656 }
657
658 #[test]
659 fn xtrans_fast_too_small_returns_invalid_dimensions() {
660 let raw = create_xtrans_raw(5, 5, 1000);
661 let mut output = vec![0u16; 5 * 5 * 3];
662 assert!(matches!(
663 XTransFast.demosaic_into(&raw, &mut output),
664 Err(DemosaicError::InvalidDimensions)
665 ));
666 }
667
668 #[test]
669 fn xtrans_fast_uniform_produces_near_uniform_output() {
670 let v: u16 = 4096;
671 let raw = create_xtrans_raw(18, 18, v);
672 let mut output = vec![0u16; 18 * 18 * 3];
673 XTransFast.demosaic_into(&raw, &mut output).unwrap();
674 let dev = max_deviation(&output, v);
675 assert!(dev <= 5, "max deviation from {v} is {dev} (expected ≤ 5)");
676 }
677
678 #[test]
679 fn xtrans_fast_respects_white_level() {
680 let wl: u16 = 16383;
681 let raw = create_xtrans_raw(12, 12, wl);
682 let mut output = vec![0u16; 12 * 12 * 3];
683 XTransFast.demosaic_into(&raw, &mut output).unwrap();
684 assert!(*output.iter().max().unwrap() <= wl);
685 }
686}