#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntraMode {
DcPred = 0,
VPred = 1,
HPred = 2,
D45Pred = 3,
D135Pred = 4,
D117Pred = 5,
D153Pred = 6,
D207Pred = 7,
D63Pred = 8,
PaethPred = 9,
SmoothPred = 10,
SmoothVPred = 11,
SmoothHPred = 12,
}
fn dc_value(top: Option<&[u8]>, left: Option<&[u8]>) -> u8 {
match (top, left) {
(None, None) => 128u8,
(Some(t), None) => {
let sum: u32 = t[..4].iter().map(|&x| x as u32).sum();
((sum + 2) / 4) as u8
}
(None, Some(l)) => {
let sum: u32 = l[..4].iter().map(|&x| x as u32).sum();
((sum + 2) / 4) as u8
}
(Some(t), Some(l)) => {
let sum: u32 = t[..4].iter().chain(l[..4].iter()).map(|&x| x as u32).sum();
((sum + 4) / 8) as u8
}
}
}
fn predict_dc(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
let dc = dc_value(top, left);
[dc; 16]
}
fn predict_v(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match top {
None => predict_dc(None, left),
Some(t) => {
let mut out = [0u8; 16];
for r in 0..4 {
out[r * 4] = t[0];
out[r * 4 + 1] = t[1];
out[r * 4 + 2] = t[2];
out[r * 4 + 3] = t[3];
}
out
}
}
}
fn predict_h(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match left {
None => predict_dc(top, None),
Some(l) => {
let mut out = [0u8; 16];
for r in 0..4 {
let pix = l[r];
out[r * 4] = pix;
out[r * 4 + 1] = pix;
out[r * 4 + 2] = pix;
out[r * 4 + 3] = pix;
}
out
}
}
}
#[inline(always)]
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
let a = a as i32;
let b = b as i32;
let c = c as i32;
let p = a + b - c;
let pa = (p - a).abs();
let pb = (p - b).abs();
let pc = (p - c).abs();
if pa <= pb && pa <= pc {
a as u8
} else if pb <= pc {
b as u8
} else {
c as u8
}
}
fn predict_paeth(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match (top, left) {
(Some(t), Some(l)) => {
let top_left = (t[0] as u32 + l[0] as u32).div_ceil(2) as u8;
let mut out = [0u8; 16];
for r in 0..4usize {
for c in 0..4usize {
out[r * 4 + c] = paeth_predictor(l[r], t[c], top_left);
}
}
out
}
_ => predict_dc(top, left),
}
}
fn predict_smooth(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match (top, left) {
(Some(t), Some(l)) => {
let bottom_right = l[3] as u32;
let mut out = [0u8; 16];
for r in 0..4usize {
let wr = r as u32; let wt = 4 - wr; for c in 0..4usize {
let blended = (t[c] as u32 * wt + bottom_right * wr + 2) / 4;
out[r * 4 + c] = blended.min(255) as u8;
}
}
out
}
_ => predict_dc(top, left),
}
}
fn predict_smooth_v(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match (top, left) {
(Some(t), Some(l)) => {
let bl = l[3] as u32;
let mut out = [0u8; 16];
for r in 0..4usize {
let wr = r as u32;
let wt = 4 - wr;
for c in 0..4usize {
let blended = (t[c] as u32 * wt + bl * wr + 2) / 4;
out[r * 4 + c] = blended.min(255) as u8;
}
}
out
}
_ => predict_dc(top, left),
}
}
fn predict_smooth_h(top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match (top, left) {
(Some(t), Some(l)) => {
let tr = t[3] as u32;
let mut out = [0u8; 16];
for r in 0..4usize {
let lr = l[r] as u32;
for c in 0..4usize {
let wl = (4 - c) as u32;
let wr = c as u32;
let blended = (lr * wl + tr * wr + 2) / 4;
out[r * 4 + c] = blended.min(255) as u8;
}
}
out
}
_ => predict_dc(top, left),
}
}
pub fn predict_4x4(mode: IntraMode, top: Option<&[u8]>, left: Option<&[u8]>) -> [u8; 16] {
match mode {
IntraMode::DcPred => predict_dc(top, left),
IntraMode::VPred => predict_v(top, left),
IntraMode::HPred => predict_h(top, left),
IntraMode::PaethPred => predict_paeth(top, left),
IntraMode::SmoothPred => predict_smooth(top, left),
IntraMode::SmoothVPred => predict_smooth_v(top, left),
IntraMode::SmoothHPred => predict_smooth_h(top, left),
IntraMode::D45Pred
| IntraMode::D135Pred
| IntraMode::D117Pred
| IntraMode::D153Pred
| IntraMode::D207Pred
| IntraMode::D63Pred => predict_dc(top, left),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dc_pred_no_borders_is_128() {
let out = predict_4x4(IntraMode::DcPred, None, None);
assert_eq!(out, [128u8; 16], "DcPred with no borders must fill with 128");
}
#[test]
fn dc_pred_top_only() {
let top = [200u8, 200, 200, 200];
let out = predict_4x4(IntraMode::DcPred, Some(&top), None);
assert_eq!(out, [200u8; 16]);
}
#[test]
fn dc_pred_left_only() {
let left = [100u8, 100, 100, 100];
let out = predict_4x4(IntraMode::DcPred, None, Some(&left));
assert_eq!(out, [100u8; 16]);
}
#[test]
fn dc_pred_both_borders() {
let top = [100u8; 4];
let left = [60u8; 4];
let out = predict_4x4(IntraMode::DcPred, Some(&top), Some(&left));
assert_eq!(out, [80u8; 16]);
}
#[test]
fn dc_pred_rounding() {
let top = [7u8; 4];
let left = [7u8; 4];
let out = predict_4x4(IntraMode::DcPred, Some(&top), Some(&left));
assert_eq!(out[0], 7);
}
#[test]
fn v_pred_copies_top_row() {
let top = [10u8, 20, 30, 40];
let left = [0u8; 4];
let out = predict_4x4(IntraMode::VPred, Some(&top), Some(&left));
for r in 0..4 {
for c in 0..4 {
assert_eq!(
out[r * 4 + c], top[c],
"VPred pixel at ({r},{c}) must equal top[{c}]={}", top[c]
);
}
}
}
#[test]
fn v_pred_no_top_falls_back_to_dc() {
let left = [80u8; 4];
let dc_out = predict_4x4(IntraMode::DcPred, None, Some(&left));
let v_out = predict_4x4(IntraMode::VPred, None, Some(&left));
assert_eq!(dc_out, v_out, "VPred with no top must fall back to DcPred");
}
#[test]
fn h_pred_copies_left_column() {
let top = [0u8; 4];
let left = [50u8, 60, 70, 80];
let out = predict_4x4(IntraMode::HPred, Some(&top), Some(&left));
for r in 0..4 {
for c in 0..4 {
assert_eq!(
out[r * 4 + c], left[r],
"HPred pixel at ({r},{c}) must equal left[{r}]={}", left[r]
);
}
}
}
#[test]
fn h_pred_no_left_falls_back_to_dc() {
let top = [90u8; 4];
let dc_out = predict_4x4(IntraMode::DcPred, Some(&top), None);
let h_out = predict_4x4(IntraMode::HPred, Some(&top), None);
assert_eq!(dc_out, h_out, "HPred with no left must fall back to DcPred");
}
#[test]
fn paeth_pred_flat_borders_equals_border_value() {
let top = [100u8; 4];
let left = [100u8; 4];
let out = predict_4x4(IntraMode::PaethPred, Some(&top), Some(&left));
assert_eq!(out, [100u8; 16]);
}
#[test]
fn paeth_pred_no_borders_fallback() {
let out = predict_4x4(IntraMode::PaethPred, None, None);
assert_eq!(out, [128u8; 16]);
}
#[test]
fn paeth_pred_values_in_range() {
let top = [30u8, 60, 90, 120];
let left = [40u8, 80, 120, 160];
let out = predict_4x4(IntraMode::PaethPred, Some(&top), Some(&left));
for &px in &out {
let _ = px; }
assert!(!out.is_empty(), "Paeth prediction must produce 16 pixels");
}
#[test]
fn smooth_pred_top_row_is_top_border() {
let top = [10u8, 20, 30, 40];
let left = [0u8, 50, 100, 200];
let out = predict_4x4(IntraMode::SmoothPred, Some(&top), Some(&left));
for c in 0..4 {
assert_eq!(out[c], top[c], "smooth row 0 col {c} must equal top[{c}]");
}
}
#[test]
fn smooth_pred_no_borders_fallback() {
let out = predict_4x4(IntraMode::SmoothPred, None, None);
assert_eq!(out, [128u8; 16]);
}
#[test]
fn all_modes_produce_valid_pixels() {
let top = [100u8, 110, 120, 130];
let left = [90u8, 95, 100, 105];
let modes = [
IntraMode::DcPred,
IntraMode::VPred,
IntraMode::HPred,
IntraMode::D45Pred,
IntraMode::D135Pred,
IntraMode::D117Pred,
IntraMode::D153Pred,
IntraMode::D207Pred,
IntraMode::D63Pred,
IntraMode::PaethPred,
IntraMode::SmoothPred,
IntraMode::SmoothVPred,
IntraMode::SmoothHPred,
];
for mode in modes {
let out = predict_4x4(mode, Some(&top), Some(&left));
for &px in &out {
assert!(px as u32 <= 255, "pixel overflow in mode {mode:?}");
}
}
}
#[test]
fn all_modes_no_border_do_not_panic() {
let modes = [
IntraMode::DcPred,
IntraMode::VPred,
IntraMode::HPred,
IntraMode::D45Pred,
IntraMode::D135Pred,
IntraMode::D117Pred,
IntraMode::D153Pred,
IntraMode::D207Pred,
IntraMode::D63Pred,
IntraMode::PaethPred,
IntraMode::SmoothPred,
IntraMode::SmoothVPred,
IntraMode::SmoothHPred,
];
for mode in modes {
let _ = predict_4x4(mode, None, None);
}
}
}