#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Intra4x4Mode {
Vertical = 0,
Horizontal = 1,
Dc = 2,
DiagonalDownLeft = 3,
DiagonalDownRight = 4,
VerticalRight = 5,
HorizontalDown = 6,
VerticalLeft = 7,
HorizontalUp = 8,
}
impl Intra4x4Mode {
pub fn as_u8(self) -> u8 {
self as u8
}
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 const ALL: [Intra4x4Mode; 9] = [
Intra4x4Mode::Vertical,
Intra4x4Mode::Horizontal,
Intra4x4Mode::Dc,
Intra4x4Mode::DiagonalDownLeft,
Intra4x4Mode::DiagonalDownRight,
Intra4x4Mode::VerticalRight,
Intra4x4Mode::HorizontalDown,
Intra4x4Mode::VerticalLeft,
Intra4x4Mode::HorizontalUp,
];
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Intra4x4Availability {
pub top_left: bool,
pub top: bool,
pub top_right: bool,
pub left: bool,
}
pub const LUMA_4X4_XY: [(usize, usize); 16] = [
(0, 0),
(4, 0),
(0, 4),
(4, 4),
(8, 0),
(12, 0),
(8, 4),
(12, 4),
(0, 8),
(4, 8),
(0, 12),
(4, 12),
(8, 8),
(12, 8),
(8, 12),
(12, 12),
];
#[allow(unused)]
pub fn block_has_real_top_right(blk: usize) -> bool {
!matches!(blk, 3 | 7 | 11 | 15 | 5 | 13)
}
pub fn gather_top_row(
recon: &[u8],
pic_w: usize,
mb_x: usize,
mb_y: usize,
bx: usize,
by: usize,
top_avail: bool,
) -> [i32; 8] {
let mut p = [0i32; 8];
if !top_avail {
return p;
}
let abs_y = (mb_y * 16 + by).wrapping_sub(1);
for (i, slot) in p[..4].iter_mut().enumerate() {
let abs_x = mb_x * 16 + bx + i;
*slot = recon[abs_y * pic_w + abs_x] as i32;
}
let blk = LUMA_4X4_XY
.iter()
.position(|&(x, y)| x == bx && y == by)
.unwrap_or(0);
let need_substitute = matches!(blk, 3 | 7 | 11 | 13 | 15);
let abs_x_start = mb_x * 16 + bx + 4;
let off_right = abs_x_start + 4 > pic_w;
if need_substitute || off_right {
let v = p[3];
for slot in &mut p[4..8] {
*slot = v;
}
} else {
for (i, slot) in p[4..8].iter_mut().enumerate() {
let abs_x = mb_x * 16 + bx + 4 + i;
*slot = recon[abs_y * pic_w + abs_x] as i32;
}
}
p
}
pub fn gather_left_col(
recon: &[u8],
pic_w: usize,
mb_x: usize,
mb_y: usize,
bx: usize,
by: usize,
left_avail: bool,
) -> [i32; 4] {
let mut l = [0i32; 4];
if !left_avail {
return l;
}
let abs_x = (mb_x * 16 + bx).wrapping_sub(1);
for (j, slot) in l.iter_mut().enumerate() {
let abs_y = mb_y * 16 + by + j;
*slot = recon[abs_y * pic_w + abs_x] as i32;
}
l
}
pub fn gather_top_left(
recon: &[u8],
pic_w: usize,
mb_x: usize,
mb_y: usize,
bx: usize,
by: usize,
top_left_avail: bool,
) -> i32 {
if !top_left_avail {
return 0;
}
let abs_x = (mb_x * 16 + bx).wrapping_sub(1);
let abs_y = (mb_y * 16 + by).wrapping_sub(1);
recon[abs_y * pic_w + abs_x] as i32
}
pub fn availability(
mb_x: usize,
mb_y: usize,
bx: usize,
by: usize,
_pic_w_mbs: usize,
) -> Intra4x4Availability {
let top = mb_y > 0 || by > 0;
let left = mb_x > 0 || bx > 0;
let top_left = top && left;
let top_right = top;
Intra4x4Availability {
top_left,
top,
top_right,
left,
}
}
#[allow(clippy::too_many_arguments)] pub fn predict_4x4(
mode: Intra4x4Mode,
recon: &[u8],
pic_w: usize,
mb_x: usize,
mb_y: usize,
bx: usize,
by: usize,
avail: Intra4x4Availability,
) -> Option<[i32; 16]> {
match mode {
Intra4x4Mode::Vertical | Intra4x4Mode::DiagonalDownLeft | Intra4x4Mode::VerticalLeft
if !avail.top =>
{
return None
}
Intra4x4Mode::Horizontal | Intra4x4Mode::HorizontalUp if !avail.left => return None,
Intra4x4Mode::DiagonalDownRight
| Intra4x4Mode::VerticalRight
| Intra4x4Mode::HorizontalDown
if !(avail.top && avail.left && avail.top_left) =>
{
return None;
}
_ => {}
}
let top = gather_top_row(recon, pic_w, mb_x, mb_y, bx, by, avail.top);
let left = gather_left_col(recon, pic_w, mb_x, mb_y, bx, by, avail.left);
let tl = gather_top_left(recon, pic_w, mb_x, mb_y, bx, by, avail.top_left);
let mut out = [0i32; 16];
match mode {
Intra4x4Mode::Vertical => p_vertical(&top, &mut out),
Intra4x4Mode::Horizontal => p_horizontal(&left, &mut out),
Intra4x4Mode::Dc => p_dc(&top, &left, avail, &mut out),
Intra4x4Mode::DiagonalDownLeft => p_ddl(&top, &mut out),
Intra4x4Mode::DiagonalDownRight => p_ddr(&top, &left, tl, &mut out),
Intra4x4Mode::VerticalRight => p_vr(&top, &left, tl, &mut out),
Intra4x4Mode::HorizontalDown => p_hd(&top, &left, tl, &mut out),
Intra4x4Mode::VerticalLeft => p_vl(&top, &mut out),
Intra4x4Mode::HorizontalUp => p_hu(&left, &mut out),
}
for s in out.iter_mut() {
*s = (*s).clamp(0, 255);
}
Some(out)
}
#[inline]
fn at4(x: usize, y: usize) -> usize {
y * 4 + x
}
fn p_vertical(top: &[i32; 8], out: &mut [i32; 16]) {
for y in 0..4 {
for x in 0..4 {
out[at4(x, y)] = top[x];
}
}
}
fn p_horizontal(left: &[i32; 4], out: &mut [i32; 16]) {
for y in 0..4 {
for x in 0..4 {
out[at4(x, y)] = left[y];
}
}
}
fn p_dc(top: &[i32; 8], left: &[i32; 4], avail: Intra4x4Availability, out: &mut [i32; 16]) {
let dc = if avail.top && avail.left {
(top[0] + top[1] + top[2] + top[3] + left[0] + left[1] + left[2] + left[3] + 4) >> 3
} else if avail.left {
(left[0] + left[1] + left[2] + left[3] + 2) >> 2
} else if avail.top {
(top[0] + top[1] + top[2] + top[3] + 2) >> 2
} else {
128
};
for s in out.iter_mut() {
*s = dc;
}
}
fn p_ddl(top: &[i32; 8], out: &mut [i32; 16]) {
let p = top;
for y in 0..4 {
for x in 0..4 {
let v = if x == 3 && y == 3 {
(p[6] + 3 * p[7] + 2) >> 2
} else {
(p[x + y] + 2 * p[x + y + 1] + p[x + y + 2] + 2) >> 2
};
out[at4(x, y)] = v;
}
}
}
fn p_ddr(top: &[i32; 8], left: &[i32; 4], tl: i32, out: &mut [i32; 16]) {
let p_top = |x: i32| -> i32 {
if x == -1 {
tl
} else {
top[x as usize]
}
};
let p_left = |y: i32| -> i32 {
if y == -1 {
tl
} else {
left[y as usize]
}
};
for y in 0..4i32 {
for x in 0..4i32 {
let v = if x > y {
(p_top(x - y - 2) + 2 * p_top(x - y - 1) + p_top(x - y) + 2) >> 2
} else if x < y {
(p_left(y - x - 2) + 2 * p_left(y - x - 1) + p_left(y - x) + 2) >> 2
} else {
(p_top(0) + 2 * tl + p_left(0) + 2) >> 2
};
out[at4(x as usize, y as usize)] = v;
}
}
}
fn p_vr(top: &[i32; 8], left: &[i32; 4], tl: i32, out: &mut [i32; 16]) {
let p_top = |x: i32| -> i32 {
if x == -1 {
tl
} else {
top[x as usize]
}
};
let p_left = |y: i32| -> i32 {
if y == -1 {
tl
} else {
left[y as usize]
}
};
for y in 0..4i32 {
for x in 0..4i32 {
let z_vr = 2 * x - y;
let v = match z_vr {
0 | 2 | 4 | 6 => {
let xp = x - (y >> 1) - 1;
(p_top(xp) + p_top(xp + 1) + 1) >> 1
}
1 | 3 | 5 => {
let xp = x - (y >> 1) - 2;
(p_top(xp) + 2 * p_top(xp + 1) + p_top(xp + 2) + 2) >> 2
}
-1 => (p_left(0) + 2 * tl + p_top(0) + 2) >> 2,
_ => (p_left(y - 1) + 2 * p_left(y - 2) + p_left(y - 3) + 2) >> 2,
};
out[at4(x as usize, y as usize)] = v;
}
}
}
fn p_hd(top: &[i32; 8], left: &[i32; 4], tl: i32, out: &mut [i32; 16]) {
let p_top = |x: i32| -> i32 {
if x == -1 {
tl
} else {
top[x as usize]
}
};
let p_left = |y: i32| -> i32 {
if y == -1 {
tl
} else {
left[y as usize]
}
};
for y in 0..4i32 {
for x in 0..4i32 {
let z_hd = 2 * y - x;
let v = match z_hd {
0 | 2 | 4 | 6 => {
let yp = y - (x >> 1) - 1;
(p_left(yp) + p_left(yp + 1) + 1) >> 1
}
1 | 3 | 5 => {
let yp = y - (x >> 1) - 2;
(p_left(yp) + 2 * p_left(yp + 1) + p_left(yp + 2) + 2) >> 2
}
-1 => (p_left(0) + 2 * tl + p_top(0) + 2) >> 2,
_ => (p_top(x - 1) + 2 * p_top(x - 2) + p_top(x - 3) + 2) >> 2,
};
out[at4(x as usize, y as usize)] = v;
}
}
}
fn p_vl(top: &[i32; 8], out: &mut [i32; 16]) {
let p = top;
for y in 0..4i32 {
for x in 0..4i32 {
let v = if y == 0 || y == 2 {
let xp = (x + (y >> 1)) as usize;
(p[xp] + p[xp + 1] + 1) >> 1
} else {
let xp = (x + (y >> 1)) as usize;
(p[xp] + 2 * p[xp + 1] + p[xp + 2] + 2) >> 2
};
out[at4(x as usize, y as usize)] = v;
}
}
}
fn p_hu(left: &[i32; 4], out: &mut [i32; 16]) {
let l = left;
for y in 0..4i32 {
for x in 0..4i32 {
let z_hu = x + 2 * y;
let v = match z_hu {
0 | 2 | 4 => {
let yp = (y + (x >> 1)) as usize;
(l[yp] + l[yp + 1] + 1) >> 1
}
1 | 3 => {
let yp = (y + (x >> 1)) as usize;
let p2 = if yp + 2 < 4 { l[yp + 2] } else { l[3] };
let p1 = if yp + 1 < 4 { l[yp + 1] } else { l[3] };
(l[yp] + 2 * p1 + p2 + 2) >> 2
}
5 => (l[2] + 3 * l[3] + 2) >> 2,
_ => l[3],
};
out[at4(x as usize, y as usize)] = v;
}
}
}
pub fn sad_4x4(src: &[u8], src_w: usize, src_x: usize, src_y: usize, pred: &[i32; 16]) -> u64 {
let mut s = 0u64;
for j in 0..4 {
for i in 0..4 {
let p = pred[j * 4 + i];
let q = src[(src_y + j) * src_w + (src_x + i)] as i32;
s += (p - q).unsigned_abs() as u64;
}
}
s
}
#[cfg(test)]
mod tests {
use super::*;
fn flat(w: usize, h: usize, val: u8) -> Vec<u8> {
vec![val; w * h]
}
#[test]
fn dc_no_neighbours_returns_128() {
let recon = flat(64, 64, 200);
let avail = Intra4x4Availability::default();
let out = predict_4x4(Intra4x4Mode::Dc, &recon, 64, 0, 0, 0, 0, avail).unwrap();
assert!(out.iter().all(|&v| v == 128));
}
#[test]
fn vertical_unavailable_at_top_edge() {
let recon = flat(64, 64, 200);
let avail = availability(0, 0, 0, 0, 4);
assert!(predict_4x4(Intra4x4Mode::Vertical, &recon, 64, 0, 0, 0, 0, avail).is_none());
assert!(predict_4x4(Intra4x4Mode::Horizontal, &recon, 64, 0, 0, 0, 0, avail).is_none());
assert!(predict_4x4(
Intra4x4Mode::DiagonalDownRight,
&recon,
64,
0,
0,
0,
0,
avail
)
.is_none());
assert!(predict_4x4(Intra4x4Mode::Dc, &recon, 64, 0, 0, 0, 0, avail).is_some());
}
#[test]
fn vertical_copies_top_row() {
let mut recon = vec![0u8; 64 * 64];
for x in 0..4 {
recon[15 * 64 + x] = ((x + 1) * 10) as u8;
}
for x in 4..8 {
recon[15 * 64 + x] = ((x + 1) * 10) as u8;
}
let avail = availability(0, 1, 0, 0, 4);
let out = predict_4x4(Intra4x4Mode::Vertical, &recon, 64, 0, 1, 0, 0, avail).unwrap();
for y in 0..4 {
for x in 0..4 {
assert_eq!(out[at4(x, y)], ((x + 1) * 10) as i32);
}
}
}
#[test]
fn horizontal_copies_left_col() {
let mut recon = vec![0u8; 64 * 64];
for y in 0..4 {
recon[y * 64 + 15] = ((y + 1) * 7) as u8;
}
let avail = availability(1, 0, 0, 0, 4);
let out = predict_4x4(Intra4x4Mode::Horizontal, &recon, 64, 1, 0, 0, 0, avail).unwrap();
for y in 0..4 {
for x in 0..4 {
assert_eq!(out[at4(x, y)], ((y + 1) * 7) as i32);
}
}
}
#[test]
fn sad_zero_when_pred_matches_src() {
let mut src = vec![0u8; 64 * 64];
for j in 0..4 {
for i in 0..4 {
src[j * 64 + i] = (j * 10 + i) as u8;
}
}
let mut pred = [0i32; 16];
for j in 0..4 {
for i in 0..4 {
pred[at4(i, j)] = (j * 10 + i) as i32;
}
}
assert_eq!(sad_4x4(&src, 64, 0, 0, &pred), 0);
}
}