//! Prediction processes, mirrored from the spec (7.11.2 intra prediction for the non-directional //! modes plus V_PRED / H_PRED without edge filtering, 7.11.3 block inter prediction without //! reference scaling) so that the encoder predicts exactly like a decoder. use crate::tables::*; use crate::transform::round2; /// A plane buffer (padded to whole superblocks). #[derive(Clone)] pub(crate) struct Plane { pub data: Vec, pub stride: usize, } impl Plane { pub fn new(w: usize, h: usize) -> Plane { Plane { data: vec![0; w * h], stride: w } } #[inline(always)] pub fn at(&self, x: usize, y: usize) -> u16 { self.data[y * self.stride + x] } #[inline(always)] pub fn row(&self, y: usize) -> &[u16] { &self.data[y * self.stride..(y + 1) * self.stride] } } /// The intra modes this encoder uses (values are the spec's y / uv mode numbers). pub(crate) const INTRA_MODES_USED: [usize; 7] = [DC_PRED, V_PRED, H_PRED, PAETH_PRED, SMOOTH_PRED, SMOOTH_V_PRED, SMOOTH_H_PRED]; /// Inputs of the intra prediction process. pub(crate) struct IntraEdge { pub x: usize, pub y: usize, pub have_left: bool, pub have_above: bool, pub have_above_right: bool, pub have_below_left: bool, pub log2: u32, /// ((MiCols * MI_SIZE) >> subX) - 1 and the same for rows. pub max_x: i32, pub max_y: i32, pub bit_depth: u32, } /// Edge samples: `above[0..2n]`, `left[0..2n]` and the top-left corner. pub(crate) struct Edges { above: [i32; 64], left: [i32; 64], corner: i32, } pub(crate) fn edges(pl: &Plane, e: &IntraEdge) -> Edges { let w = 1usize << e.log2; let h = w; let bd = e.bit_depth; let (x, y) = (e.x, e.y); let n = w + h; let mut above = [0i32; 64]; let mut left = [0i32; 64]; if !e.have_above && e.have_left { let v = pl.at(x - 1, y) as i32; above[..n].iter_mut().for_each(|a| *a = v); } else if !e.have_above && !e.have_left { let v = (1 << (bd - 1)) - 1; above[..n].iter_mut().for_each(|a| *a = v); } else { let above_limit = e.max_x.min(x as i32 + if e.have_above_right { 2 * w as i32 } else { w as i32 } - 1); let row = pl.row(y - 1); for (i, a) in above[..n].iter_mut().enumerate() { *a = row[above_limit.min(x as i32 + i as i32) as usize] as i32; } } if !e.have_left && e.have_above { let v = pl.at(x, y - 1) as i32; left[..n].iter_mut().for_each(|a| *a = v); } else if !e.have_left && !e.have_above { let v = (1 << (bd - 1)) + 1; left[..n].iter_mut().for_each(|a| *a = v); } else { let left_limit = e.max_y.min(y as i32 + if e.have_below_left { 2 * h as i32 } else { h as i32 } - 1); for (i, l) in left[..n].iter_mut().enumerate() { *l = pl.at(x - 1, left_limit.min(y as i32 + i as i32) as usize) as i32; } } let corner = if e.have_above && e.have_left { pl.at(x - 1, y - 1) as i32 } else if e.have_above { pl.at(x, y - 1) as i32 } else if e.have_left { pl.at(x - 1, y) as i32 } else { 1 << (bd - 1) }; Edges { above, left, corner } } fn sm_weights(log2: u32) -> &'static [u8] { match log2 { 2 => &SM_WEIGHTS_TX_4X4, 3 => &SM_WEIGHTS_TX_8X8, 4 => &SM_WEIGHTS_TX_16X16, _ => &SM_WEIGHTS_TX_32X32, } } /// Predicts a square `2^log2` block with `mode` into `pred` (row stride = block width). pub(crate) fn predict_intra(ed: &Edges, e: &IntraEdge, mode: usize, pred: &mut [u16]) { let log2 = e.log2; let w = 1usize << log2; let h = w; let bd = e.bit_depth; let (above, left) = (&ed.above, &ed.left); match mode { DC_PRED => { let v = if e.have_left && e.have_above { let sum: i32 = left[..h].iter().sum::() + above[..w].iter().sum::() + ((w + h) >> 1) as i32; sum / (w + h) as i32 } else if e.have_left { (left[..h].iter().sum::() + (h >> 1) as i32) >> log2 } else if e.have_above { (above[..w].iter().sum::() + (w >> 1) as i32) >> log2 } else { 1 << (bd - 1) }; let v = v.clamp(0, (1 << bd) - 1) as u16; pred[..w * h].iter_mut().for_each(|p| *p = v); } V_PRED => { for i in 0..h { for j in 0..w { pred[i * w + j] = above[j] as u16; } } } H_PRED => { for i in 0..h { pred[i * w..i * w + w].iter_mut().for_each(|p| *p = left[i] as u16); } } PAETH_PRED => { let tl = ed.corner; for i in 0..h { for j in 0..w { let a = above[j]; let l = left[i]; let base = a + l - tl; let p_left = (base - l).abs(); let p_top = (base - a).abs(); let p_tl = (base - tl).abs(); pred[i * w + j] = if p_left <= p_top && p_left <= p_tl { l as u16 } else if p_top <= p_tl { a as u16 } else { tl as u16 }; } } } _ => { let wx = sm_weights(log2); let wy = wx; for i in 0..h { for j in 0..w { let v = if mode == SMOOTH_PRED { let s = wy[i] as i32 * above[j] + (256 - wy[i] as i32) * left[h - 1] + wx[j] as i32 * left[i] + (256 - wx[j] as i32) * above[w - 1]; round2(s, 9) } else if mode == SMOOTH_V_PRED { round2(wy[i] as i32 * above[j] + (256 - wy[i] as i32) * left[h - 1], 8) } else { round2(wx[j] as i32 * left[i] + (256 - wx[j] as i32) * above[w - 1], 8) }; pred[i * w + j] = v as u16; } } } } } /// Block inter prediction (7.11.3.3 motion vector scaling without scaling, 7.11.3.4 block /// inter prediction with the EIGHTTAP filter, single reference so InterRound1 = 11 and the /// final rounding is the identity) of the `w x h` block at plane position (x, y) displaced by /// `mv` (1/8 luma sample units, [row, col]). `last_x` / `last_y` are the largest sample /// coordinates of the reference plane; the result goes to `pred` (row stride `w`). pub(crate) fn predict_inter( refp: &Plane, last_x: i32, last_y: i32, x: usize, y: usize, w: usize, h: usize, mv: [i32; 2], ss: usize, bit_depth: u32, pred: &mut [u16], ) { // positions in 1/16 sample units ((startX >> 6) & 15 is the filter phase) let px = ((x as i32) << 4) + ((2 * mv[1]) >> ss); let py = ((y as i32) << 4) + ((2 * mv[0]) >> ss); let (fx, fy) = ((px & 15) as usize, (py & 15) as usize); let (ix, iy) = (px >> 4, py >> 4); let max = (1i32 << bit_depth) - 1; if fx == 0 && fy == 0 { // the filters' phase-0 kernel is a unit impulse: a clamped copy for r in 0..h { let ry = (iy + r as i32).clamp(0, last_y) as usize; let row = refp.row(ry); let out = &mut pred[r * w..r * w + w]; let x0 = ix; if x0 >= 0 && x0 + w as i32 - 1 <= last_x { out.copy_from_slice(&row[x0 as usize..x0 as usize + w]); } else { for (c, o) in out.iter_mut().enumerate() { *o = row[(x0 + c as i32).clamp(0, last_x) as usize]; } } } return; } let filt_h = if w <= 4 { 4 } else { EIGHTTAP }; let filt_v = if h <= 4 { 4 } else { EIGHTTAP }; let kh = &SUBPEL_FILTERS[filt_h][fx]; let kv = &SUBPEL_FILTERS[filt_v][fy]; let bx = ix - 3; let x_inside = bx >= 0 && bx + w as i32 + 7 <= last_x + 1; // horizontal pass of one reference row into `out` (Round2( sum, InterRound0 = 3 )) let hfilter = |ry: usize, out: &mut [i32]| { let row = refp.row(ry); if x_inside { let seg = &row[bx as usize..bx as usize + w + 7]; for (c, o) in out.iter_mut().enumerate() { let mut s = 0i32; for t in 0..8 { s += kh[t] as i32 * seg[c + t] as i32; } *o = round2(s, 3); } } else { for (c, o) in out.iter_mut().enumerate() { let mut s = 0i32; for t in 0..8 { s += kh[t] as i32 * row[(bx + c as i32 + t as i32).clamp(0, last_x) as usize] as i32; } *o = round2(s, 3); } } }; let mut inter = [0i32; (64 + 7) * 64]; if fy == 0 { // the vertical kernel is 128 at the centre: Round2( 128 * v, 11 ) = Round2( v, 4 ) for r in 0..h { let ry = (iy + r as i32).clamp(0, last_y) as usize; hfilter(ry, &mut inter[..w]); for (p, &v) in pred[r * w..r * w + w].iter_mut().zip(&inter[..w]) { *p = round2(v, 4).clamp(0, max) as u16; } } return; } if fx == 0 { // the horizontal pass is exactly 16 * sample: Round2( 16 * sum, 11 ) = Round2( sum, 7 ) for r in 0..h { let rows: [&[u16]; 8] = std::array::from_fn(|t| refp.row((iy + r as i32 + t as i32 - 3).clamp(0, last_y) as usize)); for c in 0..w { let xx = if x_inside { (ix + c as i32) as usize } else { (ix + c as i32).clamp(0, last_x) as usize }; let mut s = 0i32; for t in 0..8 { s += kv[t] as i32 * rows[t][xx] as i32; } pred[r * w + c] = round2(s, 7).clamp(0, max) as u16; } } return; } let inter_h = h + 7; for r in 0..inter_h { let ry = (iy + r as i32 - 3).clamp(0, last_y) as usize; hfilter(ry, &mut inter[r * w..r * w + w]); } for r in 0..h { for c in 0..w { let mut s = 0i32; for t in 0..8 { s += kv[t] as i32 * inter[(r + t) * w + c]; } pred[r * w + c] = round2(s, 11).clamp(0, max) as u16; } } } /// The visible `w x h` area of a reference plane interpolated at the sub-sample phase /// (`fx`, `fy`) (1/16 units) with the EIGHTTAP filter, as `predict_inter` computes it for /// blocks wider and taller than 4. Used for motion search. pub(crate) fn subpel_plane(refp: &Plane, w: usize, h: usize, fx: usize, fy: usize, bit_depth: u32) -> Vec { let kh = &SUBPEL_FILTERS[EIGHTTAP][fx]; let kv = &SUBPEL_FILTERS[EIGHTTAP][fy]; let max = (1i32 << bit_depth) - 1; let (lx, ly) = (w as i32 - 1, h as i32 - 1); let mut inter = vec![0i32; w * h]; for y in 0..h { let row = refp.row(y); for x in 0..w { let mut s = 0i32; for t in 0..8 { s += kh[t] as i32 * row[(x as i32 + t as i32 - 3).clamp(0, lx) as usize] as i32; } inter[y * w + x] = round2(s, 3); } } let mut out = vec![0u16; w * h]; for y in 0..h { for x in 0..w { let mut s = 0i32; for t in 0..8 { s += kv[t] as i32 * inter[(y as i32 + t as i32 - 3).clamp(0, ly) as usize * w + x]; } out[y * w + x] = round2(s, 11).clamp(0, max) as u16; } } out } #[cfg(test)] mod tests { use super::*; /// Whole-plane interpolation equals block inter prediction at the same phase. #[test] fn subpel_plane_matches_block_prediction() { let (w, h) = (37, 29); let mut p = Plane::new(64, 64); for (i, v) in p.data.iter_mut().enumerate() { *v = ((i * 7919) % 256) as u16; } for (fx, fy) in [(8, 0), (0, 8), (8, 8), (4, 12)] { let plane = subpel_plane(&p, w, h, fx, fy, 8); for (bx, by) in [(0i32, 0i32), (8, 8), (24, 16), (-5, 20), (30, -3)] { let mut pred = vec![0u16; 64]; let mv = [by * 8 + fy as i32 / 2, bx * 8 + fx as i32 / 2]; predict_inter(&p, w as i32 - 1, h as i32 - 1, 0, 0, 8, 8, mv, 0, 8, &mut pred); for r in 0..8 { for c in 0..8 { let (x, y) = ((bx + c).clamp(0, w as i32 - 1), (by + r).clamp(0, h as i32 - 1)); if bx + c == x && by + r == y { assert_eq!(pred[(r * 8 + c) as usize], plane[y as usize * w + x as usize], "phase ({fx}, {fy}) at ({}, {})", bx + c, by + r); } } } } } } #[test] fn phase_zero_filters_are_impulses() { for f in SUBPEL_FILTERS.iter() { assert_eq!(f[0], [0, 0, 0, 128, 0, 0, 0, 0]); } } }