use mediaframe::{
PixelSink,
frame::{BayerDemosaic, BayerPattern, ColorCorrectionMatrix, WhiteBalance},
};
use super::*;
use crate::{
frame::Bayer12Frame,
row::{bayer16_to_rgb_row, bayer16_to_rgb_u16_row},
};
use core::convert::Infallible;
struct CaptureRgbU8<'a, const BITS: u32> {
out: &'a mut [u8],
width: u32,
}
impl<const BITS: u32> PixelSink for CaptureRgbU8<'_, BITS> {
type Input<'b> = BayerRow16<'b, BITS>;
type Error = Infallible;
fn begin_frame(&mut self, width: u32, _height: u32) -> Result<(), Self::Error> {
self.width = width;
Ok(())
}
fn process(&mut self, row: BayerRow16<'_, BITS>) -> Result<(), Self::Error> {
let r = row.row();
let w = self.width as usize;
let off = r * w * 3;
let dst = &mut self.out[off..off + 3 * w];
bayer16_to_rgb_row::<BITS>(
row.above(),
row.mid(),
row.below(),
row.row_parity(),
row.pattern(),
row.demosaic(),
row.m(),
dst,
false,
);
Ok(())
}
}
impl<const BITS: u32> BayerSink16<BITS> for CaptureRgbU8<'_, BITS> {}
struct CaptureRgbU16<'a, const BITS: u32> {
out: &'a mut [u16],
width: u32,
}
impl<const BITS: u32> PixelSink for CaptureRgbU16<'_, BITS> {
type Input<'b> = BayerRow16<'b, BITS>;
type Error = Infallible;
fn begin_frame(&mut self, width: u32, _height: u32) -> Result<(), Self::Error> {
self.width = width;
Ok(())
}
fn process(&mut self, row: BayerRow16<'_, BITS>) -> Result<(), Self::Error> {
let r = row.row();
let w = self.width as usize;
let off = r * w * 3;
let dst = &mut self.out[off..off + 3 * w];
bayer16_to_rgb_u16_row::<BITS>(
row.above(),
row.mid(),
row.below(),
row.row_parity(),
row.pattern(),
row.demosaic(),
row.m(),
dst,
false,
);
Ok(())
}
}
impl<const BITS: u32> BayerSink16<BITS> for CaptureRgbU16<'_, BITS> {}
fn solid_rggb_12bit(width: u32, height: u32, r: u16, g: u16, b: u16) -> std::vec::Vec<u16> {
let w = width as usize;
let h = height as usize;
let mut data = std::vec![0u16; w * h];
for y in 0..h {
for x in 0..w {
let v = match (y & 1, x & 1) {
(0, 0) => r,
(0, 1) => g,
(1, 0) => g,
(1, 1) => b,
_ => unreachable!(),
};
data[y * w + x] = v;
}
}
data
}
fn assert_full_frame_u8(rgb: &[u8], w: u32, h: u32, expect: (u8, u8, u8)) {
let w = w as usize;
let h = h as usize;
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 3;
assert_eq!(rgb[i], expect.0, "u8 px ({x},{y}) R");
assert_eq!(rgb[i + 1], expect.1, "u8 px ({x},{y}) G");
assert_eq!(rgb[i + 2], expect.2, "u8 px ({x},{y}) B");
}
}
}
fn assert_full_frame_u16(rgb: &[u16], w: u32, h: u32, expect: (u16, u16, u16)) {
let w = w as usize;
let h = h as usize;
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 3;
assert_eq!(rgb[i], expect.0, "u16 px ({x},{y}) R");
assert_eq!(rgb[i + 1], expect.1, "u16 px ({x},{y}) G");
assert_eq!(rgb[i + 2], expect.2, "u16 px ({x},{y}) B");
}
}
}
#[test]
fn bayer12_solid_red_rggb_yields_u8_red_full_frame() {
let (w, h) = (8u32, 6u32);
let raw = solid_rggb_12bit(w, h, 4095, 0, 0);
let frame = Bayer12Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u8; (w * h * 3) as usize];
let mut sink = CaptureRgbU8::<12> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
assert_full_frame_u8(&rgb, w, h, (255, 0, 0));
}
#[test]
fn bayer12_solid_red_rggb_yields_u16_red_full_frame() {
let (w, h) = (8u32, 6u32);
let raw = solid_rggb_12bit(w, h, 4095, 0, 0);
let frame = Bayer12Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u16; (w * h * 3) as usize];
let mut sink = CaptureRgbU16::<12> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
assert_full_frame_u16(&rgb, w, h, (4095, 0, 0));
}
#[test]
fn bayer12_uniform_value_yields_uniform_u8_output() {
let (w, h) = (8u32, 6u32);
let raw = std::vec![2048u16; (w * h) as usize];
let frame = Bayer12Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u8; (w * h * 3) as usize];
let mut sink = CaptureRgbU8::<12> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
for &c in &rgb {
assert!((c as i32 - 128).abs() <= 1, "got {c}");
}
}
#[test]
fn bayer12_uniform_value_yields_uniform_u16_output() {
let (w, h) = (8u32, 6u32);
let raw = std::vec![4095u16; (w * h) as usize];
let frame = Bayer12Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u16; (w * h * 3) as usize];
let mut sink = CaptureRgbU16::<12> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
for &c in &rgb {
assert_eq!(c, 4095);
}
}
#[test]
fn bayer10_low_packed_white_yields_full_scale_u8() {
let (w, h) = (8u32, 6u32);
let raw = std::vec![1023u16; (w * h) as usize];
let frame = crate::frame::Bayer10Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u8; (w * h * 3) as usize];
let mut sink = CaptureRgbU8::<10> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
for &c in &rgb {
assert_eq!(c, 255, "10-bit low-packed white must scale to u8 255");
}
}
#[test]
fn bayer14_low_packed_white_yields_full_scale_u16() {
let (w, h) = (8u32, 6u32);
let raw = std::vec![16383u16; (w * h) as usize];
let frame = crate::frame::Bayer14Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u16; (w * h * 3) as usize];
let mut sink = CaptureRgbU16::<14> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
for &c in &rgb {
assert_eq!(c, 16383, "14-bit low-packed white must stay 16383");
}
}
#[test]
fn bayer12_try_new_rejects_sample_above_max() {
let (w, h) = (4u32, 2u32);
let mut raw = std::vec![100u16; (w * h) as usize];
raw[3] = 4096; let e = Bayer12Frame::try_new(&raw, w, h, w).unwrap_err();
let crate::frame::BayerFrame16Error::SampleOutOfRange(p) = e else {
panic!("expected SampleOutOfRange, got {e:?}");
};
assert_eq!(p.index(), 3);
assert_eq!(p.value(), 4096);
assert_eq!(p.max_valid(), 4095);
}
#[test]
fn bayer12_try_new_rejects_msb_aligned_input() {
let (w, h) = (4u32, 2u32);
let raw = std::vec![0x8000u16; (w * h) as usize]; let e = Bayer12Frame::try_new(&raw, w, h, w).unwrap_err();
let crate::frame::BayerFrame16Error::SampleOutOfRange(p) = e else {
panic!("expected SampleOutOfRange, got {e:?}");
};
assert_eq!(p.value(), 0x8000);
assert_eq!(p.max_valid(), 4095);
}
#[test]
fn bayer12_try_new_rejects_bad_sample_in_later_row() {
let (w, h) = (4u32, 8u32);
let mut raw = std::vec![100u16; (w * h) as usize];
let off = (6 * w) as usize + 2;
raw[off] = 4096; let e = Bayer12Frame::try_new(&raw, w, h, w).unwrap_err();
let crate::frame::BayerFrame16Error::SampleOutOfRange(p) = e else {
panic!("expected SampleOutOfRange, got {e:?}");
};
assert_eq!(p.value(), 4096);
assert_eq!(p.max_valid(), 4095);
}
#[test]
fn bayer12_walker_accepts_padded_stride_with_dirty_padding() {
let w: u32 = 4;
let h: u32 = 4;
let stride: u32 = 8; let mut raw = std::vec![100u16; (stride * h) as usize];
for r in 0..(h as usize) {
for c in 4..(stride as usize) {
raw[r * stride as usize + c] = 0xFFFF;
}
}
let frame = Bayer12Frame::try_new(&raw, w, h, stride).unwrap();
let mut rgb = std::vec![0u8; (w * h * 3) as usize];
let mut sink = CaptureRgbU8::<12> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
}
#[test]
fn bayer12_walker_accepts_overlong_slice_with_trailing_junk() {
let w: u32 = 4;
let h: u32 = 2;
let stride: u32 = 4;
let mut raw = std::vec![100u16; (stride * h * 2) as usize];
for v in raw.iter_mut().skip((stride * h) as usize) {
*v = 0xFFFF;
}
let frame = Bayer12Frame::try_new(&raw, w, h, stride).unwrap();
let mut rgb = std::vec![0u8; (w * h * 3) as usize];
let mut sink = CaptureRgbU8::<12> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
}
#[test]
fn bayer16bit_dispatcher_accepts_full_u16_range() {
let (w, h) = (4u32, 2u32);
let raw = std::vec![0xFFFFu16; (w * h) as usize];
let frame = crate::frame::Bayer16Frame::try_new(&raw, w, h, w).unwrap();
let mut rgb = std::vec![0u8; (w * h * 3) as usize];
let mut sink = CaptureRgbU8::<16> {
out: &mut rgb,
width: 0,
};
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
for &c in &rgb {
assert_eq!(c, 255);
}
}
#[test]
fn bayer12_walker_calls_sink_once_per_row() {
struct CountSink<const BITS: u32> {
rows: u32,
}
impl<const BITS: u32> PixelSink for CountSink<BITS> {
type Input<'a> = BayerRow16<'a, BITS>;
type Error = Infallible;
fn process(&mut self, _row: BayerRow16<'_, BITS>) -> Result<(), Self::Error> {
self.rows += 1;
Ok(())
}
}
impl<const BITS: u32> BayerSink16<BITS> for CountSink<BITS> {}
let (w, h) = (8u32, 6u32);
let raw = std::vec![0u16; (w * h) as usize];
let frame = Bayer12Frame::try_new(&raw, w, h, w).unwrap();
let mut sink = CountSink::<12> { rows: 0 };
bayer16_to(
&frame,
BayerPattern::Rggb,
BayerDemosaic::Bilinear,
WhiteBalance::neutral(),
ColorCorrectionMatrix::identity(),
&mut sink,
)
.unwrap();
assert_eq!(sink.rows, h);
}