#[derive(Debug, Clone, Copy)]
pub struct Neighbors8x8 {
pub top: [u8; 16],
pub left: [u8; 8],
pub top_left: u8,
pub top_available: bool,
pub top_right_available: bool,
pub left_available: bool,
pub top_left_available: bool,
}
impl Neighbors8x8 {
pub fn with_top_right_fallback(
top_base: [u8; 8],
top_right: Option<[u8; 8]>,
left: [u8; 8],
top_left: u8,
top_available: bool,
left_available: bool,
top_left_available: bool,
) -> Self {
let mut top = [0u8; 16];
top[..8].copy_from_slice(&top_base);
let top_right_available = top_right.is_some();
match top_right {
Some(tr) => top[8..16].copy_from_slice(&tr),
None => {
for i in 8..16 {
top[i] = top_base[7];
}
}
}
Self {
top,
left,
top_left,
top_available,
top_right_available,
left_available,
top_left_available,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct FilteredSamples8x8 {
pub top: [u8; 16],
pub left: [u8; 8],
pub top_left: u8,
}
pub fn filter_reference_samples(n: &Neighbors8x8) -> FilteredSamples8x8 {
let mut f_top = [0u8; 16];
let mut f_left = [0u8; 8];
let f_top_left = if n.top_left_available {
let a = if n.top_available { n.top[0] } else { n.top_left };
let c = if n.left_available { n.left[0] } else { n.top_left };
(((a as u32) + 2 * (n.top_left as u32) + (c as u32) + 2) >> 2) as u8
} else {
n.top_left
};
if n.top_available {
let left_of = if n.top_left_available {
n.top_left as u32
} else {
n.top[0] as u32
};
f_top[0] = ((left_of + 2 * (n.top[0] as u32) + (n.top[1] as u32) + 2) >> 2) as u8;
for x in 1..=14 {
let a = n.top[x - 1] as u32;
let b = n.top[x] as u32;
let c = n.top[x + 1] as u32;
f_top[x] = ((a + 2 * b + c + 2) >> 2) as u8;
}
let a = n.top[14] as u32;
let b = n.top[15] as u32;
f_top[15] = ((a + 3 * b + 2) >> 2) as u8;
}
if n.left_available {
let above = if n.top_left_available {
n.top_left as u32
} else {
n.left[0] as u32
};
f_left[0] = ((above + 2 * (n.left[0] as u32) + (n.left[1] as u32) + 2) >> 2) as u8;
for y in 1..=6 {
let a = n.left[y - 1] as u32;
let b = n.left[y] as u32;
let c = n.left[y + 1] as u32;
f_left[y] = ((a + 2 * b + c + 2) >> 2) as u8;
}
let a = n.left[6] as u32;
let b = n.left[7] as u32;
f_left[7] = ((a + 3 * b + 2) >> 2) as u8;
}
FilteredSamples8x8 {
top: f_top,
left: f_left,
top_left: f_top_left,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Intra8x8Mode {
Vertical = 0,
Horizontal = 1,
Dc = 2,
DiagonalDownLeft = 3,
DiagonalDownRight = 4,
VerticalRight = 5,
HorizontalDown = 6,
VerticalLeft = 7,
HorizontalUp = 8,
}
impl Intra8x8Mode {
pub fn from_u8(v: u8) -> Option<Self> {
Some(match v {
0 => Self::Vertical,
1 => Self::Horizontal,
2 => Self::Dc,
3 => Self::DiagonalDownLeft,
4 => Self::DiagonalDownRight,
5 => Self::VerticalRight,
6 => Self::HorizontalDown,
7 => Self::VerticalLeft,
8 => Self::HorizontalUp,
_ => return None,
})
}
}
pub fn predict_8x8(mode: Intra8x8Mode, n: &Neighbors8x8) -> [[u8; 8]; 8] {
let f = filter_reference_samples(n);
match mode {
Intra8x8Mode::Vertical => pred_vertical(&f),
Intra8x8Mode::Horizontal => pred_horizontal(&f),
Intra8x8Mode::Dc => pred_dc(&f, n),
Intra8x8Mode::DiagonalDownLeft => pred_ddl(&f),
Intra8x8Mode::DiagonalDownRight => pred_ddr(&f),
Intra8x8Mode::VerticalRight => pred_vr(&f),
Intra8x8Mode::HorizontalDown => pred_hd(&f),
Intra8x8Mode::VerticalLeft => pred_vl(&f),
Intra8x8Mode::HorizontalUp => pred_hu(&f),
}
}
fn pred_vertical(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut out = [[0u8; 8]; 8];
for y in 0..8 {
for x in 0..8 {
out[y][x] = f.top[x];
}
}
out
}
fn pred_horizontal(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut out = [[0u8; 8]; 8];
for y in 0..8 {
for x in 0..8 {
out[y][x] = f.left[y];
}
}
out
}
fn pred_dc(f: &FilteredSamples8x8, raw: &Neighbors8x8) -> [[u8; 8]; 8] {
let dc: u32 = if raw.top_available && raw.left_available {
let sum: u32 = f.top[..8].iter().map(|&v| v as u32).sum::<u32>()
+ f.left.iter().map(|&v| v as u32).sum::<u32>();
(sum + 8) >> 4
} else if raw.top_available {
let sum: u32 = f.top[..8].iter().map(|&v| v as u32).sum();
(sum + 4) >> 3
} else if raw.left_available {
let sum: u32 = f.left.iter().map(|&v| v as u32).sum();
(sum + 4) >> 3
} else {
128
};
[[dc as u8; 8]; 8]
}
fn pred_ddl(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut out = [[0u8; 8]; 8];
for y in 0..8 {
for x in 0..8 {
let pred = if x == 7 && y == 7 {
let a = f.top[14] as u32;
let b = f.top[15] as u32;
(a + 3 * b + 2) >> 2
} else {
let a = f.top[x + y] as u32;
let b = f.top[x + y + 1] as u32;
let c = f.top[x + y + 2] as u32;
(a + 2 * b + c + 2) >> 2
};
out[y][x] = pred as u8;
}
}
out
}
fn pred_ddr(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut pred = [[0u8; 8]; 8];
let dc_diag = (ref_top(f, 0) + 2 * ref_top_left(f) + ref_left(f, 0) + 2) >> 2;
for y in 0..8i32 {
for x in 0..8i32 {
let v = if x > y {
let k = (x - y) as isize;
(ref_top_signed(f, k - 2) + 2 * ref_top_signed(f, k - 1)
+ ref_top_signed(f, k) + 2) >> 2
} else if x < y {
let k = (y - x) as isize;
(ref_left_signed(f, k - 2) + 2 * ref_left_signed(f, k - 1)
+ ref_left_signed(f, k) + 2) >> 2
} else {
dc_diag
};
pred[y as usize][x as usize] = v as u8;
}
}
pred
}
fn pred_vr(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut pred = [[0u8; 8]; 8];
let bridge = (ref_left(f, 0) + 2 * ref_top_left(f) + ref_top(f, 0) + 2) >> 2;
for y in 0..8i32 {
for x in 0..8i32 {
let z_vr = 2 * x - y;
let v = if z_vr >= 0 && z_vr & 1 == 0 {
let k = (x - (y >> 1)) as isize;
(ref_top_signed(f, k - 1) + ref_top_signed(f, k) + 1) >> 1
} else if z_vr >= 0 {
let k = (x - (y >> 1)) as isize;
(ref_top_signed(f, k - 2) + 2 * ref_top_signed(f, k - 1)
+ ref_top_signed(f, k) + 2) >> 2
} else if z_vr == -1 {
bridge
} else {
let k = (y - 2 * x) as isize;
(ref_left_signed(f, k - 1) + 2 * ref_left_signed(f, k - 2)
+ ref_left_signed(f, k - 3) + 2) >> 2
};
pred[y as usize][x as usize] = v as u8;
}
}
pred
}
fn pred_hd(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut pred = [[0u8; 8]; 8];
let bridge = (ref_left(f, 0) + 2 * ref_top_left(f) + ref_top(f, 0) + 2) >> 2;
for y in 0..8i32 {
for x in 0..8i32 {
let z_hd = 2 * y - x;
let v = if z_hd >= 0 && z_hd & 1 == 0 {
let k = (y - (x >> 1)) as isize;
(ref_left_signed(f, k - 1) + ref_left_signed(f, k) + 1) >> 1
} else if z_hd >= 0 {
let k = (y - (x >> 1)) as isize;
(ref_left_signed(f, k - 2) + 2 * ref_left_signed(f, k - 1)
+ ref_left_signed(f, k) + 2) >> 2
} else if z_hd == -1 {
bridge
} else {
let k = (x - 2 * y) as isize;
(ref_top_signed(f, k - 1) + 2 * ref_top_signed(f, k - 2)
+ ref_top_signed(f, k - 3) + 2) >> 2
};
pred[y as usize][x as usize] = v as u8;
}
}
pred
}
fn pred_vl(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut out = [[0u8; 8]; 8];
for y in 0..8 {
for x in 0..8 {
let zvl_even = y & 1 == 0;
let pred = if zvl_even {
let idx = x + (y >> 1);
let a = f.top[idx] as u32;
let b = f.top[idx + 1] as u32;
(a + b + 1) >> 1
} else {
let idx = x + (y >> 1);
let a = f.top[idx] as u32;
let b = f.top[idx + 1] as u32;
let c = f.top[idx + 2] as u32;
(a + 2 * b + c + 2) >> 2
};
out[y][x] = pred as u8;
}
}
out
}
fn pred_hu(f: &FilteredSamples8x8) -> [[u8; 8]; 8] {
let mut pred = [[0u8; 8]; 8];
let edge_tap = (ref_left(f, 6) + 3 * ref_left(f, 7) + 2) >> 2;
let tail = ref_left(f, 7);
for y in 0..8i32 {
for x in 0..8i32 {
let z_hu = x + 2 * y;
let k = (y + (x >> 1)) as isize;
let v = if z_hu <= 12 && z_hu & 1 == 0 {
(ref_left_signed(f, k) + ref_left_signed(f, k + 1) + 1) >> 1
} else if z_hu <= 11 {
(ref_left_signed(f, k) + 2 * ref_left_signed(f, k + 1)
+ ref_left_signed(f, k + 2) + 2) >> 2
} else if z_hu == 13 {
edge_tap
} else {
tail
};
pred[y as usize][x as usize] = v as u8;
}
}
pred
}
#[inline]
fn ref_top(f: &FilteredSamples8x8, x: usize) -> u32 {
f.top[x] as u32
}
#[inline]
fn ref_left(f: &FilteredSamples8x8, y: usize) -> u32 {
f.left[y] as u32
}
#[inline]
fn ref_top_left(f: &FilteredSamples8x8) -> u32 {
f.top_left as u32
}
#[inline]
fn ref_top_signed(f: &FilteredSamples8x8, x: isize) -> u32 {
if x < 0 {
f.top_left as u32
} else if (x as usize) < f.top.len() {
f.top[x as usize] as u32
} else {
f.top[f.top.len() - 1] as u32
}
}
#[inline]
fn ref_left_signed(f: &FilteredSamples8x8, y: isize) -> u32 {
if y < 0 {
f.top_left as u32
} else if (y as usize) < f.left.len() {
f.left[y as usize] as u32
} else {
f.left[f.left.len() - 1] as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
fn neighbors_all_eq(v: u8) -> Neighbors8x8 {
Neighbors8x8 {
top: [v; 16],
left: [v; 8],
top_left: v,
top_available: true,
top_right_available: true,
left_available: true,
top_left_available: true,
}
}
fn neighbors_none() -> Neighbors8x8 {
Neighbors8x8 {
top: [0; 16],
left: [0; 8],
top_left: 0,
top_available: false,
top_right_available: false,
left_available: false,
top_left_available: false,
}
}
#[test]
fn filter_flat_neighbors_is_flat() {
let n = neighbors_all_eq(128);
let f = filter_reference_samples(&n);
for &v in &f.top {
assert_eq!(v, 128);
}
for &v in &f.left {
assert_eq!(v, 128);
}
assert_eq!(f.top_left, 128);
}
#[test]
fn filter_preserves_smooth_gradient() {
let mut n = neighbors_all_eq(0);
for i in 0..16 {
n.top[i] = (i * 16) as u8; }
for i in 0..8 {
n.left[i] = (i * 16) as u8;
}
n.top_left = 0;
let f = filter_reference_samples(&n);
for i in 1..=14 {
let diff = (f.top[i] as i32 - n.top[i] as i32).abs();
assert!(diff <= 1, "top[{i}] filter changed gradient by {diff}");
}
}
#[test]
fn vertical_mode_copies_top_down() {
let mut n = neighbors_all_eq(100);
for i in 0..8 {
n.top[i] = (100 + i * 10) as u8;
}
let out = predict_8x8(Intra8x8Mode::Vertical, &n);
for y in 0..8 {
for x in 0..8 {
assert!(out[y][x] >= 90 && out[y][x] <= 200);
assert_eq!(out[y][x], out[0][x]);
}
}
}
#[test]
fn horizontal_mode_copies_left_across() {
let mut n = neighbors_all_eq(100);
for i in 0..8 {
n.left[i] = (100 + i * 10) as u8;
}
let out = predict_8x8(Intra8x8Mode::Horizontal, &n);
for y in 0..8 {
for x in 0..8 {
assert_eq!(out[y][x], out[y][0]);
}
}
}
#[test]
fn dc_mode_all_equal() {
let n = neighbors_all_eq(75);
let out = predict_8x8(Intra8x8Mode::Dc, &n);
let v0 = out[0][0];
for row in &out {
for &v in row {
assert_eq!(v, v0);
}
}
assert!((74..=76).contains(&v0));
}
#[test]
fn dc_mode_no_neighbors_is_128() {
let n = neighbors_none();
let out = predict_8x8(Intra8x8Mode::Dc, &n);
for row in &out {
for &v in row {
assert_eq!(v, 128);
}
}
}
#[test]
fn all_modes_produce_valid_u8() {
let mut n = neighbors_all_eq(64);
for i in 0..16 {
n.top[i] = (i * 16) as u8;
}
for i in 0..8 {
n.left[i] = (128 + i * 8) as u8;
}
n.top_left = 64;
for mode_id in 0..=8u8 {
let mode = Intra8x8Mode::from_u8(mode_id).unwrap();
let out = predict_8x8(mode, &n);
for row in &out {
for &v in row {
let _ = v; }
}
}
}
#[test]
fn from_u8_roundtrip() {
for id in 0..=8u8 {
let m = Intra8x8Mode::from_u8(id).unwrap();
assert_eq!(m as u8, id);
}
assert!(Intra8x8Mode::from_u8(9).is_none());
}
#[test]
fn with_top_right_fallback_replicates_top7() {
let top_base = [10, 20, 30, 40, 50, 60, 70, 80];
let n = Neighbors8x8::with_top_right_fallback(
top_base, None, [0; 8], 0,
true, true, true,
);
assert_eq!(&n.top[..8], &top_base);
for i in 8..16 {
assert_eq!(n.top[i], 80, "top[{i}] should replicate top[7]=80");
}
assert!(!n.top_right_available);
}
}