use crate::{BayerPattern, ColorSpace, DemosaicMethod, PixelType};
use super::{ImageSpec, Op, PipelineError, ResizeFilter, ScaleFactor, Strategy};
pub(super) struct Plan {
pub(super) steps: Vec<Step>,
pub(super) coeffs: Vec<Box<[f64]>>,
pub(super) specs: Vec<ImageSpec>, pub(super) out_spec: ImageSpec,
}
impl Plan {
pub(super) fn build(ops: &[Op], input: &ImageSpec) -> Result<Self, PipelineError> {
let mut cur = input.clone();
let mut specs = Vec::with_capacity(ops.len() + 1);
specs.push(cur.clone());
let mut steps = Vec::with_capacity(ops.len());
let mut coeffs: Vec<Box<[f64]>> = Vec::new();
for op in ops {
let next = op.output_spec(&cur)?;
let mut step = Step {
kind: StepKind::Convert,
in_pt: cur.pixel_type,
out_pt: next.pixel_type.storage(),
in_channels: cur.cspace.channels(),
bayer: None,
coeff_idx: 0,
luma_identity: false,
};
match op {
Op::Debayer(m) => {
let ColorSpace::Bayer(pat) = cur.cspace else {
unreachable!("checked by output_spec")
};
step.kind = StepKind::Debayer(*m);
step.bayer = Some(pat);
}
Op::ToLuma | Op::ToLumaCustom(_) => {
step.kind = StepKind::Luma;
if matches!(cur.cspace, ColorSpace::Gray) {
step.luma_identity = true;
} else {
let w: Box<[f64]> = match op {
Op::ToLumaCustom(v) => v.clone().into_boxed_slice(),
_ => Box::new([0.299, 0.587, 0.114]),
};
step.coeff_idx = coeffs.len();
coeffs.push(w);
}
}
Op::Scale { gain, offset } => {
step.kind = StepKind::Scale {
gain: *gain,
offset: *offset,
}
}
Op::ScalePixels(factor) => step.kind = StepKind::ScalePixels(*factor),
Op::Convert(_) => step.kind = StepKind::Convert,
Op::Crop { x, y, .. } => {
step.kind = StepKind::Crop {
x: *x,
y: *y,
w: next.width,
h: next.height,
}
}
Op::Roi { x, y, .. } => {
step.kind = StepKind::Roi {
x: *x,
y: *y,
w: next.width,
h: next.height,
}
}
Op::FlipHorizontal => {
step.kind = StepKind::Flip {
horizontal: true,
vertical: false,
}
}
Op::FlipVertical => {
step.kind = StepKind::Flip {
horizontal: false,
vertical: true,
}
}
Op::Rotate180 => {
step.kind = StepKind::Flip {
horizontal: true,
vertical: true,
}
}
Op::Rotate90 => step.kind = StepKind::Rot90 { ccw: false },
Op::Rotate270 => step.kind = StepKind::Rot90 { ccw: true },
Op::ResizeToFit { filter, .. } => {
step.kind = StepKind::Resize {
w: next.width,
h: next.height,
filter: *filter,
}
}
Op::Nop => {
step.kind = StepKind::Nop;
}
}
steps.push(step);
specs.push(next.clone());
cur = next;
}
Ok(Plan {
steps,
coeffs,
out_spec: cur,
specs,
})
}
pub(super) fn buf_caps(
&self,
lo: usize,
hi: usize,
cell: impl Fn(&ImageSpec) -> Result<usize, PipelineError>,
) -> Result<(usize, usize), PipelineError> {
let mut in_b = false;
let mut a = cell(&self.specs[lo])?;
let mut b = 0usize;
for i in lo..hi {
if self.steps[i].swaps() {
in_b = !in_b;
}
let c = cell(&self.specs[i + 1])?;
if in_b {
b = b.max(c);
} else {
a = a.max(c);
}
}
Ok((a.max(1), b.max(1)))
}
pub(super) fn max_bytes(&self, lo: usize, hi: usize) -> Result<usize, PipelineError> {
let mut m = 0;
for s in &self.specs[lo..=hi] {
m = m.max(s.bytes()?);
}
Ok(m.max(1))
}
}
pub(super) fn f32_cap(bytes: usize) -> usize {
bytes.div_ceil(4).max(1)
}
const TILE_TARGET_BYTES: usize = 256 * 1024;
pub(super) struct ResolvedTile {
pub(super) tile_rows: usize,
pub(super) tile_cols: usize,
pub(super) halo: usize,
pub(super) even: bool,
pub(super) parallel: bool,
}
#[derive(Debug, Clone, Copy)]
pub(super) enum TailPhase {
Tiled {
lo: usize,
hi: usize,
tile_rows: usize,
tile_cols: usize,
parallel: bool,
},
Whole { lo: usize, hi: usize },
}
fn push_whole(phases: &mut Vec<TailPhase>, lo: usize, hi: usize) {
if let Some(TailPhase::Whole { hi: prev_hi, .. }) = phases.last_mut()
&& *prev_hi == lo
{
*prev_hi = hi;
return;
}
phases.push(TailPhase::Whole { lo, hi });
}
pub(super) fn build_tail_phases(
steps: &[Step],
specs: &[ImageSpec],
strategy: Strategy,
start: usize,
) -> Vec<TailPhase> {
let n = steps.len();
let mut phases = Vec::new();
let mut i = start;
while i < n {
let geo = steps[i].kind.is_geometric();
let mut j = i + 1;
while j < n && steps[j].kind.is_geometric() == geo {
j += 1;
}
let s = &specs[i];
match (geo, resolve_exec(strategy, s.width, s.height, 0, false)) {
(false, Some(rt)) => phases.push(TailPhase::Tiled {
lo: i,
hi: j,
tile_rows: rt.tile_rows,
tile_cols: rt.tile_cols,
parallel: rt.parallel,
}),
_ => push_whole(&mut phases, i, j),
}
i = j;
}
phases
}
pub(super) fn resolve_exec(
strategy: Strategy,
w: usize,
h: usize,
halo: usize,
even: bool,
) -> Option<ResolvedTile> {
let Strategy::Tiled {
tile_rows,
tile_cols,
parallel,
} = strategy
else {
return None;
};
let min_dim = 2 * halo + 6;
let mut cols = if tile_cols == 0 || tile_cols >= w {
0 } else {
tile_cols
};
if cols != 0 && cols < min_dim {
cols = 0; }
let band_w = if cols != 0 { cols } else { w };
let rows = if tile_rows == 0 {
(TILE_TARGET_BYTES / (band_w * 6).max(1)).clamp(1, h)
} else {
tile_rows
};
let one_band = rows >= h;
let one_col = cols == 0;
if (one_band && one_col) || h < min_dim {
return None;
}
Some(ResolvedTile {
tile_rows: rows.min(h),
tile_cols: cols,
halo,
even,
parallel,
})
}
#[derive(Debug, Clone, Copy)]
pub(super) enum Exec {
Sequential,
Tiled {
tile_rows: usize,
tile_cols: usize, halo: usize,
even: bool,
parallel: bool,
in_off_x: usize,
in_off_y: usize,
prefix_lo: usize,
prefix_hi: usize,
},
}
#[derive(Debug, Clone, Copy)]
pub(super) struct Step {
pub(super) kind: StepKind,
pub(super) in_pt: PixelType,
pub(super) out_pt: PixelType,
pub(super) in_channels: u8,
pub(super) bayer: Option<BayerPattern>,
pub(super) coeff_idx: usize,
pub(super) luma_identity: bool,
}
impl Step {
pub(super) fn swaps(&self) -> bool {
matches!(
self.kind,
StepKind::Debayer(_)
| StepKind::Crop { .. }
| StepKind::Roi { .. }
| StepKind::Flip { .. }
| StepKind::Rot90 { .. }
| StepKind::Resize { .. }
)
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum StepKind {
Debayer(DemosaicMethod),
Luma,
Scale {
gain: f64,
offset: f64,
},
ScalePixels(ScaleFactor),
Convert,
Crop {
x: usize,
y: usize,
w: usize,
h: usize,
},
Roi {
x: usize,
y: usize,
w: usize,
h: usize,
},
Flip {
horizontal: bool,
vertical: bool,
},
Rot90 {
ccw: bool,
},
Resize {
w: usize,
h: usize,
filter: ResizeFilter,
},
Nop,
}
impl StepKind {
pub(super) fn is_geometric(&self) -> bool {
matches!(
self,
StepKind::Crop { .. }
| StepKind::Roi { .. }
| StepKind::Flip { .. }
| StepKind::Rot90 { .. }
| StepKind::Resize { .. }
)
}
}