#![forbid(unsafe_code)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "sample values are 8-bit (0..=255); intermediate i32 arithmetic below is \
explicitly clamped to that range before the final cast"
)]
use super::error::H264Error;
fn rounded_average(values: &[u8], shift: u32) -> u8 {
let sum: u32 = values.iter().map(|&v| u32::from(v)).sum();
let round = 1u32 << (shift - 1);
((sum + round) >> shift) as u8
}
pub(super) fn predict_16x16(
mode: u8,
top: Option<&[u8; 16]>,
left: Option<&[u8; 16]>,
corner: Option<u8>,
) -> Result<[u8; 256], H264Error> {
match mode {
0 => {
let top = top.ok_or(H264Error::UnavailableIntraNeighbor)?;
let mut out = [0u8; 256];
for y in 0..16 {
out[y * 16..y * 16 + 16].copy_from_slice(top);
}
Ok(out)
}
1 => {
let left = left.ok_or(H264Error::UnavailableIntraNeighbor)?;
let mut out = [0u8; 256];
for (y, &sample) in left.iter().enumerate() {
out[y * 16..y * 16 + 16].fill(sample);
}
Ok(out)
}
2 => Ok([luma_dc(top, left); 256]),
3 => {
let top = top.ok_or(H264Error::UnavailableIntraNeighbor)?;
let left = left.ok_or(H264Error::UnavailableIntraNeighbor)?;
let corner = corner.ok_or(H264Error::UnavailableIntraNeighbor)?;
Ok(luma_plane(top, left, corner))
}
_ => Err(H264Error::InvalidMbType),
}
}
fn luma_dc(top: Option<&[u8; 16]>, left: Option<&[u8; 16]>) -> u8 {
match (top, left) {
(Some(t), Some(l)) => {
let sum: u32 = t.iter().chain(l.iter()).map(|&v| u32::from(v)).sum();
((sum + 16) >> 5) as u8
}
(Some(t), None) => rounded_average(t, 4),
(None, Some(l)) => rounded_average(l, 4),
(None, None) => 128,
}
}
#[allow(
clippy::many_single_char_names,
reason = "these names (a, b, c, H, V) are the spec's own variable names for this formula"
)]
fn luma_plane(top: &[u8; 16], left: &[u8; 16], corner: u8) -> [u8; 256] {
let t = |i: i32| i32::from(top[i as usize]);
let l = |i: i32| i32::from(left[i as usize]);
let lt = i32::from(corner);
let mut h = 0i32;
let mut v = 0i32;
for xp in 0..8i32 {
let below = if xp <= 6 { t(6 - xp) } else { lt };
h += (xp + 1) * (t(8 + xp) - below);
}
for yp in 0..8i32 {
let below = if yp <= 6 { l(6 - yp) } else { lt };
v += (yp + 1) * (l(8 + yp) - below);
}
let b = (5 * h + 32) >> 6;
let c = (5 * v + 32) >> 6;
let a = 16 * (l(15) + t(15));
let mut out = [0u8; 256];
for y in 0..16i32 {
for x in 0..16i32 {
let value = (a + b * (x - 7) + c * (y - 7) + 16) >> 5;
out[(y * 16 + x) as usize] = value.clamp(0, 255) as u8;
}
}
out
}
pub(super) fn predict_chroma_8x8(
mode: u8,
top: Option<&[u8; 8]>,
left: Option<&[u8; 8]>,
corner: Option<u8>,
) -> Result<[u8; 64], H264Error> {
match mode {
0 => Ok(chroma_dc(top, left)),
1 => {
let left = left.ok_or(H264Error::UnavailableIntraNeighbor)?;
let mut out = [0u8; 64];
for (y, &sample) in left.iter().enumerate() {
out[y * 8..y * 8 + 8].fill(sample);
}
Ok(out)
}
2 => {
let top = top.ok_or(H264Error::UnavailableIntraNeighbor)?;
let mut out = [0u8; 64];
for y in 0..8 {
out[y * 8..y * 8 + 8].copy_from_slice(top);
}
Ok(out)
}
3 => {
let top = top.ok_or(H264Error::UnavailableIntraNeighbor)?;
let left = left.ok_or(H264Error::UnavailableIntraNeighbor)?;
let corner = corner.ok_or(H264Error::UnavailableIntraNeighbor)?;
Ok(chroma_plane(top, left, corner))
}
_ => Err(H264Error::InvalidMbType),
}
}
fn chroma_dc(top: Option<&[u8; 8]>, left: Option<&[u8; 8]>) -> [u8; 64] {
let top_left = &top.map(|t| &t[0..4]);
let top_right = &top.map(|t| &t[4..8]);
let left_top = &left.map(|l| &l[0..4]);
let left_bottom = &left.map(|l| &l[4..8]);
let tl = combine_or_fallback(*top_left, *left_top);
let tr = single_or_fallback(*top_right, *left_top);
let bl = single_or_fallback(*left_bottom, *top_left);
let br = combine_or_fallback(*top_right, *left_bottom);
let mut out = [0u8; 64];
for y in 0..8 {
for x in 0..8 {
out[y * 8 + x] = match (x < 4, y < 4) {
(true, true) => tl,
(false, true) => tr,
(true, false) => bl,
(false, false) => br,
};
}
}
out
}
fn combine_or_fallback(primary: Option<&[u8]>, secondary: Option<&[u8]>) -> u8 {
match (primary, secondary) {
(Some(p), Some(s)) => {
let sum: u32 = p.iter().chain(s.iter()).map(|&v| u32::from(v)).sum();
((sum + 4) >> 3) as u8
}
(Some(p), None) => rounded_average(p, 2),
(None, Some(s)) => rounded_average(s, 2),
(None, None) => 128,
}
}
fn single_or_fallback(primary: Option<&[u8]>, secondary: Option<&[u8]>) -> u8 {
primary
.or(secondary)
.map_or(128, |samples| rounded_average(samples, 2))
}
#[allow(
clippy::many_single_char_names,
clippy::trivially_copy_pass_by_ref,
reason = "these names (a, b, c, H, V) are the spec's own variable names for this \
formula; top/left are exactly at clippy's by-value threshold (8 bytes) and \
every call site already holds them as references (from `Option<&[u8; 8]>`)"
)]
fn chroma_plane(top: &[u8; 8], left: &[u8; 8], corner: u8) -> [u8; 64] {
let t = |i: i32| i32::from(top[i as usize]);
let l = |i: i32| i32::from(left[i as usize]);
let lt = i32::from(corner);
let mut h = 0i32;
let mut v = 0i32;
for xp in 0..4i32 {
let below = if xp <= 2 { t(2 - xp) } else { lt };
h += (xp + 1) * (t(4 + xp) - below);
}
for yp in 0..4i32 {
let below = if yp <= 2 { l(2 - yp) } else { lt };
v += (yp + 1) * (l(4 + yp) - below);
}
let b = (17 * h + 16) >> 5;
let c = (17 * v + 16) >> 5;
let a = 16 * (l(7) + t(7));
let mut out = [0u8; 64];
for y in 0..8i32 {
for x in 0..8i32 {
let value = (a + b * (x - 3) + c * (y - 3) + 16) >> 5;
out[(y * 8 + x) as usize] = value.clamp(0, 255) as u8;
}
}
out
}
#[cfg(test)]
#[path = "intra_pred_tests.rs"]
mod tests;