#![allow(dead_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::too_many_arguments)]
pub const MAX_LOOP_FILTER: u8 = 63;
pub const MAX_SHARPNESS: u8 = 7;
#[derive(Clone, Debug, Default)]
pub struct LoopFilterConfig {
pub level: u8,
pub sharpness: u8,
pub filter_type: u8,
pub ref_deltas: [i8; 4],
pub mode_deltas: [i8; 4],
pub delta_enabled: bool,
}
impl LoopFilterConfig {
#[must_use]
pub fn new(level: u8, sharpness: u8) -> Self {
Self {
level,
sharpness,
filter_type: 0,
ref_deltas: [1, 0, -1, -1], mode_deltas: [0, 0, 0, 0],
delta_enabled: true,
}
}
#[must_use]
pub const fn is_enabled(&self) -> bool {
self.level > 0
}
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub fn effective_level(&self, ref_frame: usize, mb_mode: usize) -> u8 {
if !self.delta_enabled {
return self.level;
}
let mut level = i32::from(self.level);
if ref_frame < 4 {
level += i32::from(self.ref_deltas[ref_frame]);
}
let mode_idx = if mb_mode == 0 { 0 } else { 1 };
if mode_idx < 4 {
level += i32::from(self.mode_deltas[mode_idx]);
}
level.clamp(0, i32::from(MAX_LOOP_FILTER)) as u8
}
}
#[allow(clippy::similar_names)]
pub fn filter_horizontal_edge(
pixels: &mut [u8],
stride: usize,
offset: usize,
hev_thresh: u8,
limit: u8,
thresh: u8,
) {
for i in 0..4 {
let idx = offset + i;
if idx < stride * 3 || idx + stride * 4 >= pixels.len() {
continue;
}
let p3 = i32::from(pixels[idx - stride * 3]);
let p2 = i32::from(pixels[idx - stride * 2]);
let p1 = i32::from(pixels[idx - stride]);
let p0 = i32::from(pixels[idx]);
let q0 = i32::from(pixels[idx + stride]);
let q1 = i32::from(pixels[idx + stride * 2]);
let q2 = i32::from(pixels[idx + stride * 3]);
let q3 = i32::from(pixels[idx + stride * 4]);
if !should_filter(p3, p2, p1, p0, q0, q1, q2, q3, limit, thresh) {
continue;
}
let hev = is_high_edge_variance(p1, p0, q0, q1, hev_thresh);
let (new_p0, new_q0, new_p1, new_q1) = if hev {
simple_filter(p0, q0)
} else {
normal_filter(p2, p1, p0, q0, q1, q2)
};
pixels[idx] = new_p0;
pixels[idx + stride] = new_q0;
if !hev {
pixels[idx - stride] = new_p1;
pixels[idx + stride * 2] = new_q1;
}
}
}
#[allow(clippy::similar_names)]
pub fn filter_vertical_edge(
pixels: &mut [u8],
stride: usize,
offset: usize,
hev_thresh: u8,
limit: u8,
thresh: u8,
) {
for i in 0..4 {
let idx = offset + i * stride;
if idx < 3 || idx + 4 >= pixels.len() {
continue;
}
let p3 = i32::from(pixels[idx - 3]);
let p2 = i32::from(pixels[idx - 2]);
let p1 = i32::from(pixels[idx - 1]);
let p0 = i32::from(pixels[idx]);
let q0 = i32::from(pixels[idx + 1]);
let q1 = i32::from(pixels[idx + 2]);
let q2 = i32::from(pixels[idx + 3]);
let q3 = i32::from(pixels[idx + 4]);
if !should_filter(p3, p2, p1, p0, q0, q1, q2, q3, limit, thresh) {
continue;
}
let hev = is_high_edge_variance(p1, p0, q0, q1, hev_thresh);
let (new_p0, new_q0, new_p1, new_q1) = if hev {
simple_filter(p0, q0)
} else {
normal_filter(p2, p1, p0, q0, q1, q2)
};
pixels[idx] = new_p0;
pixels[idx + 1] = new_q0;
if !hev {
pixels[idx - 1] = new_p1;
pixels[idx + 2] = new_q1;
}
}
}
#[allow(clippy::similar_names)]
#[allow(clippy::many_single_char_names)]
fn should_filter(
p3: i32,
p2: i32,
p1: i32,
p0: i32,
q0: i32,
q1: i32,
q2: i32,
q3: i32,
limit: u8,
thresh: u8,
) -> bool {
let limit = i32::from(limit);
let thresh = i32::from(thresh);
if (p0 - q0).abs() * 2 + (p1 - q1).abs() / 2 > limit {
return false;
}
if (p3 - p2).abs() > thresh
|| (p2 - p1).abs() > thresh
|| (p1 - p0).abs() > thresh
|| (q3 - q2).abs() > thresh
|| (q2 - q1).abs() > thresh
|| (q1 - q0).abs() > thresh
{
return false;
}
true
}
#[allow(clippy::similar_names)]
fn is_high_edge_variance(p1: i32, p0: i32, q0: i32, q1: i32, thresh: u8) -> bool {
let thresh = i32::from(thresh);
(p1 - p0).abs() > thresh || (q1 - q0).abs() > thresh
}
#[allow(clippy::similar_names)]
fn simple_filter(p0: i32, q0: i32) -> (u8, u8, u8, u8) {
let diff = (q0 - p0).clamp(-128, 127);
let delta = (diff * 3 + 4) >> 3;
let new_p0 = (p0 + delta).clamp(0, 255) as u8;
let new_q0 = (q0 - delta).clamp(0, 255) as u8;
(new_p0, new_q0, p0 as u8, q0 as u8)
}
#[allow(clippy::similar_names)]
fn normal_filter(p2: i32, p1: i32, p0: i32, q0: i32, q1: i32, q2: i32) -> (u8, u8, u8, u8) {
let diff = (q0 - p0).clamp(-128, 127);
let delta = ((diff * 3 + 4) >> 3).clamp(-63, 63);
let new_p0 = (p0 + delta).clamp(0, 255) as u8;
let new_q0 = (q0 - delta).clamp(0, 255) as u8;
let delta2 = (delta + 1) >> 1;
let new_p1 = (p1 + delta2).clamp(0, 255) as u8;
let new_q1 = (q1 - delta2).clamp(0, 255) as u8;
let _ = (p2, q2);
(new_p0, new_q0, new_p1, new_q1)
}
#[must_use]
pub fn calculate_filter_params(level: u8, sharpness: u8, is_keyframe: bool) -> (u8, u8, u8) {
let level = i32::from(level);
let sharpness = i32::from(sharpness);
let hev_thresh = if is_keyframe { 1 } else { 0 };
let limit = if level < 1 {
0
} else {
((level + 1) * 2).min(63) as u8
};
let thresh = if sharpness > 0 {
(level >> (sharpness - 1)).min(9) as u8
} else {
level.min(9) as u8
};
(hev_thresh, limit, thresh)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loop_filter_config() {
let config = LoopFilterConfig::new(10, 3);
assert_eq!(config.level, 10);
assert_eq!(config.sharpness, 3);
assert!(config.is_enabled());
let disabled = LoopFilterConfig::new(0, 0);
assert!(!disabled.is_enabled());
}
#[test]
fn test_effective_level() {
let config = LoopFilterConfig::new(10, 0);
let level = config.effective_level(0, 0);
assert_eq!(level, 11);
let level = config.effective_level(2, 1);
assert_eq!(level, 9); }
#[test]
fn test_should_filter() {
assert!(should_filter(
100, 102, 104, 106, 108, 110, 112, 114, 20, 10
));
assert!(!should_filter(
100, 100, 100, 100, 200, 200, 200, 200, 20, 10
));
assert!(!should_filter(
100, 150, 100, 150, 100, 150, 100, 150, 20, 10
));
}
#[test]
fn test_is_high_edge_variance() {
assert!(!is_high_edge_variance(100, 102, 104, 106, 10));
assert!(is_high_edge_variance(100, 120, 130, 150, 10));
}
#[test]
fn test_simple_filter() {
let (p0, q0, p1, q1) = simple_filter(100, 110);
assert!(p0 > 100);
assert!(q0 < 110);
assert_eq!(p1, 100);
assert_eq!(q1, 110);
}
#[test]
fn test_normal_filter() {
let (p0, q0, p1, q1) = normal_filter(98, 100, 102, 108, 110, 112);
assert!(p0 > 102);
assert!(q0 < 108);
assert!(p1 >= 100);
assert!(q1 <= 110);
}
#[test]
fn test_calculate_filter_params() {
let (hev, limit, thresh) = calculate_filter_params(10, 2, true);
assert_eq!(hev, 1); assert!(limit > 0);
assert!(thresh > 0);
let (hev2, _, _) = calculate_filter_params(10, 2, false);
assert_eq!(hev2, 0); }
#[test]
fn test_filter_horizontal_edge() {
let mut pixels = vec![100u8; 64]; for i in 0..32 {
pixels[i] = 100; }
for i in 32..64 {
pixels[i] = 110; }
filter_horizontal_edge(&mut pixels, 8, 28, 5, 50, 20);
assert!(pixels.len() == 64);
}
#[test]
fn test_filter_vertical_edge() {
let mut pixels = vec![100u8; 64];
for row in 0..8 {
for col in 0..4 {
pixels[row * 8 + col] = 100; }
for col in 4..8 {
pixels[row * 8 + col] = 110; }
}
filter_vertical_edge(&mut pixels, 8, 4, 5, 50, 20);
assert!(pixels.len() == 64);
}
#[test]
fn test_max_constants() {
assert_eq!(MAX_LOOP_FILTER, 63);
assert_eq!(MAX_SHARPNESS, 7);
}
}