use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Channel {
Red = 0,
Green = 1,
Blue = 2,
}
impl fmt::Display for Channel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Red => f.write_str("R"),
Self::Green => f.write_str("G"),
Self::Blue => f.write_str("B"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CfaPattern {
pattern: [Channel; 36],
width: usize,
height: usize,
}
use Channel::*;
const XTRANS_DEFAULT: [Channel; 36] = [
Red, Blue, Green, Blue, Red, Green,
Green, Green, Red, Green, Green, Blue,
Green, Green, Blue, Green, Green, Red,
Blue, Red, Green, Red, Blue, Green,
Green, Green, Blue, Green, Green, Red,
Green, Green, Red, Green, Green, Blue,
];
impl CfaPattern {
pub fn bayer_rggb() -> Self {
Self::bayer([Red, Green, Green, Blue])
}
pub fn bayer_bggr() -> Self {
Self::bayer([Blue, Green, Green, Red])
}
pub fn bayer_grbg() -> Self {
Self::bayer([Green, Red, Blue, Green])
}
pub fn bayer_gbrg() -> Self {
Self::bayer([Green, Blue, Red, Green])
}
fn bayer(pat: [Channel; 4]) -> Self {
let mut pattern = [Green; 36];
pattern[0] = pat[0];
pattern[1] = pat[1];
pattern[2] = pat[2];
pattern[3] = pat[3];
Self { pattern, width: 2, height: 2 }
}
pub fn xtrans(pattern: [Channel; 36]) -> Self {
Self { pattern, width: 6, height: 6 }
}
pub fn xtrans_default() -> Self {
Self { pattern: XTRANS_DEFAULT, width: 6, height: 6 }
}
pub fn quad_bayer_rggb() -> Self {
Self::quad_bayer([Red, Green, Green, Blue])
}
pub fn quad_bayer_bggr() -> Self {
Self::quad_bayer([Blue, Green, Green, Red])
}
pub fn quad_bayer_grbg() -> Self {
Self::quad_bayer([Green, Red, Blue, Green])
}
pub fn quad_bayer_gbrg() -> Self {
Self::quad_bayer([Green, Blue, Red, Green])
}
fn quad_bayer(base: [Channel; 4]) -> Self {
let mut pattern = [Green; 36];
for r in 0..2 {
for c in 0..2 {
let ch = base[r * 2 + c];
let r0 = 2 * r;
let c0 = 2 * c;
pattern[r0 * 4 + c0] = ch;
pattern[r0 * 4 + c0 + 1] = ch;
pattern[(r0 + 1) * 4 + c0] = ch;
pattern[(r0 + 1) * 4 + c0 + 1] = ch;
}
}
Self { pattern, width: 4, height: 4 }
}
pub fn shift(&self, dy: usize, dx: usize) -> Self {
let mut shifted = [Green; 36];
for y in 0..self.height {
for x in 0..self.width {
shifted[y * self.width + x] =
self.pattern[((y + dy) % self.height) * self.width + ((x + dx) % self.width)];
}
}
Self { pattern: shifted, width: self.width, height: self.height }
}
#[inline]
pub fn color_at(&self, row: usize, col: usize) -> Channel {
self.pattern[(row % self.height) * self.width + (col % self.width)]
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn is_bayer(&self) -> bool {
self.width == 2
}
pub fn is_quad_bayer(&self) -> bool {
self.width == 4 && self.height == 4
}
pub fn is_xtrans(&self) -> bool {
self.width == 6
}
pub fn quad_to_bayer(&self) -> Option<CfaPattern> {
if !self.is_quad_bayer() {
return None;
}
Some(Self::bayer([
self.color_at(0, 0),
self.color_at(0, 2),
self.color_at(2, 0),
self.color_at(2, 2),
]))
}
}
impl fmt::Display for CfaPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_bayer() {
for i in 0..4 {
write!(f, "{}", self.pattern[i])?;
}
Ok(())
} else if self.is_quad_bayer() {
write!(f, "Quad Bayer ")?;
write!(f, "{}{}{}{}", self.pattern[0], self.pattern[2], self.pattern[8], self.pattern[10])
} else {
write!(f, "X-Trans 6x6")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bayer_rggb_pattern() {
let cfa = CfaPattern::bayer_rggb();
assert_eq!(cfa.color_at(0, 0), Red);
assert_eq!(cfa.color_at(0, 1), Green);
assert_eq!(cfa.color_at(1, 0), Green);
assert_eq!(cfa.color_at(1, 1), Blue);
assert_eq!(cfa.color_at(2, 2), Red);
assert_eq!(cfa.color_at(3, 3), Blue);
}
#[test]
fn xtrans_default_pattern() {
let cfa = CfaPattern::xtrans_default();
assert_eq!(cfa.width(), 6);
assert_eq!(cfa.height(), 6);
assert_eq!(cfa.color_at(0, 0), Red);
assert_eq!(cfa.color_at(0, 1), Blue);
assert_eq!(cfa.color_at(0, 2), Green);
assert_eq!(cfa.color_at(6, 0), Red);
assert_eq!(cfa.color_at(0, 6), Red);
}
#[test]
fn xtrans_shift() {
let cfa = CfaPattern::xtrans_default();
let shifted = cfa.shift(1, 1);
assert_eq!(shifted.color_at(0, 0), cfa.color_at(1, 1));
assert_eq!(shifted.color_at(2, 3), cfa.color_at(3, 4));
}
#[test]
fn quad_bayer_rggb_pattern() {
let cfa = CfaPattern::quad_bayer_rggb();
assert_eq!(cfa.width(), 4);
assert_eq!(cfa.height(), 4);
assert!(cfa.is_quad_bayer());
assert!(!cfa.is_bayer());
assert!(!cfa.is_xtrans());
assert_eq!(cfa.color_at(0, 0), Red);
assert_eq!(cfa.color_at(0, 1), Red);
assert_eq!(cfa.color_at(0, 2), Green);
assert_eq!(cfa.color_at(0, 3), Green);
assert_eq!(cfa.color_at(1, 0), Red);
assert_eq!(cfa.color_at(1, 1), Red);
assert_eq!(cfa.color_at(1, 2), Green);
assert_eq!(cfa.color_at(1, 3), Green);
assert_eq!(cfa.color_at(2, 0), Green);
assert_eq!(cfa.color_at(2, 1), Green);
assert_eq!(cfa.color_at(2, 2), Blue);
assert_eq!(cfa.color_at(2, 3), Blue);
assert_eq!(cfa.color_at(3, 0), Green);
assert_eq!(cfa.color_at(3, 1), Green);
assert_eq!(cfa.color_at(3, 2), Blue);
assert_eq!(cfa.color_at(3, 3), Blue);
assert_eq!(cfa.color_at(4, 0), Red);
assert_eq!(cfa.color_at(0, 4), Red);
assert_eq!(cfa.color_at(6, 6), Blue);
}
#[test]
fn quad_bayer_all_variants() {
let rggb = CfaPattern::quad_bayer_rggb();
let bggr = CfaPattern::quad_bayer_bggr();
let grbg = CfaPattern::quad_bayer_grbg();
let gbrg = CfaPattern::quad_bayer_gbrg();
assert_eq!(rggb.color_at(0, 0), Red);
assert_eq!(bggr.color_at(0, 0), Blue);
assert_eq!(grbg.color_at(0, 0), Green);
assert_eq!(gbrg.color_at(0, 0), Green);
assert_eq!(rggb.color_at(2, 2), Blue);
assert_eq!(bggr.color_at(2, 2), Red);
assert_eq!(grbg.color_at(2, 2), Green);
assert_eq!(gbrg.color_at(2, 2), Green);
for cfa in &[&rggb, &bggr, &grbg, &gbrg] {
assert!(cfa.is_quad_bayer());
}
}
#[test]
fn quad_bayer_shift() {
let cfa = CfaPattern::quad_bayer_rggb();
let shifted = cfa.shift(1, 1);
assert_eq!(shifted.color_at(0, 0), cfa.color_at(1, 1));
assert_eq!(shifted.color_at(2, 3), cfa.color_at(3, 0));
}
#[test]
fn quad_to_bayer_mapping() {
assert_eq!(
CfaPattern::quad_bayer_rggb().quad_to_bayer().unwrap(),
CfaPattern::bayer_rggb()
);
assert_eq!(
CfaPattern::quad_bayer_bggr().quad_to_bayer().unwrap(),
CfaPattern::bayer_bggr()
);
assert_eq!(
CfaPattern::quad_bayer_grbg().quad_to_bayer().unwrap(),
CfaPattern::bayer_grbg()
);
assert_eq!(
CfaPattern::quad_bayer_gbrg().quad_to_bayer().unwrap(),
CfaPattern::bayer_gbrg()
);
}
#[test]
fn quad_to_bayer_returns_none_for_non_quad() {
assert!(CfaPattern::bayer_rggb().quad_to_bayer().is_none());
assert!(CfaPattern::xtrans_default().quad_to_bayer().is_none());
}
#[test]
fn quad_bayer_color_count_per_tile() {
let cfa = CfaPattern::quad_bayer_rggb();
let mut counts = [0u32; 3];
for r in 0..4 {
for c in 0..4 {
counts[cfa.color_at(r, c) as usize] += 1;
}
}
assert_eq!(counts[Red as usize], 4);
assert_eq!(counts[Green as usize], 8);
assert_eq!(counts[Blue as usize], 4);
}
#[test]
fn xtrans_every_3x3_has_all_colors() {
let cfa = CfaPattern::xtrans_default();
for by in 0..6 {
for bx in 0..6 {
let mut has = [false; 3];
for y in 0..3 {
for x in 0..3 {
has[cfa.color_at(by + y, bx + x) as usize] = true;
}
}
assert!(has[0] && has[1] && has[2],
"3x3 block at ({by},{bx}) missing a color");
}
}
}
}