use crate::pixel::Pixel;
pub fn default_ref_sample(bit_depth: u8) -> u8 {
1u8 << (bit_depth - 1)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReferenceAvailability {
pub up_left: bool,
pub up: bool,
pub up_right: bool,
pub left: bool,
pub bottom_left: bool,
}
#[allow(clippy::too_many_arguments)]
pub fn build_reference_samples<P: Pixel>(
plane: &[P],
stride: usize,
pic_w: usize,
pic_h: usize,
x0: usize,
y0: usize,
log2_size: u8,
bit_depth: u8,
avail: ReferenceAvailability,
) -> (Vec<P>, Vec<P>) {
let size = 1usize << log2_size;
let len = 2 * size + 1;
let mut top = vec![P::zero(); len];
let mut left = vec![P::zero(); len];
let mut top_avail = vec![false; len];
let mut left_avail = vec![false; len];
if avail.up_left && x0 > 0 && y0 > 0 {
let v = plane[(y0 - 1) * stride + (x0 - 1)];
top[0] = v;
left[0] = v;
top_avail[0] = true;
left_avail[0] = true;
}
if avail.up && y0 > 0 {
for i in 0..size {
top[1 + i] = plane[(y0 - 1) * stride + (x0 + i)];
top_avail[1 + i] = true;
}
}
if avail.up_right && y0 > 0 {
let avail_x = pic_w.saturating_sub(x0 + size);
let count = avail_x.min(size);
for i in 0..count {
top[1 + size + i] = plane[(y0 - 1) * stride + (x0 + size + i)];
top_avail[1 + size + i] = true;
}
}
if avail.left && x0 > 0 {
for i in 0..size {
left[1 + i] = plane[(y0 + i) * stride + (x0 - 1)];
left_avail[1 + i] = true;
}
}
if avail.bottom_left && x0 > 0 {
let avail_y = pic_h.saturating_sub(y0 + size);
let count = avail_y.min(size);
for i in 0..count {
left[1 + size + i] = plane[(y0 + size + i) * stride + (x0 - 1)];
left_avail[1 + size + i] = true;
}
}
let any_top_avail = top_avail.iter().any(|&a| a);
let any_left_avail = left_avail.iter().any(|&a| a);
if !any_top_avail && !any_left_avail {
let fill = P::from_i32_clamped(crate::pixel::default_ref_sample(bit_depth), bit_depth);
for v in top.iter_mut() {
*v = fill;
}
for v in left.iter_mut() {
*v = fill;
}
return (top, left);
}
let total = 4 * size + 1;
let mut ref_array = vec![P::zero(); total];
let mut ref_avail = vec![false; total];
for i in 0..2 * size {
ref_array[i] = left[2 * size - i];
ref_avail[i] = left_avail[2 * size - i];
}
ref_array[2 * size] = top[0];
ref_avail[2 * size] = top_avail[0];
for i in 0..2 * size {
ref_array[2 * size + 1 + i] = top[1 + i];
ref_avail[2 * size + 1 + i] = top_avail[1 + i];
}
let first = ref_avail.iter().position(|&a| a).unwrap();
let first_val = ref_array[first];
for v in ref_array.iter_mut().take(first) {
*v = first_val;
}
for a in ref_avail.iter_mut().take(first) {
*a = true;
}
for i in (first + 1)..total {
if !ref_avail[i] {
ref_array[i] = ref_array[i - 1];
ref_avail[i] = true;
}
}
for i in 0..2 * size {
left[2 * size - i] = ref_array[i];
}
top[0] = ref_array[2 * size];
left[0] = ref_array[2 * size];
for i in 0..2 * size {
top[1 + i] = ref_array[2 * size + 1 + i];
}
(top, left)
}
pub fn predict_planar<P: Pixel>(
dst: &mut [P],
dst_stride: usize,
top: &[P],
left: &[P],
log2_size: u8,
bit_depth: u8,
) {
let size = 1usize << log2_size;
let top_p = &top[1..];
let left_p = &left[1..];
let shift = log2_size as u32 + 1;
for y in 0..size {
for x in 0..size {
let pred = (size - 1 - x) as i32 * left_p[y].to_i32()
+ (x + 1) as i32 * top_p[size].to_i32()
+ (size - 1 - y) as i32 * top_p[x].to_i32()
+ (y + 1) as i32 * left_p[size].to_i32()
+ size as i32;
dst[y * dst_stride + x] = P::from_i32_clamped(pred >> shift, bit_depth);
}
}
}
pub fn predict_dc<P: Pixel>(
dst: &mut [P],
dst_stride: usize,
top: &[P],
left: &[P],
log2_size: u8,
apply_luma_filter: bool,
bit_depth: u8,
) {
let size = 1usize << log2_size;
let top_p = &top[1..];
let left_p = &left[1..];
let mut dc_sum: i32 = size as i32;
for i in 0..size {
dc_sum += left_p[i].to_i32() + top_p[i].to_i32();
}
let dc_val = dc_sum >> (log2_size as u32 + 1);
let dc = P::from_i32_clamped(dc_val, bit_depth);
for y in 0..size {
for x in 0..size {
dst[y * dst_stride + x] = dc;
}
}
if apply_luma_filter && size < 32 {
dst[0] = P::from_i32_clamped(
((left_p[0].to_i32()) + 2 * dc_val + (top_p[0].to_i32()) + 2) >> 2,
bit_depth,
);
for x in 1..size {
dst[x] = P::from_i32_clamped(((top_p[x].to_i32()) + 3 * dc_val + 2) >> 2, bit_depth);
}
for y in 1..size {
dst[y * dst_stride] =
P::from_i32_clamped(((left_p[y].to_i32()) + 3 * dc_val + 2) >> 2, bit_depth);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn predict_angular<P: Pixel>(
dst: &mut [P],
dst_stride: usize,
top: &[P],
left: &[P],
log2_size: u8,
mode: u8,
c_idx: u8,
bit_depth: u8,
) {
debug_assert!((2..=34).contains(&mode));
let size = 1usize << log2_size;
static INTRA_PRED_ANGLE: [i32; 33] = [
32, 26, 21, 17, 13, 9, 5, 2, 0, -2, -5, -9, -13, -17, -21, -26, -32, -26, -21, -17, -13,
-9, -5, -2, 0, 2, 5, 9, 13, 17, 21, 26, 32,
];
static INV_ANGLE: [i32; 15] = [
-4096, -1638, -910, -630, -482, -390, -315, -256, -315, -390, -482, -630, -910, -1638,
-4096,
];
let angle = INTRA_PRED_ANGLE[(mode - 2) as usize];
let last = ((size as i32) * angle) >> 5;
let top_p = &top[1..];
let left_p = &left[1..];
let corner = top[0];
if mode >= 18 {
let mut ref_buf = vec![P::zero(); 3 * size + 4];
let ref_origin = size;
ref_buf[ref_origin] = corner;
for i in 0..2 * size {
ref_buf[ref_origin + 1 + i] = top_p[i];
}
if angle < 0 && last < -1 {
for x in last..=-1 {
let left_idx = -1 + ((x * INV_ANGLE[(mode - 11) as usize] + 128) >> 8);
ref_buf[(ref_origin as i32 + x) as usize] = left_p[left_idx as usize];
}
}
for y in 0..size {
let idx = (((y + 1) as i32) * angle) >> 5;
let fact = (((y + 1) as i32) * angle) & 31;
if fact != 0 {
for x in 0..size {
let ri = (ref_origin as i32 + x as i32 + idx + 1) as usize;
dst[y * dst_stride + x] = P::from_i32_clamped(
((32 - fact) * ref_buf[ri].to_i32() + fact * ref_buf[ri + 1].to_i32() + 16)
>> 5,
bit_depth,
);
}
} else {
for x in 0..size {
let ri = (ref_origin as i32 + x as i32 + idx + 1) as usize;
dst[y * dst_stride + x] = ref_buf[ri];
}
}
}
if mode == 26 && c_idx == 0 && size < 32 {
for y in 0..size {
let val = top_p[0].to_i32() + ((left_p[y].to_i32() - corner.to_i32()) >> 1);
dst[y * dst_stride] = P::from_i32_clamped(val, bit_depth);
}
}
} else {
let mut ref_buf = vec![P::zero(); 3 * size + 4];
let ref_origin = size;
ref_buf[ref_origin] = corner;
for i in 0..2 * size {
ref_buf[ref_origin + 1 + i] = left_p[i];
}
if angle < 0 && last < -1 {
for x in last..=-1 {
let top_idx = -1 + ((x * INV_ANGLE[(mode - 11) as usize] + 128) >> 8);
ref_buf[(ref_origin as i32 + x) as usize] = top_p[top_idx as usize];
}
}
for x in 0..size {
let idx = (((x + 1) as i32) * angle) >> 5;
let fact = (((x + 1) as i32) * angle) & 31;
if fact != 0 {
for y in 0..size {
let ri = (ref_origin as i32 + y as i32 + idx + 1) as usize;
dst[y * dst_stride + x] = P::from_i32_clamped(
((32 - fact) * ref_buf[ri].to_i32() + fact * ref_buf[ri + 1].to_i32() + 16)
>> 5,
bit_depth,
);
}
} else {
for y in 0..size {
let ri = (ref_origin as i32 + y as i32 + idx + 1) as usize;
dst[y * dst_stride + x] = ref_buf[ri];
}
}
}
if mode == 10 && c_idx == 0 && size < 32 {
for x in 0..size {
let val = left_p[0].to_i32() + ((top_p[x].to_i32() - corner.to_i32()) >> 1);
dst[x] = P::from_i32_clamped(val, bit_depth);
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn filter_reference_samples<P: Pixel>(
top: &mut Vec<P>,
left: &mut Vec<P>,
log2_size: u8,
mode: u8,
strong_intra_smoothing_enabled: bool,
c_idx: u8,
chroma_format_idc: u32,
bit_depth: u8,
) {
let size = 1usize << log2_size;
if mode == 1 || size == 4 {
return;
}
if c_idx != 0 && chroma_format_idc != 3 {
return;
}
static INTRA_HOR_VER_DIST_THRESH: [i32; 3] = [7, 1, 0];
let thresh_idx = (log2_size as usize).saturating_sub(3);
if thresh_idx >= INTRA_HOR_VER_DIST_THRESH.len() {
return; }
let min_dist_vert_hor = ((mode as i32) - 26).abs().min(((mode as i32) - 10).abs());
if min_dist_vert_hor <= INTRA_HOR_VER_DIST_THRESH[thresh_idx] {
return;
}
if strong_intra_smoothing_enabled && c_idx == 0 && log2_size == 5 {
let threshold = 1i32 << (bit_depth as i32 - 5);
let top_smooth =
(top[0].to_i32() + top[2 * size].to_i32() - 2 * top[size].to_i32()).abs() < threshold;
let left_smooth = (left[0].to_i32() + left[2 * size].to_i32() - 2 * left[size].to_i32())
.abs()
< threshold;
if top_smooth && left_smooth {
let mut filtered_top = vec![P::zero(); 2 * size + 1];
filtered_top[0] = top[0]; filtered_top[2 * size] = top[2 * size]; for i in 0..(2 * size - 1) {
filtered_top[i + 1] = P::from_i32_clamped(
((64 - (i + 1) as i32) * top[0].to_i32()
+ (i + 1) as i32 * top[2 * size].to_i32()
+ 32)
>> 6,
bit_depth,
);
}
let left_corner = left[0];
let left_end = left[2 * size];
for i in 0..(2 * size - 1) {
left[i + 1] = P::from_i32_clamped(
((64 - (i + 1) as i32) * left_corner.to_i32()
+ (i + 1) as i32 * left_end.to_i32()
+ 32)
>> 6,
bit_depth,
);
}
*top = filtered_top;
return;
}
}
let mut filtered_top = vec![P::zero(); 2 * size + 1];
let mut filtered_left = vec![P::zero(); 2 * size + 1];
filtered_top[2 * size] = top[2 * size];
filtered_left[2 * size] = left[2 * size];
for k in (1..2 * size).rev() {
filtered_top[k] = P::from_i32_clamped(
(top[k + 1].to_i32() + 2 * top[k].to_i32() + top[k - 1].to_i32() + 2) >> 2,
bit_depth,
);
filtered_left[k] = P::from_i32_clamped(
(left[k + 1].to_i32() + 2 * left[k].to_i32() + left[k - 1].to_i32() + 2) >> 2,
bit_depth,
);
}
let new_corner = P::from_i32_clamped(
(left[1].to_i32() + 2 * left[0].to_i32() + top[1].to_i32() + 2) >> 2,
bit_depth,
);
filtered_top[0] = new_corner;
filtered_left[0] = new_corner;
*top = filtered_top;
*left = filtered_left;
}
pub fn add_residual<P: Pixel>(
dst: &mut [P],
dst_stride: usize,
residual: &[i16],
log2_size: u8,
bit_depth: u8,
) {
let size = 1usize << log2_size;
for y in 0..size {
for x in 0..size {
let pixel = dst[y * dst_stride + x].to_i32() + residual[y * size + x] as i32;
dst[y * dst_stride + x] = P::from_i32_clamped(pixel, bit_depth);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_neighbors_fills_with_midpoint() {
let plane = vec![0u8; 0];
let avail = ReferenceAvailability::default();
let (top, left) = build_reference_samples(&plane, 0, 0, 0, 0, 0, 4, 8, avail);
assert_eq!(top.len(), 33); assert_eq!(left.len(), 33);
assert!(top.iter().all(|&p| p == 128));
assert!(left.iter().all(|&p| p == 128));
}
#[test]
fn test_left_only_substitution() {
let stride = 32;
let plane: Vec<u8> = (0..32 * 16).map(|_| 0x7E).collect();
let avail = ReferenceAvailability {
up_left: false,
up: false,
up_right: false,
left: true,
bottom_left: false,
};
let (top, left) = build_reference_samples(&plane, stride, 32, 16, 16, 0, 4, 8, avail);
for i in 0..16 {
assert_eq!(left[1 + i], 0x7E, "left[{}]", 1 + i);
}
assert_eq!(top[0], 0x7E, "corner");
for i in 0..32 {
assert_eq!(top[1 + i], 0x7E, "top[{}]", 1 + i);
}
for i in 16..32 {
assert_eq!(left[1 + i], 0x7E, "left ext[{}]", 1 + i);
}
}
#[test]
fn test_planar_uniform_neighbors() {
let top = vec![128u8; 33];
let left = vec![128u8; 33];
let mut dst = vec![0u8; 256];
predict_planar(&mut dst, 16, &top, &left, 4, 8);
assert!(dst.iter().all(|&p| p == 128), "first row: {:?}", &dst[..16]);
}
#[test]
fn test_dc_uniform_neighbors_with_filter() {
let top = vec![128u8; 33];
let left = vec![128u8; 33];
let mut dst = vec![0u8; 256];
predict_dc(&mut dst, 16, &top, &left, 4, true, 8);
assert!(dst.iter().all(|&p| p == 128));
}
#[test]
fn test_add_residual_basic() {
let mut dst = vec![128u8; 16];
let mut residual = vec![0i16; 16];
residual[0] = 10;
residual[1] = -200;
residual[2] = 200;
add_residual(&mut dst, 4, &residual, 2, 8);
assert_eq!(dst[0], 138);
assert_eq!(dst[1], 0); assert_eq!(dst[2], 255); assert_eq!(dst[3], 128);
}
#[test]
fn test_fixture_luma_reconstruction() {
let plane = vec![0u8; 0];
let avail = ReferenceAvailability::default();
let (top, left) = build_reference_samples(&plane, 0, 0, 0, 0, 0, 4, 8, avail);
let mut block = vec![0u8; 256];
predict_planar(&mut block, 16, &top, &left, 4, 8);
let residual = vec![-2i16; 256];
add_residual(&mut block, 16, &residual, 4, 8);
assert!(
block.iter().all(|&p| p == 0x7E),
"expected all 0x7E (= 126), got first row: {:?}",
&block[..16]
);
}
#[test]
fn test_fixture_chroma_reconstruction() {
let plane = vec![0u8; 0];
let avail = ReferenceAvailability::default();
let (top, left) = build_reference_samples(&plane, 0, 0, 0, 0, 0, 3, 8, avail);
let mut block = vec![0u8; 64];
predict_planar(&mut block, 8, &top, &left, 3, 8);
assert!(
block.iter().all(|&p| p == 0x80),
"expected all 0x80 (= 128), got first row: {:?}",
&block[..8]
);
}
}