use crate::cdf::MI_SIZE;
use crate::loop_filter::PlaneBuffer;
use crate::uncompressed_header_tail::{FrameRestorationType, LrParams};
pub const FILTER_BITS: u32 = 7;
pub const WIENER_COEFFS: usize = 3;
pub const SGRPROJ_PARAMS_BITS: u32 = 4;
pub const SGRPROJ_PRJ_SUBEXP_K: u32 = 4;
pub const SGRPROJ_PRJ_BITS: u32 = 7;
pub const SGRPROJ_RST_BITS: u32 = 4;
pub const SGRPROJ_MTABLE_BITS: u32 = 20;
pub const SGRPROJ_RECIP_BITS: u32 = 12;
pub const SGRPROJ_SGR_BITS: u32 = 8;
pub const WIENER_TAPS_MID: [i32; WIENER_COEFFS] = [3, -7, 15];
pub const WIENER_TAPS_MIN: [i32; WIENER_COEFFS] = [-5, -23, -17];
pub const WIENER_TAPS_MAX: [i32; WIENER_COEFFS] = [10, 8, 46];
pub const WIENER_TAPS_K: [u32; WIENER_COEFFS] = [1, 2, 3];
pub const SGRPROJ_XQD_MID: [i32; 2] = [-32, 31];
pub const SGRPROJ_XQD_MIN: [i32; 2] = [-96, -32];
pub const SGRPROJ_XQD_MAX: [i32; 2] = [31, 95];
pub const SGR_PARAMS: [[i32; 4]; 16] = [
[2, 12, 1, 4],
[2, 15, 1, 6],
[2, 18, 1, 8],
[2, 21, 1, 9],
[2, 24, 1, 10],
[2, 29, 1, 11],
[2, 36, 1, 12],
[2, 45, 1, 13],
[2, 56, 1, 14],
[2, 68, 1, 15],
[0, 0, 1, 5],
[0, 0, 1, 8],
[0, 0, 1, 11],
[0, 0, 1, 14],
[2, 30, 0, 0],
[2, 75, 0, 0],
];
pub struct LoopRestorationFrameContext<'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 frame_height: u32,
pub upscaled_width: u32,
pub lr_params: &'a LrParams,
pub lr_type: &'a dyn Fn(u8, u32, u32) -> FrameRestorationType,
pub lr_wiener: &'a dyn Fn(u8, u32, u32, u8, usize) -> i32,
pub lr_sgr_set: &'a dyn Fn(u8, u32, u32) -> u8,
pub lr_sgr_xqd: &'a dyn Fn(u8, u32, u32, usize) -> i32,
}
impl std::fmt::Debug for LoopRestorationFrameContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LoopRestorationFrameContext")
.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("frame_height", &self.frame_height)
.field("upscaled_width", &self.upscaled_width)
.field("lr_params", &self.lr_params)
.finish_non_exhaustive()
}
}
pub fn loop_restoration_frame(
ctx: &LoopRestorationFrameContext<'_>,
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
lr_planes: &mut [PlaneBuffer<'_>],
) {
let num_planes = ctx
.num_planes
.min(curr_planes.len() as u8)
.min(cdef_planes.len() as u8)
.min(lr_planes.len() as u8);
for plane in 0..num_planes {
copy_plane(cdef_planes, lr_planes, plane);
}
if !ctx.lr_params.uses_lr {
return;
}
for plane in 0..num_planes {
let r_type = ctx.lr_params.frame_restoration_type[plane as usize];
if r_type == FrameRestorationType::None {
continue;
}
for geom in stripe_unit_rects(ctx, plane) {
loop_restore_rect(ctx, curr_planes, cdef_planes, lr_planes, plane, &geom);
}
}
}
#[must_use]
pub fn stripe_unit_rects(ctx: &LoopRestorationFrameContext<'_>, plane: u8) -> Vec<LrBlockGeometry> {
let (sub_x, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let plane_w = round2(ctx.upscaled_width, sub_x);
let plane_h = round2(ctx.frame_height, sub_y);
let mut out = Vec::new();
if plane_w == 0 || plane_h == 0 {
return out;
}
let unit_size = ctx
.lr_params
.loop_restoration_size
.get(plane as usize)
.copied()
.unwrap_or(0)
.max(1);
let unit_rows = count_units_in_frame(unit_size, plane_h);
let unit_cols = count_units_in_frame(unit_size, plane_w);
let plane_end_x = plane_w as i32 - 1;
let plane_end_y = plane_h as i32 - 1;
let mut stripe = 0i32;
loop {
let stripe_start_y = (-8 + stripe * 64) >> sub_y;
let stripe_end_y = stripe_start_y + (64 >> sub_y) - 1;
let y0 = stripe_start_y.max(0);
if y0 > plane_end_y {
break;
}
let y1 = stripe_end_y.min(plane_end_y);
let unit_row = (((64 * stripe) as u32) >> sub_y) / unit_size;
let unit_row = unit_row.min(unit_rows.saturating_sub(1));
for unit_col in 0..unit_cols {
let x0 = unit_col * unit_size;
if x0 as i32 > plane_end_x {
break;
}
let x1 = if unit_col + 1 == unit_cols {
plane_end_x
} else {
(x0 + unit_size - 1).min(plane_end_x as u32) as i32
};
out.push(LrBlockGeometry {
unit_row,
unit_col,
x: x0,
y: y0 as u32,
w: (x1 - x0 as i32 + 1) as u32,
h: (y1 - y0 + 1) as u32,
stripe_start_y,
stripe_end_y,
plane_end_x,
plane_end_y,
});
}
stripe += 1;
}
out
}
pub fn loop_restore_rect(
ctx: &LoopRestorationFrameContext<'_>,
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
lr_planes: &mut [PlaneBuffer<'_>],
plane: u8,
geom: &LrBlockGeometry,
) {
if geom.w == 0 || geom.h == 0 {
return;
}
let r_type = (ctx.lr_type)(plane, geom.unit_row, geom.unit_col);
match r_type {
FrameRestorationType::Wiener => {
wiener_filter(ctx, curr_planes, cdef_planes, lr_planes, plane, geom);
}
FrameRestorationType::SgrProj => {
self_guided_filter(ctx, curr_planes, cdef_planes, lr_planes, plane, geom);
}
FrameRestorationType::None | FrameRestorationType::Switchable => {}
}
}
fn copy_plane(src_planes: &[PlaneBuffer<'_>], dst_planes: &mut [PlaneBuffer<'_>], plane: 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 rows = src.rows.min(dst.rows) as usize;
let cols = src.cols.min(dst.cols) as usize;
let src_cols = src.cols as usize;
let dst_cols = dst.cols as usize;
for y in 0..rows {
for x in 0..cols {
dst.samples[y * dst_cols + x] = src.samples[y * src_cols + x];
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct LrBlockGeometry {
pub unit_row: u32,
pub unit_col: u32,
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
pub stripe_start_y: i32,
pub stripe_end_y: i32,
pub plane_end_x: i32,
pub plane_end_y: i32,
}
pub fn derive_block_geometry(
ctx: &LoopRestorationFrameContext<'_>,
plane: u8,
row: u32,
col: u32,
) -> LrBlockGeometry {
let luma_y = (row as i32) * (MI_SIZE as i32);
let stripe_num = (luma_y + 8) / 64;
let (sub_x, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let stripe_start_y = (-8 + stripe_num * 64) >> sub_y;
let stripe_end_y = stripe_start_y + (64 >> sub_y) - 1;
let unit_size = ctx
.lr_params
.loop_restoration_size
.get(plane as usize)
.copied()
.unwrap_or(0)
.max(1);
let unit_rows = count_units_in_frame(unit_size, round2(ctx.frame_height, sub_y));
let unit_cols = count_units_in_frame(unit_size, round2(ctx.upscaled_width, sub_x));
let unit_row_raw = ((row * (MI_SIZE as u32) + 8) >> sub_y) / unit_size;
let unit_row = unit_row_raw.min(unit_rows.saturating_sub(1));
let unit_col_raw = ((col * (MI_SIZE as u32)) >> sub_x) / unit_size;
let unit_col = unit_col_raw.min(unit_cols.saturating_sub(1));
let plane_end_x = round2(ctx.upscaled_width, sub_x) as i32 - 1;
let plane_end_y = round2(ctx.frame_height, sub_y) as i32 - 1;
let x = (col * (MI_SIZE as u32)) >> sub_x;
let y = (row * (MI_SIZE as u32)) >> sub_y;
let w = ((MI_SIZE as u32) >> sub_x).min((plane_end_x + 1 - x as i32).max(0) as u32);
let h = ((MI_SIZE as u32) >> sub_y).min((plane_end_y + 1 - y as i32).max(0) as u32);
LrBlockGeometry {
unit_row,
unit_col,
x,
y,
w,
h,
stripe_start_y,
stripe_end_y,
plane_end_x,
plane_end_y,
}
}
pub fn loop_restore_block(
ctx: &LoopRestorationFrameContext<'_>,
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
lr_planes: &mut [PlaneBuffer<'_>],
plane: u8,
row: u32,
col: u32,
) {
let geom = derive_block_geometry(ctx, plane, row, col);
if geom.w == 0 || geom.h == 0 {
return;
}
let r_type = (ctx.lr_type)(plane, geom.unit_row, geom.unit_col);
match r_type {
FrameRestorationType::Wiener => {
wiener_filter(ctx, curr_planes, cdef_planes, lr_planes, plane, &geom);
}
FrameRestorationType::SgrProj => {
self_guided_filter(ctx, curr_planes, cdef_planes, lr_planes, plane, &geom);
}
FrameRestorationType::None | FrameRestorationType::Switchable => {
}
}
}
pub fn self_guided_filter(
ctx: &LoopRestorationFrameContext<'_>,
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
lr_planes: &mut [PlaneBuffer<'_>],
plane: u8,
geom: &LrBlockGeometry,
) {
let Some(lr) = lr_planes.get_mut(plane as usize) else {
return;
};
let src = PlaneSource {
curr: curr_planes,
cdef: cdef_planes,
plane,
};
self_guided_filter_src(ctx, &src, lr, 0, plane, geom);
}
pub(crate) fn self_guided_filter_src<S: LrSource>(
ctx: &LoopRestorationFrameContext<'_>,
src: &S,
lr: &mut PlaneBuffer<'_>,
out_row0: i32,
plane: u8,
geom: &LrBlockGeometry,
) {
if geom.w == 0 || geom.h == 0 {
return;
}
let set = (ctx.lr_sgr_set)(plane, geom.unit_row, geom.unit_col) as usize;
let set = set.min(SGR_PARAMS.len() - 1);
let r0 = SGR_PARAMS[set][0];
let eps0 = SGR_PARAMS[set][1];
let r1 = SGR_PARAMS[set][2];
let eps1 = SGR_PARAMS[set][3];
let w0 = (ctx.lr_sgr_xqd)(plane, geom.unit_row, geom.unit_col, 0);
let w1 = (ctx.lr_sgr_xqd)(plane, geom.unit_row, geom.unit_col, 1);
let flt0 = box_filter_src(src, geom, ctx.bit_depth, r0, eps0, 0);
let flt1 = box_filter_src(src, geom, ctx.bit_depth, r1, eps1, 1);
let h = geom.h as usize;
let w = geom.w as usize;
let lr_rows = lr.rows as i32;
let lr_cols = lr.cols as i32;
let lr_cols_usize = lr.cols as usize;
for i in 0..h {
for j in 0..w {
let py = geom.y as i32 + i as i32;
let px = geom.x as i32 + j as i32;
let u = src.cdef_at(px, py) << SGRPROJ_RST_BITS;
let out = sgr_project(
u,
flt0[i * w + j],
flt1[i * w + j],
w0,
w1,
r0,
r1,
ctx.bit_depth,
);
let wy = py - out_row0;
if wy < 0 || wy >= lr_rows || px < 0 || px >= lr_cols {
continue;
}
lr.samples[(wy as usize) * lr_cols_usize + px as usize] = out;
}
}
}
#[inline]
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn sgr_project(
u: i32,
flt0: i32,
flt1: i32,
w0: i32,
w1: i32,
r0: i32,
r1: i32,
bit_depth: u8,
) -> i32 {
let w2 = (1i32 << SGRPROJ_PRJ_BITS) - w0 - w1;
let max_sample = (1i32 << bit_depth) - 1;
let shift = SGRPROJ_RST_BITS + SGRPROJ_PRJ_BITS;
let mut v = w1 * u;
v += w0 * if r0 != 0 { flt0 } else { u };
v += w2 * if r1 != 0 { flt1 } else { u };
let s = round2_i32(v, shift);
clip3(0, max_sample, s)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn box_filter(
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
plane: u8,
geom: &LrBlockGeometry,
bit_depth: u8,
r: i32,
eps: i32,
pass: u8,
) -> Vec<i32> {
let src = PlaneSource {
curr: curr_planes,
cdef: cdef_planes,
plane,
};
box_filter_src(&src, geom, bit_depth, r, eps, pass)
}
pub(crate) fn box_filter_src<S: LrSource>(
src_view: &S,
geom: &LrBlockGeometry,
bit_depth: u8,
r: i32,
eps: i32,
pass: u8,
) -> Vec<i32> {
let h = geom.h as usize;
let w = geom.w as usize;
let mut f = vec![0i32; h * w];
if r == 0 {
return f;
}
let ru = r as usize;
let n = (2 * r + 1) * (2 * r + 1);
let n2e = (n as i64) * (n as i64) * (eps as i64);
let s_div: i64 = ((1i64 << SGRPROJ_MTABLE_BITS) + n2e / 2) / n2e.max(1);
let one_over_n = ((1i32 << SGRPROJ_RECIP_BITS) + n / 2) / n;
let aw = w + 2;
let ah = h + 2;
let mut a_arr = vec![0i32; ah * aw];
let mut b_arr = vec![0i32; ah * aw];
let bd_shift_a = 2 * (bit_depth as i32 - 8).max(0);
let bd_shift_b = (bit_depth as i32 - 8).max(0);
let sw = aw + 2 * ru;
let sh = ah + 2 * ru;
let mut src = vec![0i64; sh * sw];
let mut row_buf = vec![0i32; sw];
for (sy, row) in src.chunks_mut(sw).enumerate() {
let y = geom.y as i32 + sy as i32 - 1 - r;
src_view.source_row(geom.x as i32 - 1 - r, y, geom, &mut row_buf);
for (v, &c) in row.iter_mut().zip(row_buf.iter()) {
*v = i64::from(c);
}
}
let win = 2 * ru + 1;
let mut row_b = vec![0i64; sh * aw];
let mut row_a = vec![0i64; sh * aw];
for sy in 0..sh {
let srow = &src[sy * sw..(sy + 1) * sw];
let mut sb: i64 = 0;
let mut sa: i64 = 0;
for &c in &srow[..win] {
sb += c;
sa += c * c;
}
row_b[sy * aw] = sb;
row_a[sy * aw] = sa;
for j in 1..aw {
let out = srow[j - 1];
let inc = srow[j - 1 + win];
sb += inc - out;
sa += inc * inc - out * out;
row_b[sy * aw + j] = sb;
row_a[sy * aw + j] = sa;
}
}
let mut col_b = vec![0i64; aw];
let mut col_a = vec![0i64; aw];
for sy in 0..win {
for j in 0..aw {
col_b[j] += row_b[sy * aw + j];
col_a[j] += row_a[sy * aw + j];
}
}
for ai in 0..ah {
if ai > 0 {
let out_row = ai - 1;
let in_row = ai - 1 + win;
for j in 0..aw {
col_b[j] += row_b[in_row * aw + j] - row_b[out_row * aw + j];
col_a[j] += row_a[in_row * aw + j] - row_a[out_row * aw + j];
}
}
for aj in 0..aw {
let a_acc = col_a[aj];
let b_acc = col_b[aj];
let a_r = round2_i64(a_acc, bd_shift_a as u32) as i32;
let d_r = round2_i64(b_acc, bd_shift_b as u32) as i32;
let p = ((a_r as i64) * (n as i64) - (d_r as i64) * (d_r as i64)).max(0);
let z = round2_i64(p.saturating_mul(s_div), SGRPROJ_MTABLE_BITS) as i32;
let a2 = if z >= 255 {
256
} else if z == 0 {
1
} else {
((z << SGRPROJ_SGR_BITS) + (z / 2)) / (z + 1)
};
let b_eff = b_acc.clamp(i32::MIN as i64, i32::MAX as i64);
let b2 = i64::from((1i32 << SGRPROJ_SGR_BITS) - a2) * b_eff * i64::from(one_over_n);
a_arr[ai * aw + aj] = a2;
b_arr[ai * aw + aj] = round2_i64(b2, SGRPROJ_RECIP_BITS) as i32;
}
}
for i in 0..h {
let shift = if pass == 0 && (i & 1) == 1 {
4u32
} else {
5u32
};
let py = geom.y as i32 + i as i32;
let row_m = (i) * aw;
let row_c = (i + 1) * aw;
let row_p = (i + 2) * aw;
for j in 0..w {
let (a_sum, b_sum): (i64, i64) = if pass == 0 {
let mut a: i64 = 0;
let mut b: i64 = 0;
for (dy, base) in [(-1i32, row_m), (0, row_c), (1, row_p)] {
if ((i as i32 + dy) & 1) != 0 {
a += 6 * a_arr[base + j + 1] as i64
+ 5 * (a_arr[base + j] as i64 + a_arr[base + j + 2] as i64);
b += 6 * b_arr[base + j + 1] as i64
+ 5 * (b_arr[base + j] as i64 + b_arr[base + j + 2] as i64);
}
}
(a, b)
} else {
let cross = |arr: &[i32]| -> i64 {
4 * (arr[row_c + j + 1] as i64
+ arr[row_c + j] as i64
+ arr[row_c + j + 2] as i64
+ arr[row_m + j + 1] as i64
+ arr[row_p + j + 1] as i64)
+ 3 * (arr[row_m + j] as i64
+ arr[row_m + j + 2] as i64
+ arr[row_p + j] as i64
+ arr[row_p + j + 2] as i64)
};
(cross(&a_arr), cross(&b_arr))
};
let px = geom.x as i32 + j as i32;
let u = i64::from(src_view.cdef_at(px, py));
let v = a_sum * u + b_sum;
let total_shift = SGRPROJ_SGR_BITS + shift - SGRPROJ_RST_BITS;
f[i * w + j] = round2_i64(v, total_shift) as i32;
}
}
f
}
pub fn wiener_filter(
ctx: &LoopRestorationFrameContext<'_>,
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
lr_planes: &mut [PlaneBuffer<'_>],
plane: u8,
geom: &LrBlockGeometry,
) {
let Some(_curr) = curr_planes.get(plane as usize) else {
return;
};
let Some(_cdef) = cdef_planes.get(plane as usize) else {
return;
};
let Some(lr) = lr_planes.get_mut(plane as usize) else {
return;
};
let src = PlaneSource {
curr: curr_planes,
cdef: cdef_planes,
plane,
};
wiener_filter_src(ctx, &src, lr, 0, plane, geom);
}
pub(crate) fn wiener_filter_src<S: LrSource>(
ctx: &LoopRestorationFrameContext<'_>,
src: &S,
lr: &mut PlaneBuffer<'_>,
out_row0: i32,
plane: u8,
geom: &LrBlockGeometry,
) {
let vcoeff: [i32; WIENER_COEFFS] = [
(ctx.lr_wiener)(plane, geom.unit_row, geom.unit_col, 0, 0),
(ctx.lr_wiener)(plane, geom.unit_row, geom.unit_col, 0, 1),
(ctx.lr_wiener)(plane, geom.unit_row, geom.unit_col, 0, 2),
];
let hcoeff: [i32; WIENER_COEFFS] = [
(ctx.lr_wiener)(plane, geom.unit_row, geom.unit_col, 1, 0),
(ctx.lr_wiener)(plane, geom.unit_row, geom.unit_col, 1, 1),
(ctx.lr_wiener)(plane, geom.unit_row, geom.unit_col, 1, 2),
];
let vfilter = wiener_coefficients(&vcoeff);
let hfilter = wiener_coefficients(&hcoeff);
let inter_round0 = inter_round0(ctx.bit_depth);
let inter_round1 = inter_round1(ctx.bit_depth);
let bit_depth = ctx.bit_depth as i32;
let offset = 1i32 << (bit_depth + FILTER_BITS as i32 - inter_round0 as i32 - 1);
let limit = (1i32 << (bit_depth + 1 + FILTER_BITS as i32 - inter_round0 as i32)) - 1;
let h = geom.h as usize;
let w = geom.w as usize;
let sw = w + 6;
let mut window = vec![0i32; (h + 6) * sw];
for (r, row) in window.chunks_mut(sw).enumerate() {
let sy = geom.y as i32 + r as i32 - 3;
src.source_row(geom.x as i32 - 3, sy, geom, row);
}
let mut intermediate = vec![0i32; (h + 6) * w];
for r in 0..(h + 6) {
let wrow = &window[r * sw..(r + 1) * sw];
for c in 0..w {
let mut s = 0i32;
for (t, &hcoef) in hfilter.iter().enumerate() {
s += hcoef * wrow[c + t];
}
let v = round2_i32(s, inter_round0);
let clipped = clip3(-offset, limit - offset, v);
intermediate[r * w + c] = clipped;
}
}
let max_sample = (1i32 << bit_depth) - 1;
let lr_cols = lr.cols as i32;
let lr_rows = lr.rows as i32;
for r in 0..h {
for c in 0..w {
let mut s = 0i32;
for (t, &vcoef) in vfilter.iter().enumerate() {
s += vcoef * intermediate[(r + t) * w + c];
}
let v = round2_i32(s, inter_round1);
let out = clip3(0, max_sample, v);
let dst_y = geom.y as i32 + r as i32 - out_row0;
let dst_x = geom.x as i32 + c as i32;
if dst_y < 0 || dst_y >= lr_rows || dst_x < 0 || dst_x >= lr_cols {
continue;
}
lr.samples[(dst_y as usize) * (lr.cols as usize) + dst_x as usize] = out;
}
}
}
pub fn wiener_coefficients(coeff: &[i32; WIENER_COEFFS]) -> [i32; 7] {
let mut filter = [0i32; 7];
filter[3] = 128;
for i in 0..3usize {
let c = coeff[i];
filter[i] = c;
filter[6 - i] = c;
filter[3] -= 2 * c;
}
filter
}
pub(crate) trait LrSource {
fn source(&self, x: i32, y: i32, geom: &LrBlockGeometry) -> i32;
fn cdef_at(&self, x: i32, y: i32) -> i32;
fn source_row(&self, x0: i32, y: i32, geom: &LrBlockGeometry, out: &mut [i32]) {
for (k, o) in out.iter_mut().enumerate() {
*o = self.source(x0 + k as i32, y, geom);
}
}
}
pub(crate) struct PlaneSource<'a, 'b> {
pub curr: &'b [PlaneBuffer<'a>],
pub cdef: &'b [PlaneBuffer<'a>],
pub plane: u8,
}
impl LrSource for PlaneSource<'_, '_> {
#[inline]
fn source(&self, x: i32, y: i32, geom: &LrBlockGeometry) -> i32 {
get_source_sample(self.curr, self.cdef, self.plane, x, y, geom)
}
#[inline]
fn cdef_at(&self, x: i32, y: i32) -> i32 {
let Some(buf) = self.cdef.get(self.plane as usize) else {
return 0;
};
if y < 0 || x < 0 || y >= buf.rows as i32 || x >= buf.cols as i32 {
return 0;
}
buf.samples[(y as usize) * (buf.cols as usize) + x as usize]
}
}
pub(crate) struct StripeSource<'a> {
pub cdef: &'a [i32],
pub cdef_row0: i32,
pub cols: usize,
pub above: Option<&'a [i32]>,
pub below: Option<&'a [i32]>,
pub plane_end_x: i32,
pub plane_end_y: i32,
}
impl StripeSource<'_> {
#[inline]
fn row(&self, y: i32, geom: &LrBlockGeometry) -> &[i32] {
let y = y.clamp(0, self.plane_end_y.max(0));
if y < geom.stripe_start_y {
let k = (y.max(geom.stripe_start_y - 2) - (geom.stripe_start_y - 2)) as usize;
if let Some(a) = self.above {
return &a[k * self.cols..(k + 1) * self.cols];
}
} else if y > geom.stripe_end_y {
let k = (y.min(geom.stripe_end_y + 2) - (geom.stripe_end_y + 1)) as usize;
if let Some(b) = self.below {
return &b[k * self.cols..(k + 1) * self.cols];
}
}
let yy = (y - self.cdef_row0).max(0) as usize;
let start = (yy * self.cols).min(self.cdef.len().saturating_sub(self.cols));
&self.cdef[start..start + self.cols]
}
}
impl LrSource for StripeSource<'_> {
#[inline]
fn source(&self, x: i32, y: i32, geom: &LrBlockGeometry) -> i32 {
let x = x.clamp(0, geom.plane_end_x.max(0)) as usize;
self.row(y, geom)[x.min(self.cols - 1)]
}
#[inline]
fn cdef_at(&self, x: i32, y: i32) -> i32 {
if x < 0 || x > self.plane_end_x || y < 0 || y > self.plane_end_y {
return 0;
}
let yy = y - self.cdef_row0;
if yy < 0 {
return 0;
}
self.cdef
.get((yy as usize) * self.cols + x as usize)
.copied()
.unwrap_or(0)
}
fn source_row(&self, x0: i32, y: i32, geom: &LrBlockGeometry, out: &mut [i32]) {
let row = self.row(y, geom);
let end_x = geom.plane_end_x.max(0).min(self.cols as i32 - 1);
for (k, o) in out.iter_mut().enumerate() {
let x = (x0 + k as i32).clamp(0, end_x) as usize;
*o = row[x];
}
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct StripeBoundaryRows {
pub cols: usize,
pub rows: Vec<Vec<i32>>,
}
impl StripeBoundaryRows {
pub(crate) fn capture(plane: &PlaneBuffer<'_>, plane_end_y: i32, sub_y: u8) -> Self {
let cols = plane.cols as usize;
let mut rows = Vec::new();
let mut s = 1i32;
loop {
let b = (64 * s - 8) >> sub_y;
if b > plane_end_y {
break;
}
let mut four = Vec::with_capacity(4 * cols);
for k in -2..2 {
let y = (b + k).clamp(0, plane_end_y.max(0)) as usize;
let y = y.min(plane.rows.saturating_sub(1) as usize);
four.extend_from_slice(&plane.samples[y * cols..(y + 1) * cols]);
}
rows.push(four);
s += 1;
}
Self { cols, rows }
}
fn above(&self, stripe: usize) -> Option<&[i32]> {
if stripe == 0 {
return None;
}
self.rows.get(stripe - 1).map(|r| &r[..2 * self.cols])
}
fn below(&self, stripe: usize) -> Option<&[i32]> {
self.rows.get(stripe).map(|r| &r[2 * self.cols..])
}
}
pub(crate) fn loop_restoration_frame_in_place(
ctx: &LoopRestorationFrameContext<'_>,
planes: &mut [PlaneBuffer<'_>],
boundary: &[StripeBoundaryRows],
) {
if !ctx.lr_params.uses_lr {
return;
}
let num_planes = ctx.num_planes.min(planes.len() as u8);
let mut stripe_copy: Vec<i32> = Vec::new();
for plane in 0..num_planes {
let Some(bnd) = boundary.get(plane as usize) else {
continue;
};
let pb = &mut planes[plane as usize];
for stripe in plane_stripes(ctx, plane) {
restore_stripe_in_place(ctx, plane, &stripe, bnd, pb, 0, &mut stripe_copy);
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct PlaneStripe {
pub stripe: usize,
pub y0: usize,
pub y1: usize,
pub rects: Vec<LrBlockGeometry>,
}
pub(crate) fn plane_stripes(ctx: &LoopRestorationFrameContext<'_>, plane: u8) -> Vec<PlaneStripe> {
let mut out = Vec::new();
if ctx.lr_params.frame_restoration_type[plane as usize] == FrameRestorationType::None {
return out;
}
let rects = stripe_unit_rects(ctx, plane);
let mut i = 0usize;
while i < rects.len() {
let stripe_start = rects[i].stripe_start_y;
let mut j = i;
while j < rects.len() && rects[j].stripe_start_y == stripe_start {
j += 1;
}
let g = &rects[i];
out.push(PlaneStripe {
stripe: stripe_index(stripe_start, ctx, plane),
y0: g.y as usize,
y1: (g.y + g.h) as usize,
rects: rects[i..j].to_vec(),
});
i = j;
}
out
}
pub(crate) fn restore_stripe_in_place(
ctx: &LoopRestorationFrameContext<'_>,
plane: u8,
stripe: &PlaneStripe,
boundary: &StripeBoundaryRows,
out: &mut PlaneBuffer<'_>,
out_row0: usize,
stripe_copy: &mut Vec<i32>,
) {
let cols = out.cols as usize;
let (y0, y1) = (stripe.y0, stripe.y1);
let (w0, w1) = (y0 - out_row0, y1 - out_row0);
stripe_copy.clear();
stripe_copy.extend_from_slice(&out.samples[w0 * cols..w1 * cols]);
let Some(g) = stripe.rects.first() else {
return;
};
let src = StripeSource {
cdef: stripe_copy,
cdef_row0: y0 as i32,
cols,
above: boundary.above(stripe.stripe),
below: boundary.below(stripe.stripe),
plane_end_x: g.plane_end_x,
plane_end_y: g.plane_end_y,
};
for geom in &stripe.rects {
let r_type = (ctx.lr_type)(plane, geom.unit_row, geom.unit_col);
match r_type {
FrameRestorationType::Wiener => {
wiener_filter_src(ctx, &src, out, out_row0 as i32, plane, geom);
}
FrameRestorationType::SgrProj => {
self_guided_filter_src(ctx, &src, out, out_row0 as i32, plane, geom);
}
FrameRestorationType::None | FrameRestorationType::Switchable => {}
}
}
}
fn stripe_index(stripe_start_y: i32, ctx: &LoopRestorationFrameContext<'_>, plane: u8) -> usize {
let (_, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let mut s = 0i32;
loop {
let start = (-8 + s * 64) >> sub_y;
if start >= stripe_start_y || s > 1 << 20 {
return s.max(0) as usize;
}
s += 1;
}
}
pub fn get_source_sample(
curr_planes: &[PlaneBuffer<'_>],
cdef_planes: &[PlaneBuffer<'_>],
plane: u8,
x: i32,
y: i32,
geom: &LrBlockGeometry,
) -> i32 {
let x = x.clamp(0, geom.plane_end_x.max(0));
let mut y = y.clamp(0, geom.plane_end_y.max(0));
let p = plane as usize;
if y < geom.stripe_start_y {
y = y.max(geom.stripe_start_y - 2);
return sample_at(curr_planes, p, x, y);
} else if y > geom.stripe_end_y {
y = y.min(geom.stripe_end_y + 2);
return sample_at(curr_planes, p, x, y);
}
sample_at(cdef_planes, p, x, y)
}
fn sample_at(planes: &[PlaneBuffer<'_>], plane: usize, x: i32, y: i32) -> i32 {
let Some(buf) = planes.get(plane) else {
return 0;
};
if buf.rows == 0 || buf.cols == 0 {
return 0;
}
let yc = y.clamp(0, buf.rows as i32 - 1) as usize;
let xc = x.clamp(0, buf.cols as i32 - 1) as usize;
buf.samples[yc * (buf.cols as usize) + xc]
}
pub fn count_units_in_frame(unit_size: u32, frame_size: u32) -> u32 {
if unit_size == 0 {
return 1;
}
((frame_size + (unit_size >> 1)) / unit_size).max(1)
}
#[inline]
fn round2(x: u32, n: u8) -> u32 {
if n == 0 {
x
} else {
(x + (1u32 << (n - 1))) >> n
}
}
#[inline]
fn round2_i32(x: i32, n: u32) -> i32 {
if n == 0 {
x
} else {
(x + (1i32 << (n - 1))) >> n
}
}
#[inline]
fn round2_i64(x: i64, n: u32) -> i64 {
if n == 0 {
x
} else {
(x + (1i64 << (n - 1))) >> n
}
}
#[inline]
fn clip3(a: i32, b: i32, x: i32) -> i32 {
if x < a {
a
} else if x > b {
b
} else {
x
}
}
#[inline]
fn subsampling_for_plane(plane: u8, sub_x: u8, sub_y: u8) -> (u8, u8) {
if plane == 0 {
(0, 0)
} else {
(sub_x, sub_y)
}
}
#[inline]
fn inter_round0(bit_depth: u8) -> u32 {
if bit_depth == 12 {
5
} else {
3
}
}
#[inline]
fn inter_round1(bit_depth: u8) -> u32 {
if bit_depth == 12 {
9
} else {
11
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::uncompressed_header_tail::RESTORATION_TILESIZE_MAX;
fn make_lr_params(uses_lr: bool, sizes: [u32; 3]) -> LrParams {
LrParams {
frame_restoration_type: [FrameRestorationType::None; 3],
uses_lr,
uses_chroma_lr: false,
lr_unit_shift: 0,
lr_uv_shift: 0,
loop_restoration_size: sizes,
short_circuited: false,
}
}
fn make_plane(rows: u32, cols: u32, fill: i32) -> Vec<i32> {
vec![fill; (rows * cols) as usize]
}
#[test]
fn wiener_taps_tables_match_spec() {
assert_eq!(WIENER_TAPS_MID, [3, -7, 15]);
assert_eq!(WIENER_TAPS_MIN, [-5, -23, -17]);
assert_eq!(WIENER_TAPS_MAX, [10, 8, 46]);
assert_eq!(WIENER_TAPS_K, [1, 2, 3]);
assert_eq!(SGRPROJ_XQD_MID, [-32, 31]);
assert_eq!(SGRPROJ_XQD_MIN, [-96, -32]);
assert_eq!(SGRPROJ_XQD_MAX, [31, 95]);
}
#[test]
fn sgr_params_table_matches_spec() {
assert_eq!(SGR_PARAMS[0], [2, 12, 1, 4]);
assert_eq!(SGR_PARAMS[10], [0, 0, 1, 5]);
assert_eq!(SGR_PARAMS[14], [2, 30, 0, 0]);
assert_eq!(SGR_PARAMS.len(), 1 << SGRPROJ_PARAMS_BITS);
}
#[test]
fn wiener_coefficients_unit_dc_gain() {
for coeff in [
[0, 0, 0],
[3, -7, 15], [-5, -23, -17], [10, 8, 46], ] {
let f = wiener_coefficients(&coeff);
assert_eq!(f.iter().sum::<i32>(), 128);
assert_eq!(f[0], f[6]);
assert_eq!(f[1], f[5]);
assert_eq!(f[2], f[4]);
assert_eq!(f[0], coeff[0]);
assert_eq!(f[1], coeff[1]);
assert_eq!(f[2], coeff[2]);
}
}
#[test]
fn wiener_coefficients_zero_centre_is_128() {
let f = wiener_coefficients(&[0, 0, 0]);
assert_eq!(f, [0, 0, 0, 128, 0, 0, 0]);
}
#[test]
fn count_units_in_frame_matches_spec() {
assert_eq!(count_units_in_frame(64, 256), 4);
assert_eq!(count_units_in_frame(64, 100), 2); assert_eq!(count_units_in_frame(64, 0), 1); assert_eq!(count_units_in_frame(256, 240), 1); }
#[test]
fn loop_restoration_frame_no_op_when_uses_lr_false() {
let mut curr = make_plane(16, 16, 50);
let mut cdef = make_plane(16, 16, 200);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let params = make_lr_params(false, [64; 3]);
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::None,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, _| 0,
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
assert!(lr_buf.samples.iter().all(|&v| v == 200));
}
#[test]
fn loop_restoration_frame_restore_none_keeps_cdef_copy() {
let mut curr = make_plane(16, 16, 50);
let mut cdef = make_plane(16, 16, 175);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let mut params = make_lr_params(true, [64; 3]);
params.frame_restoration_type[0] = FrameRestorationType::None;
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::None,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, _| 0,
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
assert!(lr_buf.samples.iter().all(|&v| v == 175));
}
#[test]
fn wiener_filter_identity_filter_recovers_source() {
let mut curr = make_plane(64, 64, 100);
let mut cdef = make_plane(64, 64, 100);
let mut lr = make_plane(64, 64, 0);
let curr_buf = PlaneBuffer {
rows: 64,
cols: 64,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 64,
cols: 64,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 64,
cols: 64,
samples: &mut lr,
};
let mut params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
params.frame_restoration_type[0] = FrameRestorationType::Wiener;
let ctx = LoopRestorationFrameContext {
mi_rows: 16,
mi_cols: 16,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 64,
upscaled_width: 64,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::Wiener,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, _| 0,
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
for &v in lr_buf.samples.iter() {
assert_eq!(v, 100);
}
}
#[test]
fn get_source_sample_inside_stripe_reads_cdef() {
let mut curr = make_plane(16, 16, 11);
let mut cdef = make_plane(16, 16, 99);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let geom = LrBlockGeometry {
unit_row: 0,
unit_col: 0,
x: 0,
y: 4,
w: 4,
h: 4,
stripe_start_y: 0,
stripe_end_y: 8,
plane_end_x: 15,
plane_end_y: 15,
};
let v = get_source_sample(
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
0,
3,
5,
&geom,
);
assert_eq!(v, 99);
}
#[test]
fn get_source_sample_above_stripe_reads_curr_clamped() {
let mut curr = make_plane(16, 16, 33);
let mut cdef = make_plane(16, 16, 77);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let geom = LrBlockGeometry {
unit_row: 0,
unit_col: 0,
x: 0,
y: 8,
w: 4,
h: 4,
stripe_start_y: 6,
stripe_end_y: 14,
plane_end_x: 15,
plane_end_y: 15,
};
let v = get_source_sample(
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
0,
3,
2,
&geom,
);
assert_eq!(v, 33);
}
#[test]
fn get_source_sample_below_stripe_reads_curr() {
let mut curr = make_plane(32, 16, 42);
let mut cdef = make_plane(32, 16, 88);
let curr_buf = PlaneBuffer {
rows: 32,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 32,
cols: 16,
samples: &mut cdef,
};
let geom = LrBlockGeometry {
unit_row: 0,
unit_col: 0,
x: 0,
y: 0,
w: 4,
h: 4,
stripe_start_y: 0,
stripe_end_y: 8,
plane_end_x: 15,
plane_end_y: 31,
};
let v = get_source_sample(
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
0,
3,
12,
&geom,
);
assert_eq!(v, 42);
}
#[test]
fn derive_block_geometry_luma_top_left() {
let params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
let ctx = LoopRestorationFrameContext {
mi_rows: 16,
mi_cols: 16,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 64,
upscaled_width: 64,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::None,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, _| 0,
};
let geom = derive_block_geometry(&ctx, 0, 0, 0);
assert_eq!(geom.unit_row, 0);
assert_eq!(geom.unit_col, 0);
assert_eq!(geom.x, 0);
assert_eq!(geom.y, 0);
assert_eq!(geom.w, MI_SIZE as u32);
assert_eq!(geom.h, MI_SIZE as u32);
assert_eq!(geom.plane_end_x, 63);
assert_eq!(geom.plane_end_y, 63);
assert_eq!(geom.stripe_start_y, -8);
assert_eq!(geom.stripe_end_y, 55);
}
#[test]
fn self_guided_uniform_zero_weights_recovers_source() {
let mut curr = make_plane(16, 16, 50);
let mut cdef = make_plane(16, 16, 222);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let mut params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
params.frame_restoration_type[0] = FrameRestorationType::SgrProj;
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::SgrProj,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, _| 0,
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
for y in 4..12 {
for x in 4..12 {
let v = lr_buf.samples[y * 16 + x];
assert!((v - 222).abs() <= 1, "lr[{y},{x}] = {v} (expected ≈ 222)");
}
}
}
#[test]
fn self_guided_w2_zero_recovers_source_directly() {
let mut curr = make_plane(16, 16, 0);
let mut cdef = make_plane(16, 16, 137);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let mut params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
params.frame_restoration_type[0] = FrameRestorationType::SgrProj;
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::SgrProj,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, i| if i == 1 { 128 } else { 0 },
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
for &v in lr_buf.samples.iter() {
assert_eq!(v, 137);
}
}
#[test]
fn self_guided_set_10_r0_zero_uses_u_for_flt0() {
let mut curr = make_plane(16, 16, 0);
let mut cdef = make_plane(16, 16, 200);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let mut params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
params.frame_restoration_type[0] = FrameRestorationType::SgrProj;
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::SgrProj,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 10,
lr_sgr_xqd: &|_, _, _, i| if i == 0 { 128 } else { 0 },
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
for &v in lr_buf.samples.iter() {
assert_eq!(v, 200);
}
}
#[test]
fn self_guided_set_14_r1_zero_uses_u_for_flt1() {
let mut curr = make_plane(16, 16, 0);
let mut cdef = make_plane(16, 16, 88);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let mut params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
params.frame_restoration_type[0] = FrameRestorationType::SgrProj;
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::SgrProj,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 14,
lr_sgr_xqd: &|_, _, _, i| if i == 1 { 128 } else { 0 },
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
for &v in lr_buf.samples.iter() {
assert_eq!(v, 88);
}
}
#[test]
fn box_filter_r0_returns_zeroed_buffer() {
let mut curr = make_plane(8, 8, 0);
let mut cdef = make_plane(8, 8, 100);
let curr_buf = PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut cdef,
};
let geom = LrBlockGeometry {
unit_row: 0,
unit_col: 0,
x: 0,
y: 0,
w: 4,
h: 4,
stripe_start_y: -8,
stripe_end_y: 55,
plane_end_x: 7,
plane_end_y: 7,
};
let out = box_filter(
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
0,
&geom,
8,
0,
0,
0,
);
assert_eq!(out.len(), 16);
assert!(out.iter().all(|&v| v == 0));
}
#[test]
fn self_guided_clips_to_sample_range() {
let mut curr = make_plane(16, 16, 0);
let mut cdef = make_plane(16, 16, 128);
let mut lr = make_plane(16, 16, 0);
let curr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut curr,
};
let cdef_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut cdef,
};
let mut lr_buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut lr,
};
let mut params = make_lr_params(true, [RESTORATION_TILESIZE_MAX; 3]);
params.frame_restoration_type[0] = FrameRestorationType::SgrProj;
let ctx = LoopRestorationFrameContext {
mi_rows: 4,
mi_cols: 4,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
frame_height: 16,
upscaled_width: 16,
lr_params: ¶ms,
lr_type: &|_, _, _| FrameRestorationType::SgrProj,
lr_wiener: &|_, _, _, _, _| 0,
lr_sgr_set: &|_, _, _| 0,
lr_sgr_xqd: &|_, _, _, i| if i == 0 { -96 } else { 95 },
};
loop_restoration_frame(
&ctx,
std::slice::from_ref(&curr_buf),
std::slice::from_ref(&cdef_buf),
std::slice::from_mut(&mut lr_buf),
);
for &v in lr_buf.samples.iter() {
assert!((0..=255).contains(&v), "lr sample {v} escapes [0, 255]");
}
}
}