use crate::process::Developed;
use flashraw_core::{FrameInfo, RawMetadata, Rect, SensorLayout, par};
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct Stagger {
pub rows: bool,
pub anchor: u32,
pub along: f64,
pub across: f64,
pub offset: bool,
}
pub(crate) fn of(meta: &RawMetadata, info: &FrameInfo) -> Option<Stagger> {
let pa = info.pixel_aspect as f64;
if pa.is_finite() && pa > 0.0 && (pa - 1.0).abs() > 0.01 {
return Some(Stagger { rows: true, anchor: 0, along: pa.max(1.0), across: (1.0 / pa).max(1.0), offset: false });
}
if info.layout != SensorLayout::OtherCfa {
return None;
}
let s2 = std::f64::consts::SQRT_2;
let a = info.active_area;
match meta.extra("fuji_rotate") {
Some("layout1") => return Some(Stagger { rows: true, anchor: a.y, along: s2, across: 1.0 / s2, offset: true }),
Some("layout0") => {
return Some(Stagger { rows: false, anchor: a.x, along: s2, across: 1.0 / s2, offset: true });
}
_ => {}
}
let layout: u32 = meta.extra("cfa_layout")?.parse().ok()?;
let (sx, sy) = meta
.dng
.as_ref()
.and_then(|d| d.default_scale)
.filter(|&(x, y)| x.is_finite() && y.is_finite() && x > 0.0 && y > 0.0)
.unwrap_or((1.0, 1.0));
let m = sx.min(sy);
let (sx, sy) = (sx / m, sy / m);
match layout {
2 => Some(Stagger { rows: false, anchor: 1, along: sy, across: sx, offset: true }),
3 => Some(Stagger { rows: false, anchor: 0, along: sy, across: sx, offset: true }),
4 => Some(Stagger { rows: true, anchor: 1, along: sx, across: sy, offset: true }),
5 => Some(Stagger { rows: true, anchor: 0, along: sx, across: sy, offset: true }),
_ => None,
}
}
impl Stagger {
pub fn output_size(&self, w: u32, h: u32) -> (u32, u32) {
let (n_along, n_lines) = if self.rows { (w, h) } else { (h, w) };
let along = ((n_along as f64 - 0.5).max(0.0) * self.along).floor() as u32 + 1;
let across = ((n_lines.saturating_sub(1)) as f64 * self.across).floor() as u32 + 1;
if self.rows { (along, across) } else { (across, along) }
}
pub fn resample(&self, d: &Developed, win: Rect, parallel: bool) -> Developed {
let (w, h) = (d.width as usize, d.height as usize);
let (ow, oh) = self.output_size(d.width, d.height);
let mut out = vec![0f32; ow as usize * oh as usize * 3];
if w == 0 || h == 0 {
return Developed { width: ow, height: oh, rgb: out };
}
let (n_lines, n_along) = if self.rows { (h, w) } else { (w, h) };
let first_line = if self.rows { win.y } else { win.x };
let offset = |l: usize| {
if self.offset { ((first_line as i64 + l as i64 - self.anchor as i64) & 1) as f64 * 0.5 } else { 0.0 }
};
let px = |l: usize, k: usize| {
let i = if self.rows { l * w + k } else { k * w + l };
[d.rgb[i * 3], d.rgb[i * 3 + 1], d.rgb[i * 3 + 2]]
};
let along = |l: usize, t: f64| {
let t = (t - offset(l)).clamp(0.0, (n_along - 1) as f64);
let k0 = t.floor() as usize;
let k1 = (k0 + 1).min(n_along - 1);
let f = (t - k0 as f64) as f32;
let (a, b) = (px(l, k0), px(l, k1));
[a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, a[2] + (b[2] - a[2]) * f]
};
let row_len = ow as usize * 3;
par::for_each_row_chunk(&mut out, row_len, 16, parallel, |first, chunk| {
for (i, row) in chunk.chunks_mut(row_len).enumerate() {
let y = first + i;
for (x, o) in row.chunks_exact_mut(3).enumerate() {
let (u, v) = if self.rows { (x, y) } else { (y, x) };
let lf = (v as f64 / self.across).clamp(0.0, (n_lines - 1) as f64);
let l0 = lf.floor() as usize;
let l1 = (l0 + 1).min(n_lines - 1);
let f = (lf - l0 as f64) as f32;
let t = u as f64 / self.along;
let (a, b) = (along(l0, t), along(l1, t));
for c in 0..3 {
o[c] = a[c] + (b[c] - a[c]) * f;
}
}
}
});
Developed { width: ow, height: oh, rgb: out }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn developed(w: u32, h: u32, f: impl Fn(u32, u32) -> f32) -> Developed {
let mut rgb = Vec::with_capacity((w * h * 3) as usize);
for y in 0..h {
for x in 0..w {
rgb.extend_from_slice(&[f(x, y); 3]);
}
}
Developed { width: w, height: h, rgb }
}
#[test]
fn superccd_layout1_geometry() {
let s = Stagger {
rows: true,
anchor: 0,
along: std::f64::consts::SQRT_2,
across: std::f64::consts::FRAC_1_SQRT_2,
offset: true,
};
let (ow, oh) = s.output_size(1000, 2000);
assert_eq!((ow, oh), (1414, 1414));
let x_of = |c: u32, r: u32| (2.0 * c as f32 + (r & 1) as f32) / std::f32::consts::SQRT_2;
let d = developed(100, 200, x_of);
let o = s.resample(&d, Rect::new(0, 0, 100, 200), false);
for y in [3usize, 50, 120] {
for x in [10usize, 40, 130] {
let v = o.rgb[(y * o.width as usize + x) * 3];
assert!((v - x as f32).abs() < 1e-3, "({x}, {y}) -> {v}");
}
}
let d = developed(100, 200, |c, r| x_of(c, r + 1));
let o = s.resample(&d, Rect::new(0, 1, 100, 200), false);
let v = o.rgb[(10 * o.width as usize + 40) * 3];
assert!((v - 40.0).abs() < 1e-3, "{v}");
}
#[test]
fn layout0_is_layout1_transposed() {
let s1 = Stagger { rows: true, anchor: 0, along: 1.5, across: 0.75, offset: true };
let s0 = Stagger { rows: false, ..s1 };
let f = |a: u32, b: u32| (a * 7 + b * 3) as f32 + (a % 3) as f32;
let (w, h) = (31u32, 17u32);
let a = s1.resample(&developed(w, h, f), Rect::new(0, 0, w, h), true);
let b = s0.resample(&developed(h, w, |x, y| f(y, x)), Rect::new(0, 0, h, w), true);
assert_eq!((a.width, a.height), (b.height, b.width));
for y in 0..a.height as usize {
for x in 0..a.width as usize {
assert_eq!(a.rgb[(y * a.width as usize + x) * 3], b.rgb[(x * b.width as usize + y) * 3]);
}
}
}
}