use super::{
GeometryOverflow, InsufficientPlane, InsufficientStride, UnsupportedBits, ZeroDimension,
};
use crate::PixelSink;
use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
use thiserror::Error;
#[derive(Debug, Clone, Copy)]
pub struct BayerFrame<'a> {
data: &'a [u8],
width: u32,
height: u32,
stride: u32,
}
impl<'a> BayerFrame<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(
data: &'a [u8],
width: u32,
height: u32,
stride: u32,
) -> Result<Self, BayerFrameError> {
if width == 0 || height == 0 {
return Err(BayerFrameError::ZeroDimension(ZeroDimension::new(
width, height,
)));
}
if stride < width {
return Err(BayerFrameError::InsufficientStride(
InsufficientStride::new(stride, width),
));
}
let min = match (stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(BayerFrameError::GeometryOverflow(GeometryOverflow::new(
stride, height,
)));
}
};
if data.len() < min {
return Err(BayerFrameError::InsufficientPlane(InsufficientPlane::new(
min,
data.len(),
)));
}
Ok(Self {
data,
width,
height,
stride,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(data: &'a [u8], width: u32, height: u32, stride: u32) -> Self {
match Self::try_new(data, width, height, stride) {
Ok(frame) => frame,
Err(_) => panic!("invalid BayerFrame dimensions or plane length"),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &'a [u8] {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn width(&self) -> u32 {
self.width
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn height(&self) -> u32 {
self.height
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stride(&self) -> u32 {
self.stride
}
}
#[derive(Debug, Clone, Copy)]
pub struct BayerFrame16<'a, const BITS: u32> {
data: &'a [u16],
width: u32,
height: u32,
stride: u32,
}
impl<'a, const BITS: u32> BayerFrame16<'a, BITS> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn try_new(
data: &'a [u16],
width: u32,
height: u32,
stride: u32,
) -> Result<Self, BayerFrame16Error> {
if BITS != 10 && BITS != 12 && BITS != 14 && BITS != 16 {
return Err(BayerFrame16Error::UnsupportedBits(UnsupportedBits::new(
BITS,
)));
}
if width == 0 || height == 0 {
return Err(BayerFrame16Error::ZeroDimension(ZeroDimension::new(
width, height,
)));
}
if stride < width {
return Err(BayerFrame16Error::InsufficientStride(
InsufficientStride::new(stride, width),
));
}
let min = match (stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(BayerFrame16Error::GeometryOverflow(GeometryOverflow::new(
stride, height,
)));
}
};
if data.len() < min {
return Err(BayerFrame16Error::InsufficientPlane(
InsufficientPlane::new(min, data.len()),
));
}
let max_valid: u16 = ((1u32 << BITS) - 1) as u16;
let w = width as usize;
let h = height as usize;
for row in 0..h {
let start = row * stride as usize;
for (col, &s) in data[start..start + w].iter().enumerate() {
if s > max_valid {
return Err(BayerFrame16Error::SampleOutOfRange(
BayerSampleOutOfRange::new(start + col, s, max_valid),
));
}
}
}
Ok(Self {
data,
width,
height,
stride,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn new(data: &'a [u16], width: u32, height: u32, stride: u32) -> Self {
match Self::try_new(data, width, height, stride) {
Ok(frame) => frame,
Err(_) => {
panic!("invalid BayerFrame16 dimensions, plane length, BITS value, or sample range")
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &'a [u16] {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn width(&self) -> u32 {
self.width
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn height(&self) -> u32 {
self.height
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stride(&self) -> u32 {
self.stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bits(&self) -> u32 {
BITS
}
}
pub type Bayer10Frame<'a> = BayerFrame16<'a, 10>;
pub type Bayer12Frame<'a> = BayerFrame16<'a, 12>;
pub type Bayer14Frame<'a> = BayerFrame16<'a, 14>;
pub type Bayer16Frame<'a> = BayerFrame16<'a, 16>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, TryUnwrap, Unwrap, Error)]
#[non_exhaustive]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum BayerFrameError {
#[error(transparent)]
ZeroDimension(ZeroDimension),
#[error(transparent)]
InsufficientStride(InsufficientStride),
#[error(transparent)]
InsufficientPlane(InsufficientPlane),
#[error(transparent)]
GeometryOverflow(GeometryOverflow),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, TryUnwrap, Unwrap, Error)]
#[non_exhaustive]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum BayerFrame16Error {
#[error(transparent)]
UnsupportedBits(UnsupportedBits),
#[error(transparent)]
ZeroDimension(ZeroDimension),
#[error(transparent)]
InsufficientStride(InsufficientStride),
#[error(transparent)]
InsufficientPlane(InsufficientPlane),
#[error(transparent)]
GeometryOverflow(GeometryOverflow),
#[error(transparent)]
SampleOutOfRange(BayerSampleOutOfRange),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
#[error("bayer sample {} at element {} exceeds {} ((1 << BITS) - 1)", self.value(), self.index(), self.max_valid())]
pub struct BayerSampleOutOfRange {
index: usize,
value: u16,
max_valid: u16,
}
impl BayerSampleOutOfRange {
#[inline]
pub const fn new(index: usize, value: u16, max_valid: u16) -> Self {
Self {
index,
value,
max_valid,
}
}
#[inline]
pub const fn index(&self) -> usize {
self.index
}
#[inline]
pub const fn value(&self) -> u16 {
self.value
}
#[inline]
pub const fn max_valid(&self) -> u16 {
self.max_valid
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum BayerPattern {
Bggr,
Rggb,
Grbg,
Gbrg,
}
impl BayerPattern {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Bggr => "bggr",
Self::Rggb => "rggb",
Self::Grbg => "grbg",
Self::Gbrg => "gbrg",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, IsVariant, Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum BayerDemosaic {
#[default]
Bilinear,
}
impl BayerDemosaic {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Bilinear => "Bilinear",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WhiteBalance {
r: f32,
g: f32,
b: f32,
}
impl WhiteBalance {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(r: f32, g: f32, b: f32) -> Result<Self, WhiteBalanceError> {
if !r.is_finite() {
return Err(WhiteBalanceError::NonFinite(
NonFiniteWhiteBalanceChannel::r(r),
));
}
if !g.is_finite() {
return Err(WhiteBalanceError::NonFinite(
NonFiniteWhiteBalanceChannel::g(g),
));
}
if !b.is_finite() {
return Err(WhiteBalanceError::NonFinite(
NonFiniteWhiteBalanceChannel::b(b),
));
}
if r < 0.0 {
return Err(WhiteBalanceError::Negative(NegativeWhiteBalanceChannel::r(
r,
)));
}
if g < 0.0 {
return Err(WhiteBalanceError::Negative(NegativeWhiteBalanceChannel::g(
g,
)));
}
if b < 0.0 {
return Err(WhiteBalanceError::Negative(NegativeWhiteBalanceChannel::b(
b,
)));
}
if r > Self::MAX_GAIN {
return Err(WhiteBalanceError::OutOfBounds(
WhiteBalanceChannelOutOfBounds::r(r, Self::MAX_GAIN),
));
}
if g > Self::MAX_GAIN {
return Err(WhiteBalanceError::OutOfBounds(
WhiteBalanceChannelOutOfBounds::g(g, Self::MAX_GAIN),
));
}
if b > Self::MAX_GAIN {
return Err(WhiteBalanceError::OutOfBounds(
WhiteBalanceChannelOutOfBounds::b(b, Self::MAX_GAIN),
));
}
Ok(Self { r, g, b })
}
pub const MAX_GAIN: f32 = 1.0e6;
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(r: f32, g: f32, b: f32) -> Self {
match Self::try_new(r, g, b) {
Ok(wb) => wb,
Err(_) => panic!("invalid WhiteBalance gains (non-finite, negative, or > MAX_GAIN)"),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn neutral() -> Self {
Self {
r: 1.0,
g: 1.0,
b: 1.0,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn r(&self) -> f32 {
self.r
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn g(&self) -> f32 {
self.g
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn b(&self) -> f32 {
self.b
}
}
impl Default for WhiteBalance {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::neutral()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ColorCorrectionMatrix {
m: [[f32; 3]; 3],
}
impl ColorCorrectionMatrix {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(m: [[f32; 3]; 3]) -> Result<Self, ColorCorrectionMatrixError> {
let mut row = 0;
while row < 3 {
let mut col = 0;
while col < 3 {
let v = m[row][col];
if !v.is_finite() {
return Err(ColorCorrectionMatrixError::NonFinite(
NonFiniteColorCorrectionMatrixElement::new(row, col, v),
));
}
if !(v >= -Self::MAX_COEFFICIENT_ABS && v <= Self::MAX_COEFFICIENT_ABS) {
return Err(ColorCorrectionMatrixError::OutOfBounds(
ColorCorrectionMatrixElementOutOfBounds::new(row, col, v, Self::MAX_COEFFICIENT_ABS),
));
}
col += 1;
}
row += 1;
}
Ok(Self { m })
}
pub const MAX_COEFFICIENT_ABS: f32 = 1.0e6;
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(m: [[f32; 3]; 3]) -> Self {
match Self::try_new(m) {
Ok(ccm) => ccm,
Err(_) => panic!(
"invalid ColorCorrectionMatrix element (non-finite or |value| > MAX_COEFFICIENT_ABS)"
),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn identity() -> Self {
Self {
m: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_array(&self) -> &[[f32; 3]; 3] {
&self.m
}
}
impl Default for ColorCorrectionMatrix {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::identity()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum WbChannel {
R,
G,
B,
}
impl WbChannel {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn as_str(&self) -> &'static str {
match self {
WbChannel::R => "R",
WbChannel::G => "G",
WbChannel::B => "B",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap, TryUnwrap, Error)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
#[non_exhaustive]
pub enum WhiteBalanceError {
#[error(transparent)]
NonFinite(#[from] NonFiniteWhiteBalanceChannel),
#[error(transparent)]
Negative(#[from] NegativeWhiteBalanceChannel),
#[error(transparent)]
OutOfBounds(#[from] WhiteBalanceChannelOutOfBounds),
}
#[derive(Debug, Clone, Copy, PartialEq, Error)]
#[error("white balance channel {} is non-finite (got {})", .channel.as_str(), .value)]
pub struct NonFiniteWhiteBalanceChannel {
channel: WbChannel,
value: f32,
}
impl NonFiniteWhiteBalanceChannel {
#[cfg_attr(not(tarpaulin), inline(always))]
const fn new(channel: WbChannel, value: f32) -> Self {
Self { channel, value }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn r(val: f32) -> Self {
Self::new(WbChannel::R, val)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn g(val: f32) -> Self {
Self::new(WbChannel::G, val)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn b(val: f32) -> Self {
Self::new(WbChannel::B, val)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channel(&self) -> WbChannel {
self.channel
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn value(&self) -> f32 {
self.value
}
}
#[derive(Debug, Clone, Copy, PartialEq, Error)]
#[error("white balance channel {} is negative (got {})", .channel.as_str(), .value)]
pub struct NegativeWhiteBalanceChannel {
channel: WbChannel,
value: f32,
}
impl NegativeWhiteBalanceChannel {
#[cfg_attr(not(tarpaulin), inline(always))]
const fn new(channel: WbChannel, value: f32) -> Self {
Self { channel, value }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn r(val: f32) -> Self {
Self::new(WbChannel::R, val)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn g(val: f32) -> Self {
Self::new(WbChannel::G, val)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn b(val: f32) -> Self {
Self::new(WbChannel::B, val)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channel(&self) -> WbChannel {
self.channel
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn value(&self) -> f32 {
self.value
}
}
#[derive(Debug, Clone, Copy, PartialEq, Error)]
#[error("white balance channel ({} = {value}) exceeds the magnitude bound ({max})", .channel.as_str())]
pub struct WhiteBalanceChannelOutOfBounds {
channel: WbChannel,
value: f32,
max: f32,
}
impl WhiteBalanceChannelOutOfBounds {
#[cfg_attr(not(tarpaulin), inline(always))]
const fn new(channel: WbChannel, value: f32, max: f32) -> Self {
Self {
channel,
value,
max,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn r(val: f32, max: f32) -> Self {
Self::new(WbChannel::R, val, max)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn g(val: f32, max: f32) -> Self {
Self::new(WbChannel::G, val, max)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn b(val: f32, max: f32) -> Self {
Self::new(WbChannel::B, val, max)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channel(&self) -> WbChannel {
self.channel
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn value(&self) -> f32 {
self.value
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn max(&self) -> f32 {
self.max
}
}
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap, TryUnwrap, Error)]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
#[non_exhaustive]
pub enum ColorCorrectionMatrixError {
#[error(transparent)]
NonFinite(#[from] NonFiniteColorCorrectionMatrixElement),
#[error(transparent)]
OutOfBounds(#[from] ColorCorrectionMatrixElementOutOfBounds),
}
#[derive(Debug, Clone, Copy, PartialEq, Error)]
#[error("ColorCorrectionMatrix[{row}][{col}] is non-finite (got {value})")]
pub struct NonFiniteColorCorrectionMatrixElement {
row: usize,
col: usize,
value: f32,
}
impl NonFiniteColorCorrectionMatrixElement {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(row: usize, col: usize, value: f32) -> Self {
Self { row, col, value }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn row(&self) -> usize {
self.row
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn col(&self) -> usize {
self.col
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn value(&self) -> f32 {
self.value
}
}
#[derive(Debug, Clone, Copy, PartialEq, Error)]
#[error(
"ColorCorrectionMatrix[{row}][{col}] = {value} exceeds the magnitude bound (|coeff| ≤ {max_abs})"
)]
pub struct ColorCorrectionMatrixElementOutOfBounds {
row: usize,
col: usize,
value: f32,
max_abs: f32,
}
impl ColorCorrectionMatrixElementOutOfBounds {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(row: usize, col: usize, value: f32, max_abs: f32) -> Self {
Self {
row,
col,
value,
max_abs,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn row(&self) -> usize {
self.row
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn col(&self) -> usize {
self.col
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn value(&self) -> f32 {
self.value
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn max_abs(&self) -> f32 {
self.max_abs
}
}
#[derive(Debug, Clone, Copy)]
pub struct BayerRow<'a> {
above: &'a [u8],
mid: &'a [u8],
below: &'a [u8],
row: usize,
pattern: BayerPattern,
demosaic: BayerDemosaic,
m: [[f32; 3]; 3],
}
impl<'a> BayerRow<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub const fn new(
above: &'a [u8],
mid: &'a [u8],
below: &'a [u8],
row: usize,
pattern: BayerPattern,
demosaic: BayerDemosaic,
m: [[f32; 3]; 3],
) -> Self {
Self {
above,
mid,
below,
row,
pattern,
demosaic,
m,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn above(&self) -> &'a [u8] {
self.above
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn mid(&self) -> &'a [u8] {
self.mid
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn below(&self) -> &'a [u8] {
self.below
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn row(&self) -> usize {
self.row
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn row_parity(&self) -> u32 {
(self.row & 1) as u32
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn pattern(&self) -> BayerPattern {
self.pattern
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn demosaic(&self) -> BayerDemosaic {
self.demosaic
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn m(&self) -> &[[f32; 3]; 3] {
&self.m
}
}
pub trait BayerSink: for<'a> PixelSink<Input<'a> = BayerRow<'a>> {}
pub fn bayer_to<S: BayerSink>(
src: &BayerFrame<'_>,
pattern: BayerPattern,
demosaic: BayerDemosaic,
wb: WhiteBalance,
ccm: ColorCorrectionMatrix,
sink: &mut S,
) -> Result<(), S::Error> {
sink.begin_frame(src.width(), src.height())?;
let m = fuse_wb_ccm(&wb, &ccm);
let w = src.width() as usize;
let h = src.height() as usize;
let stride = src.stride() as usize;
let plane = src.data();
for row in 0..h {
let above_row = if row == 0 {
if h >= 2 { 1 } else { 0 }
} else {
row - 1
};
let below_row = if row + 1 == h {
if h >= 2 { h - 2 } else { h - 1 }
} else {
row + 1
};
let above = &plane[above_row * stride..above_row * stride + w];
let mid = &plane[row * stride..row * stride + w];
let below = &plane[below_row * stride..below_row * stride + w];
sink.process(BayerRow::new(above, mid, below, row, pattern, demosaic, m))?;
}
Ok(())
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn fuse_wb_ccm(wb: &WhiteBalance, ccm: &ColorCorrectionMatrix) -> [[f32; 3]; 3] {
let m = ccm.as_array();
let (wr, wg, wb_) = (wb.r(), wb.g(), wb.b());
[
[m[0][0] * wr, m[0][1] * wg, m[0][2] * wb_],
[m[1][0] * wr, m[1][1] * wg, m[1][2] * wb_],
[m[2][0] * wr, m[2][1] * wg, m[2][2] * wb_],
]
}
#[derive(Debug, Clone, Copy)]
pub struct BayerRow16<'a, const BITS: u32> {
above: &'a [u16],
mid: &'a [u16],
below: &'a [u16],
row: usize,
pattern: BayerPattern,
demosaic: BayerDemosaic,
m: [[f32; 3]; 3],
}
impl<'a, const BITS: u32> BayerRow16<'a, BITS> {
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub const fn new(
above: &'a [u16],
mid: &'a [u16],
below: &'a [u16],
row: usize,
pattern: BayerPattern,
demosaic: BayerDemosaic,
m: [[f32; 3]; 3],
) -> Self {
Self {
above,
mid,
below,
row,
pattern,
demosaic,
m,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn above(&self) -> &'a [u16] {
self.above
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn mid(&self) -> &'a [u16] {
self.mid
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn below(&self) -> &'a [u16] {
self.below
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn row(&self) -> usize {
self.row
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn row_parity(&self) -> u32 {
(self.row & 1) as u32
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn pattern(&self) -> BayerPattern {
self.pattern
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn demosaic(&self) -> BayerDemosaic {
self.demosaic
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn m(&self) -> &[[f32; 3]; 3] {
&self.m
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bits(&self) -> u32 {
BITS
}
}
pub trait BayerSink16<const BITS: u32>:
for<'a> PixelSink<Input<'a> = BayerRow16<'a, BITS>>
{
}
pub fn bayer16_to<const BITS: u32, S: BayerSink16<BITS>>(
src: &BayerFrame16<'_, BITS>,
pattern: BayerPattern,
demosaic: BayerDemosaic,
wb: WhiteBalance,
ccm: ColorCorrectionMatrix,
sink: &mut S,
) -> Result<(), S::Error> {
let w = src.width() as usize;
let h = src.height() as usize;
let stride = src.stride() as usize;
let plane = src.data();
sink.begin_frame(src.width(), src.height())?;
let m = fuse_wb_ccm(&wb, &ccm);
for row in 0..h {
let above_row = if row == 0 {
if h >= 2 { 1 } else { 0 }
} else {
row - 1
};
let below_row = if row + 1 == h {
if h >= 2 { h - 2 } else { h - 1 }
} else {
row + 1
};
let above = &plane[above_row * stride..above_row * stride + w];
let mid = &plane[row * stride..row * stride + w];
let below = &plane[below_row * stride..below_row * stride + w];
sink.process(BayerRow16::<BITS>::new(
above, mid, below, row, pattern, demosaic, m,
))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn variants_construct_and_compare() {
assert_eq!(BayerPattern::Bggr, BayerPattern::Bggr);
assert_ne!(BayerPattern::Bggr, BayerPattern::Rggb);
}
#[test]
fn is_variant_helpers_work() {
assert!(BayerPattern::Bggr.is_bggr());
assert!(!BayerPattern::Bggr.is_rggb());
}
#[cfg(feature = "std")]
#[test]
fn copy_and_hash() {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let p = BayerPattern::Grbg;
let _copy = p; let mut h = DefaultHasher::new();
p.hash(&mut h);
let _ = h.finish();
}
#[cfg(feature = "std")]
#[test]
fn as_str_matches_display() {
use std::format;
for v in [
BayerPattern::Bggr,
BayerPattern::Rggb,
BayerPattern::Grbg,
BayerPattern::Gbrg,
] {
assert_eq!(v.as_str(), format!("{v}"));
}
assert_eq!(BayerPattern::Bggr.as_str(), "bggr");
}
}