#![forbid(unsafe_code)]
#![allow(dead_code)]
#![allow(clippy::doc_markdown)]
use super::{BlockDimensions, IntraPredContext, IntraPredictor};
#[must_use]
#[inline]
pub fn paeth_predictor(top: u16, left: u16, top_left: u16) -> u16 {
let p = i32::from(top) + i32::from(left) - i32::from(top_left);
let p_top = (p - i32::from(top)).abs();
let p_left = (p - i32::from(left)).abs();
let p_top_left = (p - i32::from(top_left)).abs();
if p_top <= p_left && p_top <= p_top_left {
top
} else if p_left <= p_top_left {
left
} else {
top_left
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PaethPredictor;
impl PaethPredictor {
#[must_use]
pub const fn new() -> Self {
Self
}
pub fn predict_paeth(
ctx: &IntraPredContext,
output: &mut [u16],
stride: usize,
dims: BlockDimensions,
) {
let top = ctx.top_samples();
let left = ctx.left_samples();
let base_top_left = ctx.top_left_sample();
for y in 0..dims.height {
let row_start = y * stride;
for x in 0..dims.width {
let t = top[x];
let l = left[y];
let tl = if x == 0 && y == 0 {
base_top_left
} else if x == 0 {
left[y.saturating_sub(1)]
} else if y == 0 {
top[x.saturating_sub(1)]
} else {
base_top_left
};
output[row_start + x] = paeth_predictor(t, l, tl);
}
}
}
pub fn predict_paeth_with_reconstruction(
ctx: &IntraPredContext,
output: &mut [u16],
stride: usize,
dims: BlockDimensions,
) {
let top = ctx.top_samples();
let left = ctx.left_samples();
let base_top_left = ctx.top_left_sample();
for y in 0..dims.height {
let row_start = y * stride;
for x in 0..dims.width {
let t = top[x];
let l = left[y];
let tl = if x == 0 && y == 0 {
base_top_left
} else if x == 0 {
left[y - 1]
} else if y == 0 {
top[x - 1]
} else {
output[(y - 1) * stride + (x - 1)]
};
output[row_start + x] = paeth_predictor(t, l, tl);
}
}
}
}
impl IntraPredictor for PaethPredictor {
fn predict(
&self,
ctx: &IntraPredContext,
output: &mut [u16],
stride: usize,
dims: BlockDimensions,
) {
Self::predict_paeth(ctx, output, stride, dims);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PaethSelection {
Top,
Left,
TopLeft,
}
#[must_use]
pub fn paeth_selection(top: u16, left: u16, top_left: u16) -> PaethSelection {
let p = i32::from(top) + i32::from(left) - i32::from(top_left);
let p_top = (p - i32::from(top)).abs();
let p_left = (p - i32::from(left)).abs();
let p_top_left = (p - i32::from(top_left)).abs();
if p_top <= p_left && p_top <= p_top_left {
PaethSelection::Top
} else if p_left <= p_top_left {
PaethSelection::Left
} else {
PaethSelection::TopLeft
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intra::context::IntraPredContext;
use crate::intra::BitDepth;
#[test]
fn test_paeth_predictor_top() {
assert_eq!(paeth_predictor(100, 100, 100), 100);
assert_eq!(paeth_predictor(200, 50, 100), 200);
}
#[test]
fn test_paeth_predictor_left() {
assert_eq!(paeth_predictor(100, 150, 100), 150);
}
#[test]
fn test_paeth_predictor_top_left() {
assert_eq!(paeth_predictor(50, 50, 100), 50);
let result = paeth_predictor(200, 200, 150);
assert_eq!(result, 200);
}
#[test]
fn test_paeth_selection() {
assert_eq!(paeth_selection(100, 100, 100), PaethSelection::Top);
assert_eq!(paeth_selection(100, 150, 100), PaethSelection::Left);
}
fn create_test_context() -> IntraPredContext {
let mut ctx = IntraPredContext::new(4, 4, BitDepth::Bits8);
for i in 0..8 {
ctx.set_top_sample(i, (100 + i * 10) as u16);
}
for i in 0..8 {
ctx.set_left_sample(i, (80 + i * 10) as u16);
}
ctx.set_top_left_sample(90);
ctx.set_availability(true, true);
ctx
}
#[test]
fn test_paeth_predictor_block() {
let ctx = create_test_context();
let predictor = PaethPredictor::new();
let dims = BlockDimensions::new(4, 4);
let mut output = vec![0u16; 16];
predictor.predict(&ctx, &mut output, 4, dims);
let top = ctx.top_samples();
let left = ctx.left_samples();
let tl = ctx.top_left_sample();
for y in 0..4 {
for x in 0..4 {
let val = output[y * 4 + x];
assert!(
val == top[x] || val == left[y] || val == tl,
"Value {} at ({}, {}) not from neighbors: top={}, left={}, tl={}",
val,
x,
y,
top[x],
left[y],
tl
);
}
}
}
#[test]
fn test_paeth_edge_cases() {
assert_eq!(paeth_predictor(0, 0, 0), 0);
assert_eq!(paeth_predictor(255, 255, 255), 255);
assert_eq!(paeth_predictor(255, 0, 128), 128);
}
#[test]
fn test_paeth_with_gradient() {
let mut ctx = IntraPredContext::new(4, 4, BitDepth::Bits8);
for i in 0..8 {
ctx.set_top_sample(i, 100);
ctx.set_left_sample(i, 100);
}
ctx.set_top_left_sample(100);
ctx.set_availability(true, true);
let predictor = PaethPredictor::new();
let dims = BlockDimensions::new(4, 4);
let mut output = vec![0u16; 16];
predictor.predict(&ctx, &mut output, 4, dims);
for &val in &output {
assert_eq!(val, 100);
}
}
}