use crate::cdf::{MI_SIZE, MI_SIZE_LOG2};
use crate::loop_filter::PlaneBuffer;
use crate::uncompressed_header_tail::CdefParams;
pub const CDEF_PRI_TAPS: [[i32; 2]; 2] = [[4, 2], [3, 3]];
pub const CDEF_SEC_TAPS: [[i32; 2]; 2] = [[2, 1], [2, 1]];
pub const CDEF_DIRECTIONS: [[[i32; 2]; 2]; 8] = [
[[-1, 1], [-2, 2]],
[[0, 1], [-1, 2]],
[[0, 1], [0, 2]],
[[0, 1], [1, 2]],
[[1, 1], [2, 2]],
[[1, 0], [2, 1]],
[[1, 0], [2, 0]],
[[1, 0], [2, -1]],
];
pub const DIV_TABLE: [i32; 9] = [0, 840, 420, 280, 210, 168, 140, 120, 105];
pub const CDEF_UV_DIR: [[[u8; 8]; 2]; 2] = [
[[0, 1, 2, 3, 4, 5, 6, 7], [1, 2, 2, 2, 3, 4, 6, 0]],
[[7, 0, 2, 4, 5, 6, 6, 6], [0, 1, 2, 3, 4, 5, 6, 7]],
];
pub struct CdefFrameContext<'a> {
pub mi_rows: u32,
pub mi_cols: u32,
pub num_planes: u8,
pub bit_depth: u8,
pub subsampling_x: u8,
pub subsampling_y: u8,
pub cdef_params: &'a CdefParams,
pub cdef_idx: &'a dyn Fn(u32, u32) -> i8,
pub skip: &'a dyn Fn(u32, u32) -> bool,
}
impl std::fmt::Debug for CdefFrameContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CdefFrameContext")
.field("mi_rows", &self.mi_rows)
.field("mi_cols", &self.mi_cols)
.field("num_planes", &self.num_planes)
.field("bit_depth", &self.bit_depth)
.field("subsampling_x", &self.subsampling_x)
.field("subsampling_y", &self.subsampling_y)
.field("cdef_params", &self.cdef_params)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CdefSrc<'a> {
pub samples: &'a [i32],
pub cols: usize,
pub row0: usize,
pub plane_rows: usize,
pub plane_cols: usize,
}
impl<'a> CdefSrc<'a> {
fn of(p: &'a PlaneBuffer<'_>) -> Self {
Self {
samples: p.samples,
cols: p.cols as usize,
row0: 0,
plane_rows: p.rows as usize,
plane_cols: p.cols as usize,
}
}
#[inline(always)]
fn at(&self, y: usize, x: usize) -> i32 {
self.samples[(y - self.row0) * self.cols + x]
}
}
const STEP_4: u32 = 2;
const CDEF_SIZE_4: u32 = 16;
const CDEF_MASK_4: u32 = !(CDEF_SIZE_4 - 1);
pub fn cdef_frame(
ctx: &CdefFrameContext<'_>,
src_planes: &[PlaneBuffer<'_>],
dst_planes: &mut [PlaneBuffer<'_>],
) {
let num_planes = ctx
.num_planes
.min(src_planes.len() as u8)
.min(dst_planes.len() as u8);
let mut r = 0u32;
while r < ctx.mi_rows {
let mut c = 0u32;
while c < ctx.mi_cols {
let base_r = r & CDEF_MASK_4;
let base_c = c & CDEF_MASK_4;
let idx = (ctx.cdef_idx)(base_r, base_c);
cdef_block(ctx, src_planes, dst_planes, num_planes, r, c, idx);
c += STEP_4;
}
r += STEP_4;
}
}
pub fn cdef_frame_in_place(ctx: &CdefFrameContext<'_>, planes: &mut [PlaneBuffer<'_>]) {
let num_planes = ctx.num_planes.min(planes.len() as u8) as usize;
if num_planes == 0 {
return;
}
const BAND_MI: u32 = 16;
const HALO: usize = 2;
let mut windows: Vec<Vec<i32>> = vec![Vec::new(); num_planes];
let mut win_rows: Vec<(usize, usize)> = vec![(0, 0); num_planes];
let mut band_r = 0u32;
while band_r < ctx.mi_rows {
let band_r1 = (band_r + BAND_MI).min(ctx.mi_rows);
for (plane, (window, held)) in windows.iter_mut().zip(win_rows.iter_mut()).enumerate() {
let sub_y = if plane == 0 {
0
} else {
ctx.subsampling_y as u32
};
let pb = &planes[plane];
let (rows, cols) = (pb.rows as usize, pb.cols as usize);
let by0 = (((band_r * MI_SIZE as u32) >> sub_y) as usize).min(rows);
let by1 = (((band_r1 * MI_SIZE as u32) >> sub_y) as usize).min(rows);
let new0 = by0.saturating_sub(HALO);
let new1 = (by1 + HALO).min(rows);
let (old0, old1) = *held;
if window.is_empty() || old1 <= new0 || new0 < old0 {
window.clear();
window.extend_from_slice(&pb.samples[new0 * cols..new1 * cols]);
} else {
let keep = (new0 - old0) * cols;
window.copy_within(keep.., 0);
window.truncate((old1 - new0) * cols);
window.extend_from_slice(&pb.samples[old1 * cols..new1 * cols]);
}
*held = (new0, new1);
}
let mut srcs = [None, None, None];
for ((slot, (window, &(row0, _))), pb) in srcs
.iter_mut()
.zip(windows.iter().zip(win_rows.iter()))
.zip(planes.iter())
{
*slot = Some(CdefSrc {
samples: window,
cols: pb.cols as usize,
row0,
plane_rows: pb.rows as usize,
plane_cols: pb.cols as usize,
});
}
cdef_band_in_place(ctx, &srcs, planes, [0; 3], band_r, band_r1);
band_r = band_r1;
}
}
pub(crate) fn cdef_band_in_place(
ctx: &CdefFrameContext<'_>,
srcs: &[Option<CdefSrc<'_>>; 3],
dst_planes: &mut [PlaneBuffer<'_>],
dst_row0: [usize; 3],
r0: u32,
r1: u32,
) {
let num_planes = ctx.num_planes.min(dst_planes.len() as u8);
let mut r = r0;
while r < r1.min(ctx.mi_rows) {
let mut c = 0u32;
while c < ctx.mi_cols {
let base_r = r & CDEF_MASK_4;
let base_c = c & CDEF_MASK_4;
let idx = (ctx.cdef_idx)(base_r, base_c);
if idx >= 0 {
let skip = (ctx.skip)(r, c)
&& (r + 1 >= ctx.mi_rows || (ctx.skip)(r + 1, c))
&& (c + 1 >= ctx.mi_cols || (ctx.skip)(r, c + 1))
&& (r + 1 >= ctx.mi_rows || c + 1 >= ctx.mi_cols || (ctx.skip)(r + 1, c + 1));
if !skip {
let dir = match srcs[0].as_ref() {
Some(src0) => cdef_direction_src(ctx, src0, r, c),
None => (0, 0),
};
cdef_block_dir_src(ctx, srcs, dst_planes, dst_row0, num_planes, r, c, idx, dir);
}
}
c += STEP_4;
}
r += STEP_4;
}
}
pub fn cdef_block(
ctx: &CdefFrameContext<'_>,
src_planes: &[PlaneBuffer<'_>],
dst_planes: &mut [PlaneBuffer<'_>],
num_planes: u8,
r: u32,
c: u32,
idx: i8,
) {
cdef_copy_plane(src_planes, dst_planes, 0, r, c, 0, 0);
if num_planes > 1 {
cdef_copy_plane(
src_planes,
dst_planes,
1,
r,
c,
ctx.subsampling_x,
ctx.subsampling_y,
);
cdef_copy_plane(
src_planes,
dst_planes,
2,
r,
c,
ctx.subsampling_x,
ctx.subsampling_y,
);
}
if idx < 0 {
return;
}
let skip = (ctx.skip)(r, c)
&& (r + 1 >= ctx.mi_rows || (ctx.skip)(r + 1, c))
&& (c + 1 >= ctx.mi_cols || (ctx.skip)(r, c + 1))
&& (r + 1 >= ctx.mi_rows || c + 1 >= ctx.mi_cols || (ctx.skip)(r + 1, c + 1));
if skip {
return;
}
let dir = cdef_direction(ctx, src_planes, r, c);
cdef_block_dir(ctx, src_planes, dst_planes, num_planes, r, c, idx, dir);
}
#[allow(clippy::too_many_arguments)]
pub fn cdef_block_dir(
ctx: &CdefFrameContext<'_>,
src_planes: &[PlaneBuffer<'_>],
dst_planes: &mut [PlaneBuffer<'_>],
num_planes: u8,
r: u32,
c: u32,
idx: i8,
dir: (i32, i32),
) {
let mut srcs = [None, None, None];
for (slot, p) in srcs.iter_mut().zip(src_planes.iter()) {
*slot = Some(CdefSrc::of(p));
}
cdef_block_dir_src(ctx, &srcs, dst_planes, [0; 3], num_planes, r, c, idx, dir);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn cdef_block_dir_src(
ctx: &CdefFrameContext<'_>,
srcs: &[Option<CdefSrc<'_>>; 3],
dst_planes: &mut [PlaneBuffer<'_>],
dst_row0: [usize; 3],
num_planes: u8,
r: u32,
c: u32,
idx: i8,
dir: (i32, i32),
) {
if idx < 0 {
return;
}
let coeff_shift = ctx.bit_depth as i32 - 8;
let (y_dir, var) = dir;
let idx = idx as usize;
let pri_str_y = (ctx.cdef_params.cdef_y_pri_strength[idx] as i32) << coeff_shift;
let sec_str_y = (ctx.cdef_params.cdef_y_sec_strength[idx] as i32) << coeff_shift;
let dir_y = if pri_str_y == 0 { 0 } else { y_dir };
let var_str = if (var >> 6) > 0 {
(floor_log2((var >> 6) as u32) as i32).min(12)
} else {
0
};
let pri_str_y_scaled = if var != 0 {
(pri_str_y * (4 + var_str) + 8) >> 4
} else {
0
};
let damping_y = ctx.cdef_params.cdef_damping as i32 + coeff_shift;
if let (Some(src), Some(dst)) = (srcs[0].as_ref(), dst_planes.first_mut()) {
cdef_filter_block_src(
ctx,
src,
dst,
dst_row0[0],
0,
r,
c,
pri_str_y_scaled,
sec_str_y,
damping_y,
dir_y,
);
}
if num_planes == 1 {
return;
}
let pri_str_uv = (ctx.cdef_params.cdef_uv_pri_strength[idx] as i32) << coeff_shift;
let sec_str_uv = (ctx.cdef_params.cdef_uv_sec_strength[idx] as i32) << coeff_shift;
let dir_uv = if pri_str_uv == 0 {
0
} else {
CDEF_UV_DIR[ctx.subsampling_x.min(1) as usize][ctx.subsampling_y.min(1) as usize]
[y_dir as usize] as i32
};
let damping_uv = ctx.cdef_params.cdef_damping as i32 + coeff_shift - 1;
for (plane, src) in srcs.iter().enumerate().skip(1) {
if let (Some(src), Some(dst)) = (src.as_ref(), dst_planes.get_mut(plane)) {
cdef_filter_block_src(
ctx,
src,
dst,
dst_row0[plane],
plane as u8,
r,
c,
pri_str_uv,
sec_str_uv,
damping_uv,
dir_uv,
);
}
}
}
fn cdef_copy_plane(
src_planes: &[PlaneBuffer<'_>],
dst_planes: &mut [PlaneBuffer<'_>],
plane: u8,
r: u32,
c: u32,
sub_x: u8,
sub_y: u8,
) {
let p = plane as usize;
let Some(src) = src_planes.get(p) else { return };
let Some(dst) = dst_planes.get_mut(p) else {
return;
};
let start_y = ((r * MI_SIZE as u32) >> sub_y) as i32;
let end_y = (((r + STEP_4) * MI_SIZE as u32) >> sub_y) as i32;
let start_x = ((c * MI_SIZE as u32) >> sub_x) as i32;
let end_x = (((c + STEP_4) * MI_SIZE as u32) >> sub_x) as i32;
let cols = src.cols.min(dst.cols) as i32;
let rows = src.rows.min(dst.rows) as i32;
for y in start_y..end_y {
if y < 0 || y >= rows {
continue;
}
for x in start_x..end_x {
if x < 0 || x >= cols {
continue;
}
let v = src.samples[(y as usize) * (src.cols as usize) + (x as usize)];
dst.samples[(y as usize) * (dst.cols as usize) + (x as usize)] = v;
}
}
}
pub fn cdef_direction(
ctx: &CdefFrameContext<'_>,
src_planes: &[PlaneBuffer<'_>],
r: u32,
c: u32,
) -> (i32, i32) {
let Some(plane0) = src_planes.first() else {
return (0, 0);
};
cdef_direction_src(ctx, &CdefSrc::of(plane0), r, c)
}
pub(crate) fn cdef_direction_src(
ctx: &CdefFrameContext<'_>,
plane0: &CdefSrc<'_>,
r: u32,
c: u32,
) -> (i32, i32) {
let mut partial = [[0i32; 15]; 8];
let mut cost = [0i32; 8];
let x0 = (c as i32) << MI_SIZE_LOG2;
let y0 = (r as i32) << MI_SIZE_LOG2;
let shift = ctx.bit_depth as i32 - 8;
let inside = x0 >= 0
&& y0 >= 0
&& x0 + 8 <= plane0.plane_cols as i32
&& y0 + 8 <= plane0.plane_rows as i32;
for i in 0..8i32 {
for j in 0..8i32 {
let raw = if inside {
plane0.at((y0 + i) as usize, (x0 + j) as usize)
} else {
let sx = (x0 + j).clamp(0, plane0.plane_cols as i32 - 1) as usize;
let sy = (y0 + i).clamp(0, plane0.plane_rows as i32 - 1) as usize;
plane0.at(sy, sx)
};
let x = (raw >> shift) - 128;
partial[0][(i + j) as usize] += x;
partial[1][(i + j / 2) as usize] += x;
partial[2][i as usize] += x;
partial[3][(3 + i - j / 2) as usize] += x;
partial[4][(7 + i - j) as usize] += x;
partial[5][(3 - i / 2 + j) as usize] += x;
partial[6][j as usize] += x;
partial[7][(i / 2 + j) as usize] += x;
}
}
for &v in &partial[2][..8] {
cost[2] += v * v;
}
for &v in &partial[6][..8] {
cost[6] += v * v;
}
cost[2] *= DIV_TABLE[8];
cost[6] *= DIV_TABLE[8];
for i in 0..7usize {
cost[0] += (partial[0][i] * partial[0][i] + partial[0][14 - i] * partial[0][14 - i])
* DIV_TABLE[i + 1];
cost[4] += (partial[4][i] * partial[4][i] + partial[4][14 - i] * partial[4][14 - i])
* DIV_TABLE[i + 1];
}
cost[0] += partial[0][7] * partial[0][7] * DIV_TABLE[8];
cost[4] += partial[4][7] * partial[4][7] * DIV_TABLE[8];
let mut i = 1usize;
while i < 8 {
for j in 0..5usize {
cost[i] += partial[i][3 + j] * partial[i][3 + j];
}
cost[i] *= DIV_TABLE[8];
for j in 0..3usize {
cost[i] += (partial[i][j] * partial[i][j] + partial[i][10 - j] * partial[i][10 - j])
* DIV_TABLE[2 * j + 2];
}
i += 2;
}
let mut best_cost = 0i32;
let mut y_dir = 0i32;
for (k, &c_k) in cost.iter().enumerate() {
if c_k > best_cost {
best_cost = c_k;
y_dir = k as i32;
}
}
let var = (best_cost - cost[((y_dir + 4) & 7) as usize]) >> 10;
(y_dir, var)
}
#[allow(clippy::too_many_arguments)]
pub fn cdef_filter_block(
ctx: &CdefFrameContext<'_>,
src_planes: &[PlaneBuffer<'_>],
dst_planes: &mut [PlaneBuffer<'_>],
plane: u8,
r: u32,
c: u32,
pri_str: i32,
sec_str: i32,
damping: i32,
dir: i32,
) {
let p = plane as usize;
let Some(src) = src_planes.get(p) else { return };
let Some(dst) = dst_planes.get_mut(p) else {
return;
};
let src = CdefSrc::of(src);
cdef_filter_block_src(
ctx, &src, dst, 0, plane, r, c, pri_str, sec_str, damping, dir,
);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn cdef_filter_block_src(
ctx: &CdefFrameContext<'_>,
src: &CdefSrc<'_>,
dst: &mut PlaneBuffer<'_>,
dst_row0: usize,
plane: u8,
r: u32,
c: u32,
pri_str: i32,
sec_str: i32,
damping: i32,
dir: i32,
) {
let (sub_x, sub_y) = if plane == 0 {
(0u8, 0u8)
} else {
(ctx.subsampling_x, ctx.subsampling_y)
};
let coeff_shift = ctx.bit_depth as i32 - 8;
let x0 = ((c as i32) * MI_SIZE as i32) >> sub_x;
let y0 = ((r as i32) * MI_SIZE as i32) >> sub_y;
let w = 8i32 >> sub_x;
let h = 8i32 >> sub_y;
let plane_w = src.plane_cols as i32;
let plane_h = src.plane_rows as i32;
if pri_str == 0 && sec_str == 0 {
return;
}
let pri_row = ((pri_str >> coeff_shift) & 1) as usize;
let sec_row = pri_row;
let pri_taps = CDEF_PRI_TAPS[pri_row];
let sec_taps = CDEF_SEC_TAPS[sec_row];
let pri_adj = if pri_str == 0 {
0
} else {
(damping - floor_log2(pri_str as u32) as i32).max(0)
};
let sec_adj = if sec_str == 0 {
0
} else {
(damping - floor_log2(sec_str as u32) as i32).max(0)
};
let dir_p = (dir & 7) as usize;
let dir_s0 = ((dir - 2) & 7) as usize;
let dir_s1 = ((dir + 2) & 7) as usize;
let src_cols = src.cols;
let dst_cols = dst.cols as usize;
if y0 >= 2 && x0 >= 2 && y0 + h + 2 <= plane_h && x0 + w + 2 <= plane_w {
let mut taps = [CdefTap::default(); 12];
let mut n = 0;
for k in 0..2usize {
for sign in [-1i32, 1i32] {
let (dy, dx) = (CDEF_DIRECTIONS[dir_p][k][0], CDEF_DIRECTIONS[dir_p][k][1]);
taps[n] = CdefTap {
offset: (sign * dy) as isize * src_cols as isize + (sign * dx) as isize,
weight: pri_taps[k],
strength: pri_str,
adj: pri_adj.min(31),
};
n += 1;
for d2 in [dir_s0, dir_s1] {
let (dy, dx) = (CDEF_DIRECTIONS[d2][k][0], CDEF_DIRECTIONS[d2][k][1]);
taps[n] = CdefTap {
offset: (sign * dy) as isize * src_cols as isize + (sign * dx) as isize,
weight: sec_taps[k],
strength: sec_str,
adj: sec_adj.min(31),
};
n += 1;
}
}
}
let geom = (
y0 as usize - src.row0,
x0 as usize,
h as usize,
src_cols,
dst_cols,
);
let drow0 = (y0 as usize - dst_row0) * dst_cols;
let dst_rows = &mut dst.samples[drow0..];
if w == 8 {
cdef_filter_rows::<8>(src.samples, dst_rows, geom, &taps);
} else {
cdef_filter_rows::<4>(src.samples, dst_rows, geom, &taps);
}
return;
}
for i in 0..h {
let py = y0 + i;
if py < 0 || py >= plane_h {
continue;
}
for j in 0..w {
let px = x0 + j;
if px < 0 || px >= plane_w {
continue;
}
let x = src.at(py as usize, px as usize);
let mut sum = 0i32;
let mut max_v = x;
let mut min_v = x;
for k in 0..2usize {
for sign in [-1i32, 1i32] {
let (dy, dx) = (CDEF_DIRECTIONS[dir_p][k][0], CDEF_DIRECTIONS[dir_p][k][1]);
let (ty, tx) = (py + sign * dy, px + sign * dx);
if ty >= 0 && ty < plane_h && tx >= 0 && tx < plane_w {
let p_val = src.at(ty as usize, tx as usize);
sum += pri_taps[k] * constrain_adj(p_val - x, pri_str, pri_adj);
max_v = max_v.max(p_val);
min_v = min_v.min(p_val);
}
for d2 in [dir_s0, dir_s1] {
let (dy, dx) = (CDEF_DIRECTIONS[d2][k][0], CDEF_DIRECTIONS[d2][k][1]);
let (ty, tx) = (py + sign * dy, px + sign * dx);
if ty >= 0 && ty < plane_h && tx >= 0 && tx < plane_w {
let s_val = src.at(ty as usize, tx as usize);
sum += sec_taps[k] * constrain_adj(s_val - x, sec_str, sec_adj);
max_v = max_v.max(s_val);
min_v = min_v.min(s_val);
}
}
}
}
let delta = (8 + sum - i32::from(sum < 0)) >> 4;
let out = clip3(min_v, max_v, x + delta);
dst.samples[(py as usize - dst_row0) * dst_cols + (px as usize)] = out;
}
}
}
#[derive(Clone, Copy, Default)]
struct CdefTap {
offset: isize,
weight: i32,
strength: i32,
adj: i32,
}
#[inline(always)]
fn cdef_filter_rows<const W: usize>(
src: &[i32],
dst: &mut [i32],
(y0, x0, h, src_cols, dst_cols): (usize, usize, usize, usize, usize),
taps: &[CdefTap; 12],
) {
for i in 0..h {
let row0 = (y0 + i) * src_cols + x0;
let centre: [i32; W] = src[row0..row0 + W].try_into().expect("W-wide run");
let drow = i * dst_cols + x0;
let mut sum = [0i32; W];
let mut max_v = centre;
let mut min_v = centre;
for tap in taps {
let start = (row0 as isize + tap.offset) as usize;
let tap_row: [i32; W] = src[start..start + W].try_into().expect("W-wide run");
let (weight, strength, adj) = (tap.weight, tap.strength, tap.adj);
for j in 0..W {
let p_val = tap_row[j];
let diff = p_val - centre[j];
let abs_diff = diff.abs();
let v = abs_diff.min((strength - (abs_diff >> adj)).max(0));
let sgn = diff >> 31;
sum[j] += weight * ((v ^ sgn) - sgn);
max_v[j] = max_v[j].max(p_val);
min_v[j] = min_v[j].min(p_val);
}
}
let out_row = &mut dst[drow..drow + W];
for j in 0..W {
let s = sum[j];
let delta = (8 + s - i32::from(s < 0)) >> 4;
out_row[j] = clip3(min_v[j], max_v[j], centre[j] + delta);
}
}
}
#[inline]
fn constrain_adj(diff: i32, threshold: i32, damping_adj: i32) -> i32 {
if threshold == 0 {
return 0;
}
let abs_diff = diff.unsigned_abs() as i32;
let shifted = if damping_adj >= 31 {
0
} else {
abs_diff >> damping_adj
};
let inner = (threshold - shifted).max(0);
let v = abs_diff.min(inner);
if diff < 0 {
-v
} else {
v
}
}
#[must_use]
pub fn constrain(diff: i32, threshold: i32, damping: i32) -> i32 {
if threshold == 0 {
return 0;
}
let damping_adj = (damping - floor_log2(threshold as u32) as i32).max(0);
let sign = if diff < 0 { -1i32 } else { 1i32 };
let abs_diff = diff.unsigned_abs() as i32;
let shifted = if damping_adj >= 31 {
0
} else {
abs_diff >> damping_adj
};
let inner = (threshold - shifted).max(0);
sign * clip3(0, abs_diff, inner)
}
#[inline]
fn floor_log2(x: u32) -> u32 {
if x == 0 {
0
} else {
31 - x.leading_zeros()
}
}
#[inline]
fn clip3(a: i32, b: i32, x: i32) -> i32 {
if x < a {
a
} else if x > b {
b
} else {
x
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::uncompressed_header_tail::CDEF_MAX_STRENGTHS;
fn make_params(damping: u8) -> CdefParams {
CdefParams {
cdef_damping: damping,
cdef_bits: 0,
cdef_y_pri_strength: [0; CDEF_MAX_STRENGTHS],
cdef_y_sec_strength: [0; CDEF_MAX_STRENGTHS],
cdef_uv_pri_strength: [0; CDEF_MAX_STRENGTHS],
cdef_uv_sec_strength: [0; CDEF_MAX_STRENGTHS],
short_circuited: false,
}
}
#[test]
fn constrain_returns_zero_for_zero_threshold() {
assert_eq!(constrain(5, 0, 3), 0);
assert_eq!(constrain(-7, 0, 3), 0);
}
#[test]
fn constrain_signs_match_diff_sign() {
assert_eq!(constrain(5, 16, 8), 5);
assert_eq!(constrain(-5, 16, 8), -5);
}
#[test]
fn constrain_large_diff_clamped_by_threshold_minus_shifted() {
assert_eq!(constrain(20, 8, 4), 0);
}
#[test]
fn cdef_directions_table_matches_spec() {
assert_eq!(CDEF_DIRECTIONS[0], [[-1, 1], [-2, 2]]);
assert_eq!(CDEF_DIRECTIONS[2], [[0, 1], [0, 2]]);
assert_eq!(CDEF_DIRECTIONS[7], [[1, 0], [2, -1]]);
}
#[test]
fn div_table_matches_spec() {
assert_eq!(DIV_TABLE, [0, 840, 420, 280, 210, 168, 140, 120, 105]);
}
#[test]
fn cdef_uv_dir_lookup_identity_for_444() {
for (idx, &mapped) in CDEF_UV_DIR[0][0].iter().enumerate() {
assert_eq!(mapped, idx as u8);
}
}
#[test]
fn direction_search_uniform_block_returns_zero_var() {
let samples = vec![100i32; 16 * 16];
let mut s = samples.clone();
let plane = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut s,
};
let params = make_params(3);
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| -1,
skip: &|_, _| true,
};
let (y_dir, var) = cdef_direction(&ctx, std::slice::from_ref(&plane), 0, 0);
assert_eq!(y_dir, 0);
assert_eq!(var, 0);
}
#[test]
fn direction_search_horizontal_stripes_pick_direction_2() {
let mut samples = vec![0i32; 16 * 16];
for y in 0..16 {
for x in 0..16 {
samples[y * 16 + x] = 128 + (y as i32) * 10;
}
}
let mut s = samples.clone();
let plane = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut s,
};
let params = make_params(3);
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| -1,
skip: &|_, _| true,
};
let (y_dir, _) = cdef_direction(&ctx, std::slice::from_ref(&plane), 0, 0);
assert_eq!(
y_dir, 2,
"horizontal stripes ⇒ edge runs horizontally ⇒ direction 2"
);
}
#[test]
fn direction_search_vertical_stripes_pick_direction_6() {
let mut samples = vec![0i32; 16 * 16];
for y in 0..16 {
for x in 0..16 {
samples[y * 16 + x] = 128 + (x as i32) * 10;
}
}
let mut s = samples.clone();
let plane = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut s,
};
let params = make_params(3);
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| -1,
skip: &|_, _| true,
};
let (y_dir, _) = cdef_direction(&ctx, std::slice::from_ref(&plane), 0, 0);
assert_eq!(
y_dir, 6,
"vertical stripes ⇒ edge runs vertically ⇒ direction 6"
);
}
#[test]
fn cdef_block_with_idx_neg1_only_copies() {
let src_samples: Vec<i32> = (0i32..(16 * 16)).map(|v| v % 200).collect();
let mut s = src_samples.clone();
let src = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut s,
};
let mut d = vec![0i32; 16 * 16];
let mut dst = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut d,
};
let params = make_params(3);
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| -1,
skip: &|_, _| false,
};
cdef_block(
&ctx,
std::slice::from_ref(&src),
std::slice::from_mut(&mut dst),
1,
0,
0,
-1,
);
for y in 0..8 {
for x in 0..8 {
let i = y * 16 + x;
assert_eq!(dst.samples[i], src_samples[i], "copy mismatch at ({y},{x})");
}
}
for y in 8..16 {
for x in 0..16 {
let i = y * 16 + x;
assert_eq!(dst.samples[i], 0, "out-of-block region must be untouched");
}
}
}
#[test]
fn cdef_block_with_skip_only_copies() {
let src_samples: Vec<i32> = (0i32..(16 * 16)).map(|v| v * 3 % 200).collect();
let mut s = src_samples.clone();
let src = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut s,
};
let mut d = vec![0i32; 16 * 16];
let mut dst = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut d,
};
let mut params = make_params(3);
params.cdef_y_pri_strength[0] = 15;
params.cdef_y_sec_strength[0] = 4;
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| 0,
skip: &|_, _| true, };
cdef_block(
&ctx,
std::slice::from_ref(&src),
std::slice::from_mut(&mut dst),
1,
0,
0,
0,
);
for y in 0..8 {
for x in 0..8 {
let i = y * 16 + x;
assert_eq!(
dst.samples[i], src_samples[i],
"skip path must leave samples == source"
);
}
}
}
#[test]
fn cdef_filter_uniform_block_is_idempotent() {
let samples = vec![100i32; 16 * 16];
let mut s = samples.clone();
let src = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut s,
};
let mut d = samples.clone();
let mut dst = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut d,
};
let mut params = make_params(3);
params.cdef_y_pri_strength[0] = 8;
params.cdef_y_sec_strength[0] = 2;
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| 0,
skip: &|_, _| false,
};
cdef_filter_block(
&ctx,
std::slice::from_ref(&src),
std::slice::from_mut(&mut dst),
0,
0,
0,
8, 2, 6, 2, );
for y in 0..8 {
for x in 0..8 {
let i = y * 16 + x;
assert_eq!(dst.samples[i], 100, "uniform-input filter must be no-op");
}
}
}
#[test]
fn cdef_frame_driver_walks_every_8x8_anchor() {
let src_samples: Vec<i32> = (0i32..(32 * 32)).map(|v| (v * 7) % 200).collect();
let mut s = src_samples.clone();
let src = PlaneBuffer {
rows: 32,
cols: 32,
samples: &mut s,
};
let mut d = vec![0i32; 32 * 32];
let mut dst = PlaneBuffer {
rows: 32,
cols: 32,
samples: &mut d,
};
let params = make_params(3);
let ctx = CdefFrameContext {
mi_rows: 8,
mi_cols: 8,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|_, _| -1,
skip: &|_, _| true,
};
cdef_frame(
&ctx,
std::slice::from_ref(&src),
std::slice::from_mut(&mut dst),
);
assert_eq!(dst.samples, src_samples, "driver must copy every 8x8 cell");
}
#[test]
fn cdef_frame_chroma_copy_respects_subsampling() {
let luma_samples = vec![50i32; 16 * 16];
let chroma_u: Vec<i32> = (0i32..(8 * 8)).map(|v| v + 80).collect();
let chroma_v: Vec<i32> = (0i32..(8 * 8)).map(|v| v + 130).collect();
let mut y_s = luma_samples.clone();
let mut u_s = chroma_u.clone();
let mut v_s = chroma_v.clone();
let src = vec![
PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut y_s,
},
PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut u_s,
},
PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut v_s,
},
];
let mut yd = vec![0i32; 16 * 16];
let mut ud = vec![0i32; 8 * 8];
let mut vd = vec![0i32; 8 * 8];
let mut dst = vec![
PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut yd,
},
PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut ud,
},
PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut vd,
},
];
let params = make_params(3);
let ctx = CdefFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 3,
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
cdef_params: ¶ms,
cdef_idx: &|_, _| -1,
skip: &|_, _| true,
};
cdef_frame(&ctx, &src, &mut dst);
assert_eq!(dst[0].samples, luma_samples);
assert_eq!(dst[1].samples, chroma_u);
assert_eq!(dst[2].samples, chroma_v);
}
}