use crate::mb_grid::{MbGrid, MbInfo};
use crate::picture::Picture;
use crate::pps::Pps;
use crate::reconstruct::deblock_picture_full;
#[derive(Debug, Clone, Copy)]
pub struct MbDeblockInfo {
pub is_intra: bool,
pub qp_y: i32,
pub luma_nonzero_4x4: u16,
pub chroma_nonzero_4x4: u16,
pub mv_l0: [(i16, i16); 16],
pub ref_idx_l0: [i8; 4],
pub ref_poc_l0: [i32; 4],
pub mv_l1: [(i16, i16); 16],
pub ref_idx_l1: [i8; 4],
pub ref_poc_l1: [i32; 4],
pub transform_size_8x8_flag: bool,
pub is_intra_4x4: bool,
}
impl Default for MbDeblockInfo {
fn default() -> Self {
Self {
is_intra: false,
qp_y: 0,
luma_nonzero_4x4: 0,
chroma_nonzero_4x4: 0,
mv_l0: [(0, 0); 16],
ref_idx_l0: [-1; 4],
ref_poc_l0: [i32::MIN; 4],
mv_l1: [(0, 0); 16],
ref_idx_l1: [-1; 4],
ref_poc_l1: [i32::MIN; 4],
transform_size_8x8_flag: false,
is_intra_4x4: false,
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn deblock_recon(
width: u32,
height: u32,
chroma_width: u32,
chroma_height: u32,
recon_y: &mut [u8],
recon_u: &mut [u8],
recon_v: &mut [u8],
mb_infos: &[MbDeblockInfo],
chroma_qp_index_offset: i32,
width_mbs: u32,
height_mbs: u32,
) {
deblock_recon_with_chroma_array_type(
width,
height,
chroma_width,
chroma_height,
recon_y,
recon_u,
recon_v,
mb_infos,
chroma_qp_index_offset,
width_mbs,
height_mbs,
1,
)
}
#[allow(clippy::too_many_arguments)]
pub fn deblock_recon_with_chroma_array_type(
width: u32,
height: u32,
chroma_width: u32,
chroma_height: u32,
recon_y: &mut [u8],
recon_u: &mut [u8],
recon_v: &mut [u8],
mb_infos: &[MbDeblockInfo],
chroma_qp_index_offset: i32,
width_mbs: u32,
height_mbs: u32,
chroma_array_type: u32,
) {
deblock_recon_inner(
width,
height,
chroma_width,
chroma_height,
recon_y,
recon_u,
recon_v,
mb_infos,
chroma_qp_index_offset,
width_mbs,
height_mbs,
chroma_array_type,
false,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub fn deblock_recon_field(
width: u32,
height: u32,
chroma_width: u32,
chroma_height: u32,
recon_y: &mut [u8],
recon_u: &mut [u8],
recon_v: &mut [u8],
mb_infos: &[MbDeblockInfo],
chroma_qp_index_offset: i32,
width_mbs: u32,
height_mbs: u32,
chroma_array_type: u32,
) {
deblock_recon_inner(
width,
height,
chroma_width,
chroma_height,
recon_y,
recon_u,
recon_v,
mb_infos,
chroma_qp_index_offset,
width_mbs,
height_mbs,
chroma_array_type,
true,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub fn deblock_recon_mbaff(
width: u32,
height: u32,
chroma_width: u32,
chroma_height: u32,
recon_y: &mut [u8],
recon_u: &mut [u8],
recon_v: &mut [u8],
mb_infos: &[MbDeblockInfo],
mb_field_flags: &[bool],
chroma_qp_index_offset: i32,
width_mbs: u32,
height_mbs: u32,
chroma_array_type: u32,
) {
debug_assert_eq!(mb_infos.len(), mb_field_flags.len());
deblock_recon_inner(
width,
height,
chroma_width,
chroma_height,
recon_y,
recon_u,
recon_v,
mb_infos,
chroma_qp_index_offset,
width_mbs,
height_mbs,
chroma_array_type,
false,
Some(mb_field_flags),
)
}
#[allow(clippy::too_many_arguments)]
fn deblock_recon_inner(
width: u32,
height: u32,
chroma_width: u32,
chroma_height: u32,
recon_y: &mut [u8],
recon_u: &mut [u8],
recon_v: &mut [u8],
mb_infos: &[MbDeblockInfo],
chroma_qp_index_offset: i32,
width_mbs: u32,
height_mbs: u32,
chroma_array_type: u32,
field_pic: bool,
mbaff_field_flags: Option<&[bool]>,
) {
debug_assert_eq!(recon_y.len(), (width as usize) * (height as usize));
debug_assert_eq!(
recon_u.len(),
(chroma_width as usize) * (chroma_height as usize)
);
debug_assert_eq!(
recon_v.len(),
(chroma_width as usize) * (chroma_height as usize)
);
debug_assert_eq!(mb_infos.len(), (width_mbs as usize) * (height_mbs as usize));
let mut pic = Picture::new(width, height, chroma_array_type, 8, 8);
for (dst, src) in pic.luma.iter_mut().zip(recon_y.iter()) {
*dst = *src as i32;
}
for (dst, src) in pic.cb.iter_mut().zip(recon_u.iter()) {
*dst = *src as i32;
}
for (dst, src) in pic.cr.iter_mut().zip(recon_v.iter()) {
*dst = *src as i32;
}
let mut grid = MbGrid::new(width_mbs, height_mbs);
grid.mbaff_frame_flag = mbaff_field_flags.is_some();
for (i, info) in mb_infos.iter().enumerate() {
let mb = &mut grid.info[i];
mb.mb_field_decoding_flag = mbaff_field_flags.is_some_and(|f| f[i]);
mb.available = true;
mb.is_intra = info.is_intra;
mb.is_intra_nxn = false; mb.is_i_pcm = false;
mb.qp_y = info.qp_y;
mb.luma_nonzero_4x4 = info.luma_nonzero_4x4;
mb.chroma_nonzero_4x4 = info.chroma_nonzero_4x4;
mb.transform_size_8x8_flag = info.transform_size_8x8_flag;
mb.slice_id = 0;
mb.mv_l0 = info.mv_l0;
mb.ref_idx_l0 = info.ref_idx_l0;
mb.ref_poc_l0 = info.ref_poc_l0;
mb.mv_l1 = info.mv_l1;
mb.ref_idx_l1 = info.ref_idx_l1;
mb.ref_poc_l1 = info.ref_poc_l1;
}
let _ = MbInfo::default;
let pps = Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode_flag: false,
bottom_field_pic_order_in_frame_present_flag: false,
num_slice_groups_minus1: 0,
slice_group_map: None,
num_ref_idx_l0_default_active_minus1: 0,
num_ref_idx_l1_default_active_minus1: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
pic_init_qp_minus26: 0,
pic_init_qs_minus26: 0,
chroma_qp_index_offset,
deblocking_filter_control_present_flag: true,
constrained_intra_pred_flag: false,
redundant_pic_cnt_present_flag: false,
extension: None,
};
deblock_picture_full(
&mut pic,
&grid,
0,
0,
8,
8,
&pps,
mbaff_field_flags.is_some(),
field_pic,
mbaff_field_flags.unwrap_or(&[]),
);
for (dst, src) in recon_y.iter_mut().zip(pic.luma.iter()) {
*dst = (*src).clamp(0, 255) as u8;
}
for (dst, src) in recon_u.iter_mut().zip(pic.cb.iter()) {
*dst = (*src).clamp(0, 255) as u8;
}
for (dst, src) in recon_v.iter_mut().zip(pic.cr.iter()) {
*dst = (*src).clamp(0, 255) as u8;
}
}
#[allow(clippy::too_many_arguments)]
pub fn deblock_recon_deep(
width: u32,
height: u32,
recon_y: &mut [i32],
recon_u: &mut [i32],
recon_v: &mut [i32],
mb_infos: &[MbDeblockInfo],
chroma_qp_index_offset: i32,
chroma_array_type: u32,
bit_depth_luma: u32,
bit_depth_chroma: u32,
) {
let width_mbs = width / 16;
let height_mbs = height / 16;
debug_assert_eq!(mb_infos.len(), (width_mbs as usize) * (height_mbs as usize));
let mut pic = Picture::new(
width,
height,
chroma_array_type,
bit_depth_luma,
bit_depth_chroma,
);
debug_assert_eq!(pic.luma.len(), recon_y.len());
debug_assert_eq!(pic.cb.len(), recon_u.len());
pic.luma.copy_from_slice(recon_y);
pic.cb.copy_from_slice(recon_u);
pic.cr.copy_from_slice(recon_v);
let mut grid = MbGrid::new(width_mbs, height_mbs);
for (i, info) in mb_infos.iter().enumerate() {
let mb = &mut grid.info[i];
mb.available = true;
mb.is_intra = info.is_intra;
mb.is_intra_nxn = false;
mb.is_i_pcm = false;
mb.qp_y = info.qp_y;
mb.luma_nonzero_4x4 = info.luma_nonzero_4x4;
mb.chroma_nonzero_4x4 = info.chroma_nonzero_4x4;
mb.transform_size_8x8_flag = info.transform_size_8x8_flag;
mb.slice_id = 0;
mb.mv_l0 = info.mv_l0;
mb.ref_idx_l0 = info.ref_idx_l0;
mb.ref_poc_l0 = info.ref_poc_l0;
mb.mv_l1 = info.mv_l1;
mb.ref_idx_l1 = info.ref_idx_l1;
mb.ref_poc_l1 = info.ref_poc_l1;
}
let pps = Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode_flag: false,
bottom_field_pic_order_in_frame_present_flag: false,
num_slice_groups_minus1: 0,
slice_group_map: None,
num_ref_idx_l0_default_active_minus1: 0,
num_ref_idx_l1_default_active_minus1: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
pic_init_qp_minus26: 0,
pic_init_qs_minus26: 0,
chroma_qp_index_offset,
deblocking_filter_control_present_flag: true,
constrained_intra_pred_flag: false,
redundant_pic_cnt_present_flag: false,
extension: None,
};
deblock_picture_full(
&mut pic,
&grid,
0,
0,
bit_depth_luma,
bit_depth_chroma,
&pps,
false,
false,
&[],
);
recon_y.copy_from_slice(&pic.luma);
recon_u.copy_from_slice(&pic.cb);
recon_v.copy_from_slice(&pic.cr);
}
#[inline]
pub fn luma_nz_mask_from_blocks(blk_has_nz: &[bool; 16]) -> u16 {
let mut m: u16 = 0;
for (i, &nz) in blk_has_nz.iter().enumerate() {
if nz {
m |= 1u16 << i;
}
}
m
}
#[inline]
pub fn chroma_nz_mask_from_blocks(cb_has_nz: &[bool; 4], cr_has_nz: &[bool; 4]) -> u16 {
let mut m: u16 = 0;
for (i, &nz) in cb_has_nz.iter().enumerate() {
if nz {
m |= 1u16 << i;
}
}
for (i, &nz) in cr_has_nz.iter().enumerate() {
if nz {
m |= 1u16 << (8 + i);
}
}
m
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flat_picture_unchanged() {
let mut y = vec![128u8; 32 * 16];
let mut u = vec![128u8; 16 * 8];
let mut v = vec![128u8; 16 * 8];
let mb_infos = vec![
MbDeblockInfo {
is_intra: true,
qp_y: 26,
luma_nonzero_4x4: 0,
chroma_nonzero_4x4: 0,
..Default::default()
},
MbDeblockInfo {
is_intra: true,
qp_y: 26,
luma_nonzero_4x4: 0,
chroma_nonzero_4x4: 0,
..Default::default()
},
];
deblock_recon(32, 16, 16, 8, &mut y, &mut u, &mut v, &mb_infos, 0, 2, 1);
assert!(y.iter().all(|&v| v == 128));
assert!(u.iter().all(|&v| v == 128));
assert!(v.iter().all(|&v| v == 128));
}
#[test]
fn block_boundary_discontinuity_smoothed() {
let mut y = vec![124u8; 32 * 16];
let u = vec![128u8; 16 * 8];
let v = vec![128u8; 16 * 8];
for j in 0..16 {
for i in 16..32 {
y[j * 32 + i] = 132;
}
}
let mut yy = y.clone();
let mut uu = u.clone();
let mut vv = v.clone();
let mb_infos = vec![
MbDeblockInfo {
is_intra: true,
qp_y: 26,
luma_nonzero_4x4: 0,
chroma_nonzero_4x4: 0,
..Default::default()
},
MbDeblockInfo {
is_intra: true,
qp_y: 26,
luma_nonzero_4x4: 0,
chroma_nonzero_4x4: 0,
..Default::default()
},
];
deblock_recon(32, 16, 16, 8, &mut yy, &mut uu, &mut vv, &mb_infos, 0, 2, 1);
let p0 = yy[15];
let q0 = yy[16];
assert!(
p0 != 124 || q0 != 132,
"deblock did not modify the block-boundary discontinuity",
);
assert!(p0 > 124, "p0={p0} did not move up");
assert!(q0 < 132, "q0={q0} did not move down");
}
#[test]
fn luma_nz_mask_packs_correctly() {
let mut nz = [false; 16];
nz[0] = true;
nz[5] = true;
nz[15] = true;
assert_eq!(
luma_nz_mask_from_blocks(&nz),
(1u16 << 0) | (1u16 << 5) | (1u16 << 15),
);
}
#[test]
fn chroma_nz_mask_packs_cb_then_cr() {
let cb = [true, false, false, true];
let cr = [false, true, false, false];
assert_eq!(
chroma_nz_mask_from_blocks(&cb, &cr),
(1u16 << 0) | (1u16 << 3) | (1u16 << 9),
);
}
}