#![forbid(unsafe_code)]
#![allow(dead_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::trivially_copy_pass_by_ref)]
#![allow(clippy::manual_div_ceil)]
#![allow(clippy::manual_rem_euclid)]
use super::{BitDepth, BlockDimensions, IntraPredContext, MAX_NEIGHBOR_SAMPLES};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum FilterStrength {
#[default]
None,
Weak,
Strong,
}
impl FilterStrength {
#[must_use]
pub fn from_angle_and_size(angle: i16, width: usize, height: usize) -> Self {
let is_steep = is_steep_angle(angle);
let min_dim = width.min(height);
if !is_steep {
Self::None
} else if min_dim >= 16 {
Self::Strong
} else if min_dim >= 8 {
Self::Weak
} else {
Self::None
}
}
}
#[must_use]
fn is_steep_angle(angle: i16) -> bool {
let angle = ((angle % 360) + 360) % 360;
let diagonals = [45, 135, 225, 315];
diagonals.iter().any(|&d| (angle - d).abs() < 23)
}
#[derive(Clone, Copy, Debug, Default)]
pub struct IntraEdgeFilter {
strength: FilterStrength,
bit_depth: BitDepth,
}
impl IntraEdgeFilter {
#[must_use]
pub const fn new(strength: FilterStrength, bit_depth: BitDepth) -> Self {
Self {
strength,
bit_depth,
}
}
#[must_use]
pub fn auto(angle: i16, dims: BlockDimensions, bit_depth: BitDepth) -> Self {
let strength = FilterStrength::from_angle_and_size(angle, dims.width, dims.height);
Self {
strength,
bit_depth,
}
}
#[must_use]
pub const fn strength(&self) -> FilterStrength {
self.strength
}
pub fn filter_top(&self, samples: &mut [u16], count: usize) {
match self.strength {
FilterStrength::None => {}
FilterStrength::Weak => self.apply_weak_filter(samples, count),
FilterStrength::Strong => self.apply_strong_filter(samples, count),
}
}
pub fn filter_left(&self, samples: &mut [u16], count: usize) {
match self.strength {
FilterStrength::None => {}
FilterStrength::Weak => self.apply_weak_filter(samples, count),
FilterStrength::Strong => self.apply_strong_filter(samples, count),
}
}
fn apply_weak_filter(&self, samples: &mut [u16], count: usize) {
if count < 3 {
return;
}
let max_val = self.bit_depth.max_value();
let mut filtered = [0u16; MAX_NEIGHBOR_SAMPLES];
filtered[0] = samples[0];
for i in 1..count.saturating_sub(1) {
let sum =
u32::from(samples[i - 1]) + 2 * u32::from(samples[i]) + u32::from(samples[i + 1]);
let val = (sum + 2) / 4;
filtered[i] = val.min(u32::from(max_val)) as u16;
}
if count > 1 {
filtered[count - 1] = samples[count - 1];
}
samples[..count].copy_from_slice(&filtered[..count]);
}
fn apply_strong_filter(&self, samples: &mut [u16], count: usize) {
if count < 5 {
self.apply_weak_filter(samples, count);
return;
}
let max_val = self.bit_depth.max_value();
let mut filtered = [0u16; MAX_NEIGHBOR_SAMPLES];
filtered[0] = samples[0];
if count > 1 {
let sum = u32::from(samples[0]) + 2 * u32::from(samples[1]) + u32::from(samples[2]);
filtered[1] = ((sum + 2) / 4).min(u32::from(max_val)) as u16;
}
for i in 2..count.saturating_sub(2) {
let sum = u32::from(samples[i - 2])
+ 2 * u32::from(samples[i - 1])
+ 2 * u32::from(samples[i])
+ 2 * u32::from(samples[i + 1])
+ u32::from(samples[i + 2]);
let val = (sum + 4) / 8;
filtered[i] = val.min(u32::from(max_val)) as u16;
}
if count > 2 {
let i = count - 2;
let sum =
u32::from(samples[i - 1]) + 2 * u32::from(samples[i]) + u32::from(samples[i + 1]);
filtered[i] = ((sum + 2) / 4).min(u32::from(max_val)) as u16;
}
if count > 1 {
filtered[count - 1] = samples[count - 1];
}
samples[..count].copy_from_slice(&filtered[..count]);
}
}
pub fn apply_intra_filter(ctx: &mut IntraPredContext, angle: i16, dims: BlockDimensions) {
let filter = IntraEdgeFilter::auto(angle, dims, ctx.bit_depth());
if filter.strength() == FilterStrength::None {
return;
}
let top_count = dims.width + dims.height;
let left_count = dims.height + dims.width;
ctx.filter_top_samples(|samples| {
filter.filter_top(samples, top_count.min(samples.len()));
});
ctx.filter_left_samples(|samples| {
filter.filter_left(samples, left_count.min(samples.len()));
});
}
pub struct RecursiveIntraHelper {
bit_depth: BitDepth,
}
impl RecursiveIntraHelper {
#[must_use]
pub const fn new(bit_depth: BitDepth) -> Self {
Self { bit_depth }
}
pub fn apply_recursive_filter(
&self,
output: &mut [u16],
stride: usize,
dims: BlockDimensions,
filter_type: RecursiveFilterType,
) {
match filter_type {
RecursiveFilterType::None => {}
RecursiveFilterType::Horizontal => {
self.filter_horizontal(output, stride, dims);
}
RecursiveFilterType::Vertical => {
self.filter_vertical(output, stride, dims);
}
RecursiveFilterType::Both => {
self.filter_horizontal(output, stride, dims);
self.filter_vertical(output, stride, dims);
}
}
}
fn filter_horizontal(&self, output: &mut [u16], stride: usize, dims: BlockDimensions) {
let max_val = self.bit_depth.max_value();
for y in 0..dims.height {
let row_start = y * stride;
for x in 1..dims.width {
let prev = u32::from(output[row_start + x - 1]);
let curr = u32::from(output[row_start + x]);
let filtered = (prev + curr + 1) / 2;
output[row_start + x] = filtered.min(u32::from(max_val)) as u16;
}
}
}
fn filter_vertical(&self, output: &mut [u16], stride: usize, dims: BlockDimensions) {
let max_val = self.bit_depth.max_value();
for x in 0..dims.width {
for y in 1..dims.height {
let prev = u32::from(output[(y - 1) * stride + x]);
let curr = u32::from(output[y * stride + x]);
let filtered = (prev + curr + 1) / 2;
output[y * stride + x] = filtered.min(u32::from(max_val)) as u16;
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum RecursiveFilterType {
#[default]
None,
Horizontal,
Vertical,
Both,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_strength_selection() {
let strength = FilterStrength::from_angle_and_size(45, 16, 16);
assert_eq!(strength, FilterStrength::Strong);
let strength = FilterStrength::from_angle_and_size(45, 8, 8);
assert_eq!(strength, FilterStrength::Weak);
let strength = FilterStrength::from_angle_and_size(45, 4, 4);
assert_eq!(strength, FilterStrength::None);
let strength = FilterStrength::from_angle_and_size(90, 16, 16);
assert_eq!(strength, FilterStrength::None);
}
#[test]
fn test_is_steep_angle() {
assert!(is_steep_angle(45));
assert!(is_steep_angle(50));
assert!(is_steep_angle(135));
assert!(is_steep_angle(315));
assert!(!is_steep_angle(0));
assert!(!is_steep_angle(90));
assert!(!is_steep_angle(180));
assert!(!is_steep_angle(270));
}
#[test]
fn test_weak_filter() {
let filter = IntraEdgeFilter::new(FilterStrength::Weak, BitDepth::Bits8);
let mut samples = [100u16, 150, 200, 150, 100];
filter.apply_weak_filter(&mut samples, 5);
assert_eq!(samples[0], 100);
assert_eq!(samples[4], 100);
assert!(samples[1] >= 140 && samples[1] <= 160);
assert!(samples[2] >= 170 && samples[2] <= 180);
assert!(samples[3] >= 140 && samples[3] <= 160);
}
#[test]
fn test_strong_filter() {
let filter = IntraEdgeFilter::new(FilterStrength::Strong, BitDepth::Bits8);
let mut samples = [100u16, 110, 200, 190, 100, 110, 100];
filter.apply_strong_filter(&mut samples, 7);
assert_eq!(samples[0], 100);
assert_eq!(samples[6], 100);
for sample in &samples {
assert!(*sample >= 100 && *sample <= 200);
}
}
#[test]
fn test_filter_clipping() {
let filter = IntraEdgeFilter::new(FilterStrength::Weak, BitDepth::Bits8);
let mut samples = [250u16, 255, 255, 255, 250];
filter.apply_weak_filter(&mut samples, 5);
for sample in &samples {
assert!(*sample <= 255);
}
}
#[test]
fn test_recursive_helper_horizontal() {
let helper = RecursiveIntraHelper::new(BitDepth::Bits8);
let mut output = vec![100u16, 200, 100, 200];
let dims = BlockDimensions::new(4, 1);
helper.filter_horizontal(&mut output, 4, dims);
assert_eq!(output[0], 100);
assert!(output[1] > 100 && output[1] < 200);
}
#[test]
fn test_recursive_helper_vertical() {
let helper = RecursiveIntraHelper::new(BitDepth::Bits8);
let mut output = vec![100u16, 100, 200, 200, 100, 100, 200, 200];
let dims = BlockDimensions::new(2, 4);
helper.filter_vertical(&mut output, 2, dims);
assert_eq!(output[0], 100);
assert_eq!(output[1], 100);
assert!(output[2] > 100 && output[2] < 200);
}
#[test]
fn test_auto_filter_creation() {
let filter = IntraEdgeFilter::auto(45, BlockDimensions::new(16, 16), BitDepth::Bits8);
assert_eq!(filter.strength(), FilterStrength::Strong);
let filter = IntraEdgeFilter::auto(90, BlockDimensions::new(16, 16), BitDepth::Bits8);
assert_eq!(filter.strength(), FilterStrength::None);
}
}