use fovea_derive::{HomogeneousPixel, LinearPixel, PlainPixel, WhiteChannel, ZeroablePixel};
use std::num::Saturating;
use crate::pixel::{
FromLinear, HomogeneousPixel, LinearPixel, Mono, MonoF32, PlainChannel, PlainPixel, Rgb, Rgb8,
Rgb16, SingleChannel, WhiteChannel, ZeroablePixel, impl_single_channel, single_channel_sealed,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CfaColor {
Red,
Green,
Blue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BayerPattern {
Rggb,
Bggr,
Grbg,
Gbrg,
}
impl BayerPattern {
#[inline(always)]
pub const fn tile(self) -> [[CfaColor; 2]; 2] {
use CfaColor::{Blue as B, Green as G, Red as R};
match self {
BayerPattern::Rggb => [[R, G], [G, B]],
BayerPattern::Bggr => [[B, G], [G, R]],
BayerPattern::Grbg => [[G, R], [B, G]],
BayerPattern::Gbrg => [[G, B], [R, G]],
}
}
#[inline(always)]
pub const fn color_at(self, x: usize, y: usize) -> CfaColor {
self.tile()[y % 2][x % 2]
}
}
pub trait BayerPixel: PlainPixel {
const PATTERN: BayerPattern;
type RgbOutput: PlainPixel;
}
macro_rules! define_bayer_pattern {
(
pattern: $pattern:expr,
tile_doc: $tile_doc:expr,
sfnc: $sfnc:expr,
generic: $Generic:ident,
p8: $P8:ident, p16: $P16:ident,
p10: $P10:ident, p12: $P12:ident, p14: $P14:ident,
) => {
#[doc = concat!("An 8-bit raw Bayer sample from a ", $tile_doc, " sensor.")]
#[doc = concat!("GenICam SFNC name: `", $sfnc, "8`.")]
#[doc = concat!("# use fovea::pixel::bayer::", stringify!($P8), ";")]
#[doc = concat!("let s = ", stringify!($P8), "::new(42);")]
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
PlainPixel,
HomogeneousPixel,
ZeroablePixel,
LinearPixel,
WhiteChannel,
)]
#[linear(accumulator = MonoF32, no_space)]
pub struct $P8(Saturating<u8>);
impl $P8 {
#[doc = concat!("Creates a `", stringify!($P8), "` from a raw 8-bit sample.")]
#[inline]
pub const fn new(value: u8) -> Self {
$P8(Saturating(value))
}
#[inline]
pub const fn value(self) -> u8 {
self.0.0
}
}
impl From<$P8> for u8 {
#[inline]
fn from(p: $P8) -> u8 {
p.0.0
}
}
impl From<u8> for $P8 {
#[inline]
fn from(v: u8) -> Self {
$P8::new(v)
}
}
impl BayerPixel for $P8 {
const PATTERN: BayerPattern = $pattern;
type RgbOutput = Rgb8;
}
#[doc = concat!("A 16-bit raw Bayer sample from a ", $tile_doc, " sensor.")]
#[doc = concat!("GenICam SFNC name: `", $sfnc, "16`.")]
#[doc = concat!("# use fovea::pixel::bayer::", stringify!($P16), ";")]
#[doc = concat!("let s = ", stringify!($P16), "::new(4242);")]
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
PlainPixel,
HomogeneousPixel,
ZeroablePixel,
LinearPixel,
WhiteChannel,
)]
#[linear(accumulator = MonoF32, no_space)]
pub struct $P16(Saturating<u16>);
impl $P16 {
#[doc = concat!("Creates a `", stringify!($P16), "` from a raw 16-bit sample.")]
#[inline]
pub const fn new(value: u16) -> Self {
$P16(Saturating(value))
}
#[inline]
pub const fn value(self) -> u16 {
self.0.0
}
}
impl From<$P16> for u16 {
#[inline]
fn from(p: $P16) -> u16 {
p.0.0
}
}
impl From<u16> for $P16 {
#[inline]
fn from(v: u16) -> Self {
$P16::new(v)
}
}
impl BayerPixel for $P16 {
const PATTERN: BayerPattern = $pattern;
type RgbOutput = Rgb16;
}
#[doc = concat!(
"A sub-word (10/12/14-bit) raw Bayer sample from a ", $tile_doc, " sensor."
)]
#[doc = concat!(
"[`", stringify!($P10), "`] / [`", stringify!($P12),
"`] / [`", stringify!($P14), "`] aliases."
)]
#[doc = concat!("# use fovea::pixel::bayer::", stringify!($P12), ";")]
#[doc = concat!("let s = ", stringify!($P12), "::new(9000);")]
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct $Generic<const BITS: usize>(Mono<BITS>);
#[doc = concat!("A 10-bit raw Bayer sample from a ", $tile_doc, " sensor.")]
#[doc = concat!("GenICam SFNC name: `", $sfnc, "10`.")]
pub type $P10 = $Generic<10>;
#[doc = concat!("A 12-bit raw Bayer sample from a ", $tile_doc, " sensor.")]
#[doc = concat!("GenICam SFNC name: `", $sfnc, "12`.")]
pub type $P12 = $Generic<12>;
#[doc = concat!("A 14-bit raw Bayer sample from a ", $tile_doc, " sensor.")]
#[doc = concat!("GenICam SFNC name: `", $sfnc, "14`.")]
pub type $P14 = $Generic<14>;
impl<const BITS: usize> $Generic<BITS> {
#[doc = concat!(
"Creates a `", stringify!($Generic),
"<BITS>`, clamping `value` to the bit-depth maximum."
)]
#[inline]
pub fn new(value: u16) -> Self {
$Generic(Mono::new(value))
}
#[inline]
pub fn value(self) -> u16 {
self.0.value()
}
}
impl<const BITS: usize> From<$Generic<BITS>> for u16 {
#[inline]
fn from(p: $Generic<BITS>) -> u16 {
p.0.value()
}
}
impl<const BITS: usize> From<u16> for $Generic<BITS> {
#[inline]
fn from(v: u16) -> Self {
$Generic::new(v)
}
}
impl<const BITS: usize> BayerPixel for $Generic<BITS> {
const PATTERN: BayerPattern = $pattern;
type RgbOutput = Rgb<BITS>;
}
unsafe impl<const BITS: usize> PlainChannel for $Generic<BITS> {}
unsafe impl<const BITS: usize> PlainPixel for $Generic<BITS> {
const CHANNELS: &'static [usize] = &[2];
}
unsafe impl<const BITS: usize> HomogeneousPixel for $Generic<BITS> {
type Channel = Saturating<u16>;
type Channels = [Saturating<u16>; 1];
}
impl<const BITS: usize> ZeroablePixel for $Generic<BITS> {
#[inline]
fn zero() -> Self {
$Generic(<Mono<BITS> as ZeroablePixel>::zero())
}
}
impl<const BITS: usize> WhiteChannel for $Generic<BITS> {
#[inline(always)]
fn white_channel() -> Saturating<u16> {
<Mono<BITS> as WhiteChannel>::white_channel()
}
}
impl<const BITS: usize> LinearPixel for $Generic<BITS> {
type Accumulator = MonoF32;
#[inline(always)]
fn to_accumulator(&self) -> MonoF32 {
<Mono<BITS> as LinearPixel>::to_accumulator(&self.0)
}
#[inline(always)]
fn scale(&self, scalar: f32) -> MonoF32 {
<Mono<BITS> as LinearPixel>::scale(&self.0, scalar)
}
#[inline(always)]
fn scale_add(&self, scalar: f32, addend: MonoF32) -> MonoF32 {
<Mono<BITS> as LinearPixel>::scale_add(&self.0, scalar, addend)
}
#[inline(always)]
fn uniform(scalar: f32) -> MonoF32 {
MonoF32(scalar)
}
}
impl<const BITS: usize> FromLinear<MonoF32> for $Generic<BITS> {
#[inline(always)]
fn from_linear(acc: MonoF32) -> Self {
$Generic(<Mono<BITS> as FromLinear<MonoF32>>::from_linear(acc))
}
}
impl<const BITS: usize> std::ops::Add for $Generic<BITS> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self {
$Generic(self.0 + other.0)
}
}
impl<const BITS: usize> std::ops::Sub for $Generic<BITS> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
$Generic(self.0 - other.0)
}
}
impl<const BITS: usize> std::ops::Mul for $Generic<BITS> {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
$Generic(self.0 * other.0)
}
}
impl_single_channel!($P8, $P16);
impl<const BITS: usize> single_channel_sealed::Sealed for $Generic<BITS> {}
impl<const BITS: usize> SingleChannel for $Generic<BITS> {}
};
}
define_bayer_pattern! {
pattern: BayerPattern::Rggb,
tile_doc: "`R G` / `G B` (RGGB)",
sfnc: "BayerRG",
generic: BayerRggb,
p8: BayerRggb8, p16: BayerRggb16,
p10: BayerRggb10, p12: BayerRggb12, p14: BayerRggb14,
}
define_bayer_pattern! {
pattern: BayerPattern::Bggr,
tile_doc: "`B G` / `G R` (BGGR)",
sfnc: "BayerBG",
generic: BayerBggr,
p8: BayerBggr8, p16: BayerBggr16,
p10: BayerBggr10, p12: BayerBggr12, p14: BayerBggr14,
}
define_bayer_pattern! {
pattern: BayerPattern::Grbg,
tile_doc: "`G R` / `B G` (GRBG)",
sfnc: "BayerGR",
generic: BayerGrbg,
p8: BayerGrbg8, p16: BayerGrbg16,
p10: BayerGrbg10, p12: BayerGrbg12, p14: BayerGrbg14,
}
define_bayer_pattern! {
pattern: BayerPattern::Gbrg,
tile_doc: "`G B` / `R G` (GBRG)",
sfnc: "BayerGB",
generic: BayerGbrg,
p8: BayerGbrg8, p16: BayerGbrg16,
p10: BayerGbrg10, p12: BayerGbrg12, p14: BayerGbrg14,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixel::{LinearSpace, Mono12, PlainChannel};
#[test]
fn every_tile_has_two_greens_one_red_one_blue() {
for p in [
BayerPattern::Rggb,
BayerPattern::Bggr,
BayerPattern::Grbg,
BayerPattern::Gbrg,
] {
let flat: Vec<CfaColor> = p.tile().into_iter().flatten().collect();
assert_eq!(
flat.iter().filter(|c| **c == CfaColor::Green).count(),
2,
"{p:?} must have two green sites"
);
assert_eq!(flat.iter().filter(|c| **c == CfaColor::Red).count(), 1);
assert_eq!(flat.iter().filter(|c| **c == CfaColor::Blue).count(), 1);
}
}
#[test]
fn color_at_matches_the_tile_for_every_parity() {
for p in [
BayerPattern::Rggb,
BayerPattern::Bggr,
BayerPattern::Grbg,
BayerPattern::Gbrg,
] {
let tile = p.tile();
for y in 0..7usize {
for x in 0..7usize {
assert_eq!(p.color_at(x, y), tile[y % 2][x % 2], "{p:?} at ({x}, {y})");
}
}
}
}
#[test]
fn the_four_patterns_are_the_four_odd_origin_shifts_of_each_other() {
let r = BayerPattern::Rggb;
for (dx, dy, expected) in [
(1usize, 0usize, BayerPattern::Grbg),
(0, 1, BayerPattern::Gbrg),
(1, 1, BayerPattern::Bggr),
] {
for y in 0..4usize {
for x in 0..4usize {
assert_eq!(
r.color_at(x + dx, y + dy),
expected.color_at(x, y),
"shift ({dx}, {dy}) at ({x}, {y})"
);
}
}
}
}
#[test]
fn pattern_const_is_distinct_per_family() {
assert_eq!(BayerRggb8::PATTERN, BayerPattern::Rggb);
assert_eq!(BayerBggr16::PATTERN, BayerPattern::Bggr);
assert_eq!(BayerGrbg12::PATTERN, BayerPattern::Grbg);
assert_eq!(BayerGbrg10::PATTERN, BayerPattern::Gbrg);
}
#[test]
fn transparent_layout_matches_the_underlying_sample() {
assert_eq!(size_of::<BayerRggb8>(), size_of::<u8>());
assert_eq!(size_of::<BayerRggb16>(), size_of::<u16>());
assert_eq!(size_of::<BayerRggb12>(), size_of::<u16>());
assert_eq!(<BayerRggb8 as PlainChannel>::SIZE, 1);
assert_eq!(<BayerRggb12 as PlainChannel>::SIZE, 2);
}
#[test]
fn cast_slice_reinterprets_a_camera_buffer_without_copying() {
let raw = [1u8, 2, 3, 4];
let samples = BayerRggb8::cast_slice(&raw).unwrap();
assert_eq!(samples.len(), 4);
assert_eq!(samples[2].value(), 3);
assert_eq!(samples.as_ptr() as usize, raw.as_ptr() as usize);
}
#[test]
fn sub_word_depths_clamp_like_mono() {
assert_eq!(BayerRggb10::new(2000).value(), 1023);
assert_eq!(BayerRggb12::new(9000).value(), 4095);
assert_eq!(BayerRggb14::new(65535).value(), 16383);
assert_eq!(BayerRggb12::new(1234).value(), 1234);
}
#[test]
fn white_channel_is_the_depth_maximum_not_the_storage_maximum() {
assert_eq!(
<BayerRggb12 as WhiteChannel>::white_channel(),
<Mono12 as WhiteChannel>::white_channel()
);
assert_eq!(
<BayerRggb12 as WhiteChannel>::white_channel(),
Saturating(4095)
);
assert_eq!(
<BayerRggb16 as WhiteChannel>::white_channel(),
Saturating(u16::MAX)
);
}
#[test]
fn zero_is_the_zero_sample() {
assert_eq!(<BayerGbrg14 as ZeroablePixel>::zero().value(), 0);
assert_eq!(<BayerGbrg8 as ZeroablePixel>::zero().value(), 0);
}
#[test]
fn round_trips_through_the_raw_scalar() {
for v in [0u8, 1, 42, 255] {
let p: BayerBggr8 = v.into();
let back: u8 = p.into();
assert_eq!(back, v);
}
for v in [0u16, 1, 4095] {
let p: BayerGrbg12 = v.into();
let back: u16 = p.into();
assert_eq!(back, v);
}
}
#[test]
fn weighted_sums_are_available_at_every_depth() {
let a = BayerRggb8::new(100);
let b = BayerRggb8::new(200);
assert_eq!(a.scale_add(0.5, b.scale(0.5)), MonoF32(150.0));
let c = BayerRggb12::new(1000);
let d = BayerRggb12::new(3000);
assert_eq!(c.scale_add(0.5, d.scale(0.5)), MonoF32(2000.0));
}
#[test]
fn from_linear_returns_to_the_bayer_type_and_re_clamps() {
let back: BayerRggb12 = FromLinear::from_linear(MonoF32(9000.0));
assert_eq!(back.value(), 4095);
let back: BayerRggb8 = FromLinear::from_linear(MonoF32(42.4));
assert_eq!(back.value(), 42);
}
#[test]
fn channel_wise_arithmetic_is_uniform_across_depths() {
assert_eq!(
BayerRggb8::new(100) + BayerRggb8::new(50),
BayerRggb8::new(150)
);
assert_eq!(
BayerRggb12::new(1000) + BayerRggb12::new(500),
BayerRggb12::new(1500)
);
assert_eq!(
BayerRggb12::new(1000) - BayerRggb12::new(500),
BayerRggb12::new(500)
);
}
#[test]
fn bayer_types_are_not_in_a_linear_space() {
fn assert_linear_space<P: LinearSpace>() {}
assert_linear_space::<Mono12>();
}
#[test]
fn every_shipped_bayer_type_implements_bayer_pixel() {
fn assert_bayer<B: BayerPixel>() {}
macro_rules! all {
($($t:ty),+ $(,)?) => {{ $( assert_bayer::<$t>(); )+ }};
}
all!(
BayerRggb8,
BayerRggb10,
BayerRggb12,
BayerRggb14,
BayerRggb16,
BayerBggr8,
BayerBggr10,
BayerBggr12,
BayerBggr14,
BayerBggr16,
BayerGrbg8,
BayerGrbg10,
BayerGrbg12,
BayerGrbg14,
BayerGrbg16,
BayerGbrg8,
BayerGbrg10,
BayerGbrg12,
BayerGbrg14,
BayerGbrg16,
);
}
#[test]
fn distinct_patterns_are_distinct_types_at_the_same_depth() {
fn output_of<B: BayerPixel>() -> BayerPattern {
B::PATTERN
}
assert_ne!(output_of::<BayerRggb12>(), output_of::<BayerBggr12>());
}
}