#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "reproduces the C uint8_t clip semantics of the reference decoder: \
clip8 stores an already-clamped [0,255] value into a u8 exactly as \
VP8kclip1 does"
)]
use crate::lossy::work::work;
#[derive(Clone, Copy, Default)]
pub(crate) struct FInfo {
pub(crate) f_limit: i32,
pub(crate) f_ilevel: i32,
pub(crate) f_inner: bool,
pub(crate) hev_thresh: i32,
}
#[derive(Clone, Copy)]
struct Limits {
thresh: i32,
ithresh: i32,
hev_thresh: i32,
}
fn clip8(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
fn sclip1(v: i32) -> i32 {
v.clamp(-128, 127)
}
fn sclip2(v: i32) -> i32 {
v.clamp(-16, 15)
}
fn filter2(p1: i32, p0: i32, q0: i32, q1: i32) -> (u8, u8) {
let a = 3 * (q0 - p0) + sclip1(p1 - q1);
let a1 = sclip2((a + 4) >> 3);
let a2 = sclip2((a + 3) >> 3);
(clip8(p0 + a2), clip8(q0 - a1))
}
fn filter4(p1: i32, p0: i32, q0: i32, q1: i32) -> (u8, u8, u8, u8) {
let a = 3 * (q0 - p0);
let a1 = sclip2((a + 4) >> 3);
let a2 = sclip2((a + 3) >> 3);
let a3 = (a1 + 1) >> 1;
(
clip8(p1 + a3),
clip8(p0 + a2),
clip8(q0 - a1),
clip8(q1 - a3),
)
}
fn filter6(p2: i32, p1: i32, p0: i32, q0: i32, q1: i32, q2: i32) -> (u8, u8, u8, u8, u8, u8) {
let a = sclip1(3 * (q0 - p0) + sclip1(p1 - q1));
let a1 = (27 * a + 63) >> 7; let a2 = (18 * a + 63) >> 7; let a3 = (9 * a + 63) >> 7; (
clip8(p2 + a3),
clip8(p1 + a2),
clip8(p0 + a1),
clip8(q0 - a1),
clip8(q1 - a2),
clip8(q2 - a3),
)
}
const fn hev(p1: i32, p0: i32, q0: i32, q1: i32, thresh: i32) -> bool {
(p1 - p0).abs() > thresh || (q1 - q0).abs() > thresh
}
const fn needs_filter(p1: i32, p0: i32, q0: i32, q1: i32, t: i32) -> bool {
4 * (p0 - q0).abs() + (p1 - q1).abs() <= t
}
#[allow(
clippy::too_many_arguments,
reason = "the eight straddling samples are loaded once by the caller and \
passed through as locals; taking a slice would reintroduce the \
bounds-checked re-reads this refactor removes"
)]
const fn needs_filter2(
p3: i32,
p2: i32,
p1: i32,
p0: i32,
q0: i32,
q1: i32,
q2: i32,
q3: i32,
t: i32,
it: i32,
) -> bool {
if 4 * (p0 - q0).abs() + (p1 - q1).abs() > t {
return false;
}
(p3 - p2).abs() <= it
&& (p2 - p1).abs() <= it
&& (p1 - p0).abs() <= it
&& (q3 - q2).abs() <= it
&& (q2 - q1).abs() <= it
&& (q1 - q0).abs() <= it
}
fn simple_v_filter16(plane: &mut [u8], off: usize, stride: usize, thresh: i32) {
let thresh2 = 2 * thresh + 1;
for i in 0..16 {
simple_filter_edge(plane, off + i, stride, thresh2);
}
}
fn simple_h_filter16(plane: &mut [u8], off: usize, stride: usize, thresh: i32) {
let thresh2 = 2 * thresh + 1;
for i in 0..16 {
simple_filter_edge(plane, off + i * stride, 1, thresh2);
}
}
fn simple_filter_edge(plane: &mut [u8], pos: usize, step: usize, thresh2: i32) {
let p1 = i32::from(plane[pos - 2 * step]);
let p0 = i32::from(plane[pos - step]);
let q0 = i32::from(plane[pos]);
let q1 = i32::from(plane[pos + step]);
if needs_filter(p1, p0, q0, q1, thresh2) {
work!(LoopFilterEdge);
let (np0, nq0) = filter2(p1, p0, q0, q1);
plane[pos - step] = np0;
plane[pos] = nq0;
}
}
fn simple_v_filter16i(plane: &mut [u8], off: usize, stride: usize, thresh: i32) {
let mut pos = off;
for _ in 0..3 {
pos += 4 * stride;
simple_v_filter16(plane, pos, stride, thresh);
}
}
fn simple_h_filter16i(plane: &mut [u8], off: usize, stride: usize, thresh: i32) {
let mut pos = off;
for _ in 0..3 {
pos += 4;
simple_h_filter16(plane, pos, stride, thresh);
}
}
fn filter_loop(
plane: &mut [u8],
off: usize,
hstride: usize,
vstride: usize,
size: usize,
lim: Limits,
use_filter6: bool,
) {
let thresh2 = 2 * lim.thresh + 1;
let step = hstride;
let mut pos = off;
for _ in 0..size {
let p3 = i32::from(plane[pos - 4 * step]);
let p2 = i32::from(plane[pos - 3 * step]);
let p1 = i32::from(plane[pos - 2 * step]);
let p0 = i32::from(plane[pos - step]);
let q0 = i32::from(plane[pos]);
let q1 = i32::from(plane[pos + step]);
let q2 = i32::from(plane[pos + 2 * step]);
let q3 = i32::from(plane[pos + 3 * step]);
if needs_filter2(p3, p2, p1, p0, q0, q1, q2, q3, thresh2, lim.ithresh) {
work!(LoopFilterEdge);
if hev(p1, p0, q0, q1, lim.hev_thresh) {
let (np0, nq0) = filter2(p1, p0, q0, q1);
plane[pos - step] = np0;
plane[pos] = nq0;
} else if use_filter6 {
let (np2, np1, np0, nq0, nq1, nq2) = filter6(p2, p1, p0, q0, q1, q2);
plane[pos - 3 * step] = np2;
plane[pos - 2 * step] = np1;
plane[pos - step] = np0;
plane[pos] = nq0;
plane[pos + step] = nq1;
plane[pos + 2 * step] = nq2;
} else {
let (np1, np0, nq0, nq1) = filter4(p1, p0, q0, q1);
plane[pos - 2 * step] = np1;
plane[pos - step] = np0;
plane[pos] = nq0;
plane[pos + step] = nq1;
}
}
pos += vstride;
}
}
fn v_filter16(plane: &mut [u8], off: usize, stride: usize, lim: Limits) {
filter_loop(plane, off, stride, 1, 16, lim, true);
}
fn h_filter16(plane: &mut [u8], off: usize, stride: usize, lim: Limits) {
filter_loop(plane, off, 1, stride, 16, lim, true);
}
fn v_filter16i(plane: &mut [u8], off: usize, stride: usize, lim: Limits) {
let mut pos = off;
for _ in 0..3 {
pos += 4 * stride;
filter_loop(plane, pos, stride, 1, 16, lim, false);
}
}
fn h_filter16i(plane: &mut [u8], off: usize, stride: usize, lim: Limits) {
let mut pos = off;
for _ in 0..3 {
pos += 4;
filter_loop(plane, pos, 1, stride, 16, lim, false);
}
}
fn v_filter8(u: &mut [u8], v: &mut [u8], off: usize, stride: usize, lim: Limits) {
filter_loop(u, off, stride, 1, 8, lim, true);
filter_loop(v, off, stride, 1, 8, lim, true);
}
fn h_filter8(u: &mut [u8], v: &mut [u8], off: usize, stride: usize, lim: Limits) {
filter_loop(u, off, 1, stride, 8, lim, true);
filter_loop(v, off, 1, stride, 8, lim, true);
}
fn v_filter8i(u: &mut [u8], v: &mut [u8], off: usize, stride: usize, lim: Limits) {
filter_loop(u, off + 4 * stride, stride, 1, 8, lim, false);
filter_loop(v, off + 4 * stride, stride, 1, 8, lim, false);
}
fn h_filter8i(u: &mut [u8], v: &mut [u8], off: usize, stride: usize, lim: Limits) {
filter_loop(u, off + 4, 1, stride, 8, lim, false);
filter_loop(v, off + 4, 1, stride, 8, lim, false);
}
pub(crate) fn filter_mb_simple(
y: &mut [u8],
off: usize,
stride: usize,
do_left_edge: bool,
do_top_edge: bool,
info: FInfo,
) {
if info.f_limit == 0 {
return;
}
let limit = info.f_limit;
if do_left_edge {
simple_h_filter16(y, off, stride, limit + 4);
}
if info.f_inner {
simple_h_filter16i(y, off, stride, limit);
}
if do_top_edge {
simple_v_filter16(y, off, stride, limit + 4);
}
if info.f_inner {
simple_v_filter16i(y, off, stride, limit);
}
}
#[allow(
clippy::too_many_arguments,
reason = "mirrors the reference VP8 DoFilter interface: three planes with \
independent offsets/strides plus the two edge flags and the \
filter-info the orchestration supplies per macroblock"
)]
pub(crate) fn filter_mb_normal(
y: &mut [u8],
y_off: usize,
y_stride: usize,
u: &mut [u8],
v: &mut [u8],
uv_off: usize,
uv_stride: usize,
do_left_edge: bool,
do_top_edge: bool,
info: FInfo,
) {
if info.f_limit == 0 {
return;
}
let outer = Limits {
thresh: info.f_limit + 4,
ithresh: info.f_ilevel,
hev_thresh: info.hev_thresh,
};
let inner = Limits {
thresh: info.f_limit,
ithresh: info.f_ilevel,
hev_thresh: info.hev_thresh,
};
if do_left_edge {
h_filter16(y, y_off, y_stride, outer);
h_filter8(u, v, uv_off, uv_stride, outer);
}
if info.f_inner {
h_filter16i(y, y_off, y_stride, inner);
h_filter8i(u, v, uv_off, uv_stride, inner);
}
if do_top_edge {
v_filter16(y, y_off, y_stride, outer);
v_filter8(u, v, uv_off, uv_stride, outer);
}
if info.f_inner {
v_filter16i(y, y_off, y_stride, inner);
v_filter8i(u, v, uv_off, uv_stride, inner);
}
}
#[cfg(test)]
#[allow(
clippy::match_same_arms,
reason = "the fixture builders match each plane row/col explicitly to document the \
filter's row layout; collapsing equal-valued rows would obscure that"
)]
mod tests {
use super::{
FInfo, filter_mb_normal, filter_mb_simple, filter2, filter4, filter6, hev, needs_filter,
needs_filter2,
};
const S: usize = 48;
const N: usize = S * S;
const R: usize = 16;
const C: usize = 16;
fn fill_by_row(plane: &mut [u8], f: impl Fn(usize) -> u8) {
for (r, row) in plane.chunks_exact_mut(S).enumerate() {
row.fill(f(r));
}
}
fn fill_by_col(plane: &mut [u8], f: impl Fn(usize) -> u8) {
for row in plane.chunks_exact_mut(S) {
for (c, px) in row.iter_mut().enumerate() {
*px = f(c);
}
}
}
#[test]
fn filter6_matches_hand_computed_6tap_kernel() {
assert_eq!(
filter6(100, 104, 108, 120, 124, 128),
(101, 106, 111, 117, 122, 127)
);
}
#[test]
fn filter6_saturates_via_sclip1_and_clip8() {
assert_eq!(
filter6(250, 100, 100, 200, 100, 8),
(255, 118, 127, 173, 82, 0)
);
}
#[test]
fn filter4_matches_hand_computed_4tap_kernel() {
assert_eq!(filter4(90, 100, 130, 145), (96, 111, 119, 139));
}
#[test]
fn filter2_matches_hand_computed_2tap_kernel_with_sclip2_saturation() {
assert_eq!(filter2(60, 70, 150, 160), (85, 135));
}
#[test]
fn hev_uses_strict_greater_than_on_both_pairs() {
assert!(!hev(100, 105, 200, 203, 5), "5 !> 5 and 3 !> 5 → false");
assert!(hev(100, 105, 200, 203, 4), "5 > 4 on the p-side → true");
assert!(!hev(120, 120, 100, 112, 12), "12 !> 12 → false");
assert!(hev(120, 120, 100, 112, 11), "12 > 11 on the q-side → true");
}
#[test]
fn needs_filter_uses_le_on_the_activation_sum() {
assert!(needs_filter(100, 110, 120, 118, 58), "58 <= 58 → true");
assert!(!needs_filter(100, 110, 120, 118, 57), "58 > 57 → false");
}
#[test]
fn needs_filter2_checks_main_sum_then_every_interior_step() {
assert!(
needs_filter2(90, 100, 108, 110, 120, 118, 130, 132, 50, 12),
"main 50<=50 and interior all <=12"
);
assert!(
!needs_filter2(90, 100, 108, 110, 120, 118, 130, 132, 49, 12),
"main 50 > 49 → reject"
);
assert!(
!needs_filter2(90, 100, 108, 110, 120, 118, 130, 132, 50, 11),
"interior 12 > 11 → reject"
);
}
#[test]
fn normal_top_edge_applies_do_filter6_to_luma_and_both_chroma() {
let mut y = [0u8; N];
let mut u = [0u8; N];
let mut v = [0u8; N];
fill_by_row(&mut y, |r| match r {
12 => 96,
13 => 100,
14 => 104,
15 => 108,
16 => 120,
17 => 124,
18 => 128,
19 => 132,
_ => 0,
});
fill_by_row(&mut u, |r| match r {
12 => 96,
13 => 100,
14 => 104,
15 => 108,
16 => 120,
17 => 124,
18 => 128,
19 => 132,
_ => 0,
});
fill_by_row(&mut v, |r| match r {
12 => 60,
13 => 66,
14 => 72,
15 => 78,
16 => 96,
17 => 102,
18 => 108,
19 => 114,
_ => 0,
});
let off = R * S + C;
let info = FInfo {
f_limit: 50,
f_ilevel: 9,
f_inner: false,
hev_thresh: 6,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, false, true, info);
for c in [C, C + 8] {
assert_eq!(y[12 * S + c], 96, "luma p3 untouched, col {c}");
assert_eq!(y[13 * S + c], 101, "luma p2, col {c}");
assert_eq!(y[14 * S + c], 106, "luma p1, col {c}");
assert_eq!(y[15 * S + c], 111, "luma p0, col {c}");
assert_eq!(y[16 * S + c], 117, "luma q0, col {c}");
assert_eq!(y[17 * S + c], 122, "luma q1, col {c}");
assert_eq!(y[18 * S + c], 127, "luma q2, col {c}");
assert_eq!(y[19 * S + c], 132, "luma q3 untouched, col {c}");
}
for c in [C, C + 4] {
assert_eq!(u[13 * S + c], 101, "U p2, col {c}");
assert_eq!(u[14 * S + c], 106, "U p1, col {c}");
assert_eq!(u[15 * S + c], 111, "U p0, col {c}");
assert_eq!(u[16 * S + c], 117, "U q0, col {c}");
assert_eq!(u[17 * S + c], 122, "U q1, col {c}");
assert_eq!(u[18 * S + c], 127, "U q2, col {c}");
assert_eq!(v[13 * S + c], 68, "V p2, col {c}");
assert_eq!(v[14 * S + c], 75, "V p1, col {c}");
assert_eq!(v[15 * S + c], 83, "V p0, col {c}");
assert_eq!(v[16 * S + c], 91, "V q0, col {c}");
assert_eq!(v[17 * S + c], 99, "V q1, col {c}");
assert_eq!(v[18 * S + c], 106, "V q2, col {c}");
}
}
#[test]
fn normal_left_edge_applies_do_filter6() {
let mut y = [0u8; N];
let mut u = [128u8; N];
let mut v = [128u8; N];
fill_by_col(&mut y, |c| match c {
12 => 96,
13 => 100,
14 => 104,
15 => 108,
16 => 120,
17 => 124,
18 => 128,
19 => 132,
_ => 0,
});
let off = R * S + C;
let info = FInfo {
f_limit: 50,
f_ilevel: 9,
f_inner: false,
hev_thresh: 6,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, true, false, info);
for r in [R, R + 8] {
assert_eq!(y[r * S + 12], 96, "p3 untouched, row {r}");
assert_eq!(y[r * S + 13], 101, "p2, row {r}");
assert_eq!(y[r * S + 14], 106, "p1, row {r}");
assert_eq!(y[r * S + 15], 111, "p0, row {r}");
assert_eq!(y[r * S + 16], 117, "q0, row {r}");
assert_eq!(y[r * S + 17], 122, "q1, row {r}");
assert_eq!(y[r * S + 18], 127, "q2, row {r}");
assert_eq!(y[r * S + 19], 132, "q3 untouched, row {r}");
}
assert!(u.iter().all(|&p| p == 128));
assert!(v.iter().all(|&p| p == 128));
}
#[test]
fn normal_interior_hev_edge_applies_do_filter2() {
let mut y = [0u8; N];
let mut u = [128u8; N];
let mut v = [128u8; N];
fill_by_col(&mut y, |c| match c {
0..=19 => 100,
20 => 120,
21 => 128,
_ => 130,
});
let off = R * S + C;
let info = FInfo {
f_limit: 60,
f_ilevel: 9,
f_inner: true,
hev_thresh: 1,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, false, false, info);
for r in [R, R + 8] {
assert_eq!(y[r * S + 18], 100, "p1 untouched, row {r}");
assert_eq!(y[r * S + 19], 104, "p0 → 104, row {r}");
assert_eq!(y[r * S + 20], 116, "q0 → 116, row {r}");
assert_eq!(y[r * S + 21], 128, "q1 untouched, row {r}");
assert_eq!(y[r * S + 23], 130, "col24 edge region flat, row {r}");
assert_eq!(y[r * S + 24], 130, "col24 edge no-op, row {r}");
}
assert!(u.iter().all(|&p| p == 128));
assert!(v.iter().all(|&p| p == 128));
}
#[test]
fn normal_interior_nonhev_edge_applies_do_filter4() {
let mut y = [0u8; N];
let mut u = [128u8; N];
let mut v = [128u8; N];
fill_by_col(&mut y, |c| if c < 20 { 100 } else { 112 });
let off = R * S + C;
let info = FInfo {
f_limit: 40,
f_ilevel: 9,
f_inner: true,
hev_thresh: 1,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, false, false, info);
for r in [R, R + 8] {
assert_eq!(y[r * S + 17], 100, "p2 untouched, row {r}");
assert_eq!(y[r * S + 18], 103, "p1 → 103, row {r}");
assert_eq!(y[r * S + 19], 104, "p0 → 104, row {r}");
assert_eq!(y[r * S + 20], 107, "q0 → 107, row {r}");
assert_eq!(y[r * S + 21], 109, "q1 → 109, row {r}");
assert_eq!(y[r * S + 22], 112, "q2 untouched, row {r}");
}
}
#[test]
fn simple_left_edge_smooths_a_step() {
let off_col = 16usize;
let off_row = 16usize;
let mut y = [0u8; N];
for (idx, px) in y.iter_mut().enumerate() {
*px = if idx % S < off_col { 128 } else { 134 };
}
let info = FInfo {
f_limit: 20,
f_ilevel: 9,
f_inner: false,
hev_thresh: 0,
};
filter_mb_simple(&mut y, off_row * S + off_col, S, true, false, info);
for r in off_row..off_row + 16 {
assert_eq!(y[r * S + (off_col - 2)], 128, "p1 row {r}");
assert_eq!(y[r * S + (off_col - 1)], 129, "p0 row {r}");
assert_eq!(y[r * S + off_col], 132, "q0 row {r}");
assert_eq!(y[r * S + (off_col + 1)], 134, "q1 row {r}");
}
}
#[test]
fn zero_f_limit_short_circuits_the_normal_filter() {
let mut base = [0u8; N];
fill_by_col(&mut base, |c| match c {
12 => 96,
13 => 100,
14 => 104,
15 => 108,
16 => 120,
17 => 124,
18 => 128,
19 => 132,
_ => 140,
});
let mut u = [128u8; N];
let mut v = [128u8; N];
let off = R * S + C;
let mut active = base;
let info_on = FInfo {
f_limit: 50,
f_ilevel: 9,
f_inner: false,
hev_thresh: 6,
};
filter_mb_normal(
&mut active,
off,
S,
&mut u,
&mut v,
off,
S,
true,
false,
info_on,
);
assert_ne!(active, base, "sanity: this plane is actually filterable");
let mut idle = base;
let info_off = FInfo {
f_limit: 0,
f_ilevel: 9,
f_inner: true,
hev_thresh: 6,
};
filter_mb_normal(
&mut idle, off, S, &mut u, &mut v, off, S, true, true, info_off,
);
assert_eq!(idle, base, "f_limit==0 must return before filtering");
}
#[test]
fn zero_f_limit_short_circuits_the_simple_filter() {
let mut base = [0u8; N];
fill_by_col(&mut base, |c| if c < C { 128 } else { 134 });
let off = R * S + C;
let mut active = base;
filter_mb_simple(
&mut active,
off,
S,
true,
false,
FInfo {
f_limit: 20,
f_ilevel: 9,
f_inner: false,
hev_thresh: 0,
},
);
assert_ne!(active, base, "sanity: this step is actually filterable");
let mut idle = base;
filter_mb_simple(
&mut idle,
off,
S,
true,
false,
FInfo {
f_limit: 0,
f_ilevel: 9,
f_inner: false,
hev_thresh: 0,
},
);
assert_eq!(idle, base, "f_limit==0 must return before filtering");
}
#[test]
fn simple_top_edge_needs_filter_threshold_is_exactly_2t_plus_1() {
let mut y = [0u8; N];
fill_by_row(&mut y, |r| match r {
14 => 103,
15 => 100,
16 => 102,
17 => 100,
_ => 0,
});
let info = FInfo {
f_limit: 1,
f_ilevel: 9,
f_inner: false,
hev_thresh: 0,
};
filter_mb_simple(&mut y, R * S + C, S, false, true, info);
for c in [C, C + 15] {
assert_eq!(y[14 * S + c], 103, "p1 untouched, col {c}");
assert_eq!(y[15 * S + c], 101, "p0 → 101, col {c}");
assert_eq!(y[16 * S + c], 101, "q0 → 101, col {c}");
assert_eq!(y[17 * S + c], 100, "q1 untouched, col {c}");
}
}
#[test]
fn simple_left_edge_needs_filter_threshold_is_exactly_2t_plus_1() {
let mut y = [0u8; N];
fill_by_col(&mut y, |c| match c {
14 => 103,
15 => 100,
16 => 102,
17 => 100,
_ => 0,
});
let info = FInfo {
f_limit: 1,
f_ilevel: 9,
f_inner: false,
hev_thresh: 0,
};
filter_mb_simple(&mut y, R * S + C, S, true, false, info);
for r in [R, R + 15] {
assert_eq!(y[r * S + 14], 103, "p1 untouched, row {r}");
assert_eq!(y[r * S + 15], 101, "p0 → 101, row {r}");
assert_eq!(y[r * S + 16], 101, "q0 → 101, row {r}");
assert_eq!(y[r * S + 17], 100, "q1 untouched, row {r}");
}
}
#[test]
fn simple_interior_horizontal_edges_filter_rows_4_8_12() {
let mut y = [0u8; N];
fill_by_row(&mut y, |r| match r {
20 | 21 | 24 | 25 | 28 | 29 => 120,
18 | 19 | 22 | 23 | 26 | 27 => 100,
_ => 0,
});
let info = FInfo {
f_limit: 60,
f_ilevel: 9,
f_inner: true,
hev_thresh: 0,
};
filter_mb_simple(&mut y, R * S + C, S, false, false, info);
for c in [C, C + 15] {
for (p0_row, q0_row) in [(19, 20), (23, 24), (27, 28)] {
assert_eq!(y[p0_row * S + c], 105, "p0 row {p0_row}, col {c}");
assert_eq!(y[q0_row * S + c], 115, "q0 row {q0_row}, col {c}");
}
assert_eq!(y[16 * S + c], 0, "top-edge row untouched, col {c}");
assert_eq!(y[18 * S + c], 100, "row 18 untouched, col {c}");
}
}
#[test]
fn simple_interior_vertical_edges_filter_cols_4_8_12() {
let mut y = [0u8; N];
fill_by_col(&mut y, |c| match c {
20 | 21 | 24 | 25 | 28 | 29 => 120,
18 | 19 | 22 | 23 | 26 | 27 => 100,
_ => 0,
});
let info = FInfo {
f_limit: 60,
f_ilevel: 9,
f_inner: true,
hev_thresh: 0,
};
filter_mb_simple(&mut y, R * S + C, S, false, false, info);
for r in [R, R + 15] {
for (p0_col, q0_col) in [(19, 20), (23, 24), (27, 28)] {
assert_eq!(y[r * S + p0_col], 105, "p0 col {p0_col}, row {r}");
assert_eq!(y[r * S + q0_col], 115, "q0 col {q0_col}, row {r}");
}
assert_eq!(y[r * S + 16], 0, "left-edge col untouched, row {r}");
assert_eq!(y[r * S + 18], 100, "col 18 untouched, row {r}");
}
}
#[test]
fn normal_interior_horizontal_nonhev_edge_applies_do_filter4() {
let mut y = [0u8; N];
let mut u = [128u8; N];
let mut v = [128u8; N];
fill_by_row(&mut y, |r| if r < 20 { 100 } else { 112 });
let off = R * S + C;
let info = FInfo {
f_limit: 40,
f_ilevel: 9,
f_inner: true,
hev_thresh: 1,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, false, false, info);
for c in [C, C + 15] {
assert_eq!(y[17 * S + c], 100, "p2 untouched, col {c}");
assert_eq!(y[18 * S + c], 103, "p1 → 103, col {c}");
assert_eq!(y[19 * S + c], 104, "p0 → 104, col {c}");
assert_eq!(y[20 * S + c], 107, "q0 → 107, col {c}");
assert_eq!(y[21 * S + c], 109, "q1 → 109, col {c}");
assert_eq!(y[22 * S + c], 112, "q2 untouched, col {c}");
}
assert!(u.iter().all(|&p| p == 128), "flat chroma untouched");
assert!(v.iter().all(|&p| p == 128), "flat chroma untouched");
}
#[test]
fn normal_interior_chroma_v_edge_filters_column_4_of_the_v_plane() {
let mut y = [100u8; N];
let mut u = [100u8; N];
let mut v = [0u8; N];
fill_by_col(&mut v, |c| if c < 20 { 100 } else { 112 });
let off = R * S + C;
let info = FInfo {
f_limit: 40,
f_ilevel: 9,
f_inner: true,
hev_thresh: 1,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, false, false, info);
for r in [R, R + 7] {
assert_eq!(v[r * S + 17], 100, "V p2 untouched, row {r}");
assert_eq!(v[r * S + 18], 103, "V p1 → 103, row {r}");
assert_eq!(v[r * S + 19], 104, "V p0 → 104, row {r}");
assert_eq!(v[r * S + 20], 107, "V q0 → 107, row {r}");
assert_eq!(v[r * S + 21], 109, "V q1 → 109, row {r}");
assert_eq!(v[r * S + 22], 112, "V q2 untouched, row {r}");
}
}
#[test]
fn normal_filter_loop_threshold_is_exactly_2t_plus_1() {
let mut y = [0u8; N];
let mut u = [128u8; N];
let mut v = [128u8; N];
fill_by_row(&mut y, |r| match r {
12..=15 => 100,
16 => 125,
17..=19 => 109,
_ => 0,
});
let off = R * S + C;
let info = FInfo {
f_limit: 50,
f_ilevel: 20,
f_inner: false,
hev_thresh: 6,
};
filter_mb_normal(&mut y, off, S, &mut u, &mut v, off, S, false, true, info);
for c in [C, C + 15] {
assert_eq!(y[14 * S + c], 100, "p1 untouched, col {c}");
assert_eq!(y[15 * S + c], 108, "p0 → 108, col {c}");
assert_eq!(y[16 * S + c], 117, "q0 → 117, col {c}");
assert_eq!(y[17 * S + c], 109, "q1 untouched, col {c}");
}
}
}