use crate::cdf::{
block_height, block_width, ceil_log2_av1, cfl_alpha_u_ctx, cfl_alpha_v_ctx, comp_group_idx_ctx,
comp_mode_ctx, comp_ref_type_ctx, compound_idx_ctx, compound_mode_ctx, count_refs,
get_palette_color_context, interintra_ctx, interp_filter_ctx, is_directional,
is_samedir_ref_pair, mi_height_log2, mi_width_log2, neg_interleave, palette_color_ctx,
palette_uv_mode_ctx, palette_y_mode_ctx, ref_count_ctx, wedge_bits, CompoundTypeReadout,
FindMvStackResult, InterIntraReadout, InterpolationFilterReadout, PalettePlane,
PartitionWalker, TileCdfContext, BILINEAR, BLOCK_128X128, BLOCK_32X32, BLOCK_64X64, BLOCK_8X8,
BLOCK_SIZES, BLOCK_SIZE_GROUPS, CFL_ALPHABET_SIZE, CFL_JOINT_SIGNS, CLASS0_SIZE,
COMPOUND_AVERAGE, COMPOUND_DIFFWTD, COMPOUND_DISTANCE, COMPOUND_INTRA, COMPOUND_MODES,
COMPOUND_MODE_CONTEXTS, COMPOUND_TYPES, COMPOUND_WEDGE, COMP_INTER_CONTEXTS, DC_PRED,
DELTA_LF_SMALL, DELTA_Q_SMALL, DRL_MODE_CONTEXTS, EIGHTTAP, FRAME_LF_COUNT,
GM_TYPE_TRANSLATION, INTERINTRA_MODES, INTERP_FILTERS, INTERP_FILTER_NONE,
INTRABC_DELAY_PIXELS, INTRA_FILTER_MODES, INTRA_MODES, INTRA_MODE_CONTEXTS, IS_INTER_CONTEXTS,
MAX_ANGLE_DELTA, MAX_REF_MV_STACK_SIZE, MAX_SEGMENTS, MI_SIZE, MODE_GLOBALMV,
MODE_GLOBAL_GLOBALMV, MODE_NEARESTMV, MODE_NEAREST_NEARESTMV, MODE_NEARMV, MODE_NEAR_NEARMV,
MODE_NEAR_NEWMV, MODE_NEWMV, MODE_NEW_NEARMV, MODE_NEW_NEWMV, MOTION_MODES, MOTION_MODE_OBMC,
MOTION_MODE_SIMPLE, MOTION_MODE_WARPED_CAUSAL, MV_CLASSES, MV_CLASS_0, MV_COMPS, MV_CONTEXTS,
MV_INTRABC_CONTEXT, MV_JOINT_HNZVNZ, MV_JOINT_HNZVZ, MV_JOINT_HZVNZ, MV_JOINT_ZERO,
NEW_MV_CONTEXTS, NUM_4X4_BLOCKS_HIGH, NUM_4X4_BLOCKS_WIDE, PALETTE_BLOCK_SIZE_CONTEXTS,
PALETTE_COLORS, REF_MV_CONTEXTS, SEGMENT_ID_CONTEXTS, SEGMENT_ID_PREDICTED_CONTEXTS,
SKIP_CONTEXTS, SKIP_MODE_CONTEXTS, SWITCHABLE, UV_INTRA_MODES_CFL_ALLOWED,
UV_INTRA_MODES_CFL_NOT_ALLOWED, WEDGE_TYPES, ZERO_MV_CONTEXTS,
};
use crate::encoder::symbol_writer::SymbolWriter;
use crate::Error;
pub fn write_skip(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
skip: u8,
ctx: usize,
seg_skip_active: bool,
) -> Result<(), Error> {
if seg_skip_active {
if skip != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if skip > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if ctx >= SKIP_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.skip_cdf(ctx);
writer.write_symbol(skip as u32, cdf)
}
pub fn write_segment_id(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
segment_id: u8,
skip: u8,
pred: u8,
ctx: usize,
last_active_seg_id: u8,
) -> Result<(), Error> {
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(Error::PartitionWalkOutOfRange);
}
let max = last_active_seg_id as u32 + 1;
if segment_id as u32 >= max {
return Err(Error::PartitionWalkOutOfRange);
}
if pred as u32 >= max {
return Err(Error::PartitionWalkOutOfRange);
}
if skip != 0 {
if segment_id != pred {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if ctx >= SEGMENT_ID_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let diff = neg_interleave(segment_id as u32, pred as u32, max);
let cdf = cdfs.segment_id_cdf(ctx);
writer.write_symbol(diff, cdf)
}
#[allow(clippy::too_many_arguments)]
pub fn write_intra_segment_id(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
segment_id: u8,
skip: u8,
pred: u8,
ctx: usize,
segmentation_enabled: bool,
last_active_seg_id: u8,
) -> Result<(), Error> {
if !segmentation_enabled {
if segment_id != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
write_segment_id(
writer,
cdfs,
segment_id,
skip,
pred,
ctx,
last_active_seg_id,
)
}
pub fn write_intra_frame_y_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
abovemode_ctx: usize,
leftmode_ctx: usize,
) -> Result<(), Error> {
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if abovemode_ctx >= INTRA_MODE_CONTEXTS || leftmode_ctx >= INTRA_MODE_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.intra_frame_y_mode_cdf(abovemode_ctx, leftmode_ctx);
writer.write_symbol(y_mode as u32, cdf)
}
pub fn write_y_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
size_group_ctx: usize,
) -> Result<(), Error> {
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if size_group_ctx >= BLOCK_SIZE_GROUPS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs
.y_mode_cdf(size_group_ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(y_mode as u32, cdf)
}
pub fn write_intra_uv_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
uv_mode: u8,
cfl_allowed: bool,
) -> Result<(), Error> {
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
let bound = if cfl_allowed {
UV_INTRA_MODES_CFL_ALLOWED
} else {
UV_INTRA_MODES_CFL_NOT_ALLOWED
};
if (uv_mode as usize) >= bound {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs
.uv_mode_cdf(cfl_allowed, y_mode as usize)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(uv_mode as u32, cdf)
}
pub fn write_cfl_alphas(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
alpha_u: i8,
alpha_v: i8,
) -> Result<(), Error> {
let abs_u: i32 = alpha_u.unsigned_abs() as i32;
let abs_v: i32 = alpha_v.unsigned_abs() as i32;
if abs_u as usize > CFL_ALPHABET_SIZE || abs_v as usize > CFL_ALPHABET_SIZE {
return Err(Error::PartitionWalkOutOfRange);
}
if alpha_u == 0 && alpha_v == 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let sign_u: usize = match alpha_u.signum() {
0 => 0, -1 => 1, _ => 2, };
let sign_v: usize = match alpha_v.signum() {
0 => 0,
-1 => 1,
_ => 2,
};
let joint: i32 = 3 * (sign_u as i32) + (sign_v as i32) - 1;
if !(0..(CFL_JOINT_SIGNS as i32)).contains(&joint) {
return Err(Error::PartitionWalkOutOfRange);
}
let signs_cdf = cdfs.cfl_sign_cdf();
writer.write_symbol(joint as u32, signs_cdf)?;
if sign_u != 0 {
let raw_u = abs_u - 1; let ctx_u = cfl_alpha_u_ctx(sign_u, sign_v);
let row_u = cdfs.cfl_alpha_cdf(ctx_u);
writer.write_symbol(raw_u as u32, row_u)?;
}
if sign_v != 0 {
let raw_v = abs_v - 1;
let ctx_v = cfl_alpha_v_ctx(sign_u, sign_v);
let row_v = cdfs.cfl_alpha_cdf(ctx_v);
writer.write_symbol(raw_v as u32, row_v)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_is_inter(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
is_inter: u8,
ctx: usize,
skip_mode: u8,
seg_ref_frame_active: bool,
seg_ref_frame_is_inter: bool,
seg_globalmv_active: bool,
) -> Result<(), Error> {
if is_inter > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if skip_mode != 0 {
if is_inter != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if seg_ref_frame_active {
let expected = seg_ref_frame_is_inter as u8;
if is_inter != expected {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if seg_globalmv_active {
if is_inter != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if ctx >= IS_INTER_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.is_inter_cdf(ctx);
writer.write_symbol(is_inter as u32, cdf)
}
pub fn write_skip_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
skip_mode: u8,
ctx: usize,
sub_size: usize,
seg_skip_mode_off: bool,
skip_mode_present: bool,
) -> Result<(), Error> {
if skip_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if sub_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
let small_block = block_width(sub_size) < 8 || block_height(sub_size) < 8;
if seg_skip_mode_off || !skip_mode_present || small_block {
if skip_mode != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if ctx >= SKIP_MODE_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.skip_mode_cdf(ctx);
writer.write_symbol(skip_mode as u32, cdf)
}
#[allow(clippy::too_many_arguments)]
pub fn write_inter_segment_id(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
segment_id: u8,
pred: u8,
seg_id_read_ctx: usize,
seg_pred_ctx: usize,
pre_skip: bool,
skip: u8,
seg_id_predicted: u8,
segmentation_enabled: bool,
segmentation_update_map: bool,
segmentation_temporal_update: bool,
seg_id_pre_skip: bool,
predicted_segment_id: u8,
last_active_seg_id: u8,
) -> Result<(), Error> {
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(Error::PartitionWalkOutOfRange);
}
let max = last_active_seg_id as u32 + 1;
if segment_id as u32 >= max {
return Err(Error::PartitionWalkOutOfRange);
}
if pred as u32 >= max {
return Err(Error::PartitionWalkOutOfRange);
}
if predicted_segment_id as u32 >= max {
return Err(Error::PartitionWalkOutOfRange);
}
if !segmentation_enabled {
if segment_id != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if !segmentation_update_map {
if segment_id != predicted_segment_id {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if pre_skip && !seg_id_pre_skip {
if segment_id != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if !pre_skip && skip != 0 {
return write_segment_id(
writer,
cdfs,
segment_id,
1,
pred,
seg_id_read_ctx,
last_active_seg_id,
);
}
if segmentation_temporal_update {
if seg_id_predicted > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if seg_pred_ctx >= SEGMENT_ID_PREDICTED_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.segment_id_predicted_cdf(seg_pred_ctx);
writer.write_symbol(seg_id_predicted as u32, cdf)?;
if seg_id_predicted != 0 {
if segment_id != predicted_segment_id {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
return write_segment_id(
writer,
cdfs,
segment_id,
0,
pred,
seg_id_read_ctx,
last_active_seg_id,
);
}
write_segment_id(
writer,
cdfs,
segment_id,
0,
pred,
seg_id_read_ctx,
last_active_seg_id,
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterFrameModeInfoPrefix {
pub segment_id: u8,
pub skip_mode: u8,
pub skip: u8,
pub is_inter: u8,
pub lossless: bool,
pub cdef_idx: Option<i8>,
pub reduced_delta_q_index: i32,
pub reduced_delta_lf: [i32; FRAME_LF_COUNT],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterFrameDeltaSiteInputs {
pub cdef_idx: i8,
pub cdef_idx_prior_stamp: i8,
pub cdef_bits: u32,
pub coded_lossless: bool,
pub enable_cdef: bool,
pub allow_intrabc: bool,
pub anchor_already_stamped: bool,
pub reduced_delta_q_index: i32,
pub reduced_delta_lf: [i32; FRAME_LF_COUNT],
pub read_deltas: bool,
pub use_128x128_superblock: bool,
pub delta_q_res: u8,
pub delta_lf_present: bool,
pub delta_lf_multi: bool,
pub mono_chrome: bool,
pub delta_lf_res: u8,
}
impl Default for InterFrameDeltaSiteInputs {
fn default() -> Self {
Self {
cdef_idx: 0,
cdef_idx_prior_stamp: -1,
cdef_bits: 0,
coded_lossless: false,
enable_cdef: false,
allow_intrabc: false,
anchor_already_stamped: false,
reduced_delta_q_index: 0,
reduced_delta_lf: [0; FRAME_LF_COUNT],
read_deltas: false,
use_128x128_superblock: false,
delta_q_res: 0,
delta_lf_present: false,
delta_lf_multi: false,
mono_chrome: false,
delta_lf_res: 0,
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn write_inter_frame_mode_info_prefix(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
sub_size: usize,
segment_id: u8,
skip_mode: u8,
skip: u8,
is_inter: u8,
seg_pred: u8,
seg_id_predicted: u8,
skip_mode_ctx_val: usize,
skip_ctx_val: usize,
seg_id_read_ctx: usize,
seg_pred_ctx: usize,
is_inter_ctx_val: usize,
seg_skip_mode_off: bool,
seg_skip_active: bool,
seg_ref_frame_active: bool,
seg_ref_frame_is_inter: bool,
seg_globalmv_active: bool,
segmentation_enabled: bool,
segmentation_update_map: bool,
segmentation_temporal_update: bool,
seg_id_pre_skip: bool,
predicted_segment_id: u8,
last_active_seg_id: u8,
skip_mode_present: bool,
lossless_array: &[bool; MAX_SEGMENTS],
delta_inputs: &InterFrameDeltaSiteInputs,
) -> Result<InterFrameModeInfoPrefix, Error> {
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(Error::PartitionWalkOutOfRange);
}
let pre_skip_segment_id = if seg_id_pre_skip {
segment_id
} else if !segmentation_enabled {
0
} else if !segmentation_update_map {
predicted_segment_id
} else {
0
};
write_inter_segment_id(
writer,
cdfs,
pre_skip_segment_id,
seg_pred,
seg_id_read_ctx,
seg_pred_ctx,
true,
0,
seg_id_predicted,
segmentation_enabled,
segmentation_update_map,
segmentation_temporal_update,
seg_id_pre_skip,
predicted_segment_id,
last_active_seg_id,
)?;
write_skip_mode(
writer,
cdfs,
skip_mode,
skip_mode_ctx_val,
sub_size,
seg_skip_mode_off,
skip_mode_present,
)?;
if skip > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let final_skip: u8 = if skip_mode != 0 {
if skip != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
1
} else {
write_skip(writer, cdfs, skip, skip_ctx_val, seg_skip_active)?;
skip
};
if !seg_id_pre_skip {
write_inter_segment_id(
writer,
cdfs,
segment_id,
seg_pred,
seg_id_read_ctx,
seg_pred_ctx,
false,
final_skip,
seg_id_predicted,
segmentation_enabled,
segmentation_update_map,
segmentation_temporal_update,
seg_id_pre_skip,
predicted_segment_id,
last_active_seg_id,
)?;
}
debug_assert!(
(segment_id as usize) < MAX_SEGMENTS,
"segment_id range-checked by write_inter_segment_id"
);
let lossless = lossless_array[segment_id as usize];
let cdef_idx_committed = write_cdef(
writer,
delta_inputs.cdef_idx,
delta_inputs.cdef_idx_prior_stamp,
delta_inputs.cdef_bits,
final_skip,
delta_inputs.coded_lossless,
delta_inputs.enable_cdef,
delta_inputs.allow_intrabc,
delta_inputs.anchor_already_stamped,
)?;
write_delta_qindex(
writer,
cdfs,
sub_size,
delta_inputs.reduced_delta_q_index,
final_skip,
delta_inputs.read_deltas,
delta_inputs.use_128x128_superblock,
delta_inputs.delta_q_res,
)?;
write_delta_lf(
writer,
cdfs,
sub_size,
&delta_inputs.reduced_delta_lf,
final_skip,
delta_inputs.read_deltas,
delta_inputs.delta_lf_present,
delta_inputs.delta_lf_multi,
delta_inputs.mono_chrome,
delta_inputs.use_128x128_superblock,
delta_inputs.delta_lf_res,
)?;
write_is_inter(
writer,
cdfs,
is_inter,
is_inter_ctx_val,
skip_mode,
seg_ref_frame_active,
seg_ref_frame_is_inter,
seg_globalmv_active,
)?;
Ok(InterFrameModeInfoPrefix {
segment_id,
skip_mode,
skip: final_skip,
is_inter,
lossless,
cdef_idx: cdef_idx_committed,
reduced_delta_q_index: delta_inputs.reduced_delta_q_index,
reduced_delta_lf: delta_inputs.reduced_delta_lf,
})
}
#[allow(clippy::too_many_arguments)]
pub fn write_cdef(
writer: &mut SymbolWriter,
cdef_idx: i8,
cdef_idx_prior_stamp: i8,
cdef_bits: u32,
skip: u8,
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
anchor_already_stamped: bool,
) -> Result<Option<i8>, Error> {
if cdef_bits > 3 {
return Err(Error::PartitionWalkOutOfRange);
}
if skip != 0 || coded_lossless || !enable_cdef || allow_intrabc {
return Ok(None);
}
if anchor_already_stamped {
if cdef_idx_prior_stamp != cdef_idx {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(None);
}
if cdef_idx < 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let value = cdef_idx as u32;
if cdef_bits == 0 {
if value != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(Some(cdef_idx));
}
if value >= (1u32 << cdef_bits) {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(cdef_bits, value)?;
Ok(Some(cdef_idx))
}
#[allow(clippy::too_many_arguments)]
pub fn write_delta_qindex(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
sub_size: usize,
reduced_delta_q_index: i32,
skip: u8,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
) -> Result<(), Error> {
if sub_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if delta_q_res > 3 {
return Err(Error::PartitionWalkOutOfRange);
}
let sb_size = if use_128x128_superblock {
BLOCK_128X128
} else {
BLOCK_64X64
};
if sub_size == sb_size && skip != 0 {
if reduced_delta_q_index != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if !read_deltas {
if reduced_delta_q_index != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let abs_value: u32 = reduced_delta_q_index.unsigned_abs();
if abs_value > 511 {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.delta_q_cdf();
if (abs_value as usize) < DELTA_Q_SMALL {
writer.write_symbol(abs_value, cdf)?;
} else {
writer.write_symbol(DELTA_Q_SMALL as u32, cdf)?;
debug_assert!(abs_value >= 3);
let n = floor_log2(abs_value - 1);
debug_assert!((1..=8).contains(&n));
let rem_bits_field = n - 1;
debug_assert!(rem_bits_field <= 7, "L(3) field width");
writer.write_literal(3, rem_bits_field)?;
let abs_bits = abs_value - (1u32 << n) - 1;
debug_assert!(abs_bits < (1u32 << n));
writer.write_literal(n, abs_bits)?;
}
if abs_value != 0 {
let sign: u32 = if reduced_delta_q_index < 0 { 1 } else { 0 };
writer.write_literal(1, sign)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_delta_lf(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
sub_size: usize,
reduced_delta_lf: &[i32; FRAME_LF_COUNT],
skip: u8,
read_deltas: bool,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
use_128x128_superblock: bool,
delta_lf_res: u8,
) -> Result<(), Error> {
if sub_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if delta_lf_res > 3 {
return Err(Error::PartitionWalkOutOfRange);
}
let sb_size = if use_128x128_superblock {
BLOCK_128X128
} else {
BLOCK_64X64
};
let sb_short_circuit = sub_size == sb_size && skip != 0;
let read_gate = read_deltas && delta_lf_present;
if sb_short_circuit || !read_gate {
if reduced_delta_lf.iter().any(|&v| v != 0) {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let frame_lf_count: usize = if delta_lf_multi {
if mono_chrome {
FRAME_LF_COUNT - 2
} else {
FRAME_LF_COUNT
}
} else {
1
};
for &v in reduced_delta_lf.iter().skip(frame_lf_count) {
if v != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
}
for (i, &reduced) in reduced_delta_lf.iter().take(frame_lf_count).enumerate() {
let abs_value: u32 = reduced.unsigned_abs();
if abs_value > 511 {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf: &mut [u16] = if delta_lf_multi {
cdfs.delta_lf_multi_cdf(i)
} else {
cdfs.delta_lf_cdf()
};
if (abs_value as usize) < DELTA_LF_SMALL {
writer.write_symbol(abs_value, cdf)?;
} else {
writer.write_symbol(DELTA_LF_SMALL as u32, cdf)?;
debug_assert!(abs_value >= 3);
let n = floor_log2(abs_value - 1);
debug_assert!((1..=8).contains(&n));
let rem_bits_field = n - 1;
debug_assert!(rem_bits_field <= 7);
writer.write_literal(3, rem_bits_field)?;
let abs_bits = abs_value - (1u32 << n) - 1;
debug_assert!(abs_bits < (1u32 << n));
writer.write_literal(n, abs_bits)?;
}
if abs_value != 0 {
let sign: u32 = if reduced < 0 { 1 } else { 0 };
writer.write_literal(1, sign)?;
}
}
Ok(())
}
pub fn write_intra_angle_info_y(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
angle_delta_y: i8,
mi_size: usize,
y_mode: u8,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
let max = MAX_ANGLE_DELTA as i8;
if !(-max..=max).contains(&angle_delta_y) {
return Err(Error::PartitionWalkOutOfRange);
}
let reads = mi_size >= BLOCK_8X8 && is_directional(y_mode);
if !reads {
if angle_delta_y != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let raw: u32 = (angle_delta_y as i32 + MAX_ANGLE_DELTA as i32) as u32;
let cdf = cdfs
.angle_delta_cdf(y_mode as usize)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(raw, cdf)
}
pub fn write_intra_angle_info_uv(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
angle_delta_uv: i8,
mi_size: usize,
uv_mode: u8,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (uv_mode as usize) >= UV_INTRA_MODES_CFL_ALLOWED {
return Err(Error::PartitionWalkOutOfRange);
}
let max = MAX_ANGLE_DELTA as i8;
if !(-max..=max).contains(&angle_delta_uv) {
return Err(Error::PartitionWalkOutOfRange);
}
let reads = mi_size >= BLOCK_8X8 && is_directional(uv_mode);
if !reads {
if angle_delta_uv != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let raw: u32 = (angle_delta_uv as i32 + MAX_ANGLE_DELTA as i32) as u32;
let cdf = cdfs
.angle_delta_cdf(uv_mode as usize)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(raw, cdf)
}
#[allow(clippy::too_many_arguments)]
pub fn write_filter_intra_mode_info(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
use_filter_intra: u8,
filter_intra_mode: Option<u8>,
mi_size: usize,
y_mode: u8,
palette_size_y: u32,
enable_filter_intra: bool,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if use_filter_intra > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let bw = block_width(mi_size);
let bh = block_height(mi_size);
let outer_gate = enable_filter_intra && y_mode == 0 && palette_size_y == 0 && bw.max(bh) <= 32;
if !outer_gate {
if use_filter_intra != 0 || filter_intra_mode.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let cdf = cdfs.filter_intra_cdf(mi_size);
writer.write_symbol(use_filter_intra as u32, cdf)?;
if use_filter_intra == 1 {
let mode = filter_intra_mode.ok_or(Error::PartitionWalkOutOfRange)?;
if (mode as usize) >= INTRA_FILTER_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
let row = cdfs.filter_intra_mode_cdf();
writer.write_symbol(mode as u32, row)?;
} else if filter_intra_mode.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_palette_mode_info(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
has_palette_y: u8,
has_palette_uv: u8,
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
has_chroma: bool,
allow_screen_content_tools: bool,
above_palette_y: bool,
left_palette_y: bool,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if let Some(uv) = uv_mode {
if (uv as usize) >= UV_INTRA_MODES_CFL_ALLOWED {
return Err(Error::PartitionWalkOutOfRange);
}
}
if has_palette_y > 1 || has_palette_uv > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if has_palette_y != 0 || has_palette_uv != 0 {
return Err(Error::PaletteEntriesUnsupported);
}
let bw = block_width(mi_size) as u32;
let bh = block_height(mi_size) as u32;
let outer_gate = allow_screen_content_tools && mi_size >= BLOCK_8X8 && bw <= 64 && bh <= 64;
if !outer_gate {
return Ok(());
}
let bsize_ctx = mi_width_log2(mi_size) + mi_height_log2(mi_size) - 2;
debug_assert!(
bsize_ctx < PALETTE_BLOCK_SIZE_CONTEXTS,
"§5.11.46 bsizeCtx must be in 0..PALETTE_BLOCK_SIZE_CONTEXTS"
);
if y_mode == 0 {
let ctx_y = palette_y_mode_ctx(above_palette_y, left_palette_y);
let row = cdfs.palette_y_mode_cdf(bsize_ctx, ctx_y);
writer.write_symbol(has_palette_y as u32, row)?;
} else if has_palette_y != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
if has_chroma && uv_mode == Some(0) {
let ctx_uv = palette_uv_mode_ctx(0);
let row = cdfs.palette_uv_mode_cdf(ctx_uv);
writer.write_symbol(has_palette_uv as u32, row)?;
} else if has_palette_uv != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_palette_entries_y(
writer: &mut SymbolWriter,
bit_depth: u8,
palette_size_y: usize,
palette_colors_y: &[u16],
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<(), Error> {
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(Error::PartitionWalkOutOfRange);
}
if !(2..=PALETTE_COLORS).contains(&palette_size_y) {
return Err(Error::PartitionWalkOutOfRange);
}
if palette_colors_y.len() < palette_size_y {
return Err(Error::PartitionWalkOutOfRange);
}
let bd = bit_depth as u32;
let max_val: u32 = 1u32 << bd;
for (i, &c) in palette_colors_y.iter().take(palette_size_y).enumerate() {
if (c as u32) >= max_val {
return Err(Error::PartitionWalkOutOfRange);
}
if i > 0 && (c as u32) <= palette_colors_y[i - 1] as u32 {
return Err(Error::PartitionWalkOutOfRange);
}
}
let mut cache_buf = [0u16; 2 * PALETTE_COLORS];
let cache_n = walker.get_palette_cache(0, mi_row, mi_col, &mut cache_buf);
let mut idx: usize = 0;
{
let mut i = 0usize;
while i < cache_n && idx < palette_size_y {
let use_cache = if cache_buf[i] == palette_colors_y[idx] {
1u32
} else {
0u32
};
writer.write_literal(1, use_cache)?;
if use_cache != 0 {
idx += 1;
}
i += 1;
}
}
if idx < palette_size_y {
let v = palette_colors_y[idx] as u32;
writer.write_literal(bd, v)?;
idx += 1;
}
if idx < palette_size_y {
let min_bits = bd - 3;
let start_idx = idx;
let extra = pick_palette_extra_bits_y(palette_colors_y, palette_size_y, start_idx, bd)?;
writer.write_literal(2, extra)?;
let mut palette_bits = min_bits + extra;
while idx < palette_size_y {
let prev = palette_colors_y[idx - 1] as u32;
let cur = palette_colors_y[idx] as u32;
let delta_raw = cur - prev - 1;
if palette_bits == 0 {
if delta_raw != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(0, 0)?;
} else {
if delta_raw >= (1u32 << palette_bits) {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(palette_bits, delta_raw)?;
}
let range = max_val.saturating_sub(cur).saturating_sub(1);
palette_bits = palette_bits.min(ceil_log2_av1(range));
idx += 1;
}
}
Ok(())
}
fn pick_palette_extra_bits_y(
palette_colors_y: &[u16],
palette_size_y: usize,
start_idx: usize,
bd: u32,
) -> Result<u32, Error> {
let min_bits = bd - 3;
let max_val: u32 = 1u32 << bd;
for extra in 0..=3u32 {
let mut palette_bits = min_bits + extra;
let mut ok = true;
let mut idx = start_idx;
while idx < palette_size_y {
let prev = palette_colors_y[idx - 1] as u32;
let cur = palette_colors_y[idx] as u32;
let delta_raw = cur - prev - 1;
let capacity = if palette_bits >= 32 {
u32::MAX
} else {
1u32 << palette_bits
};
if palette_bits == 0 {
if delta_raw != 0 {
ok = false;
break;
}
} else if delta_raw >= capacity {
ok = false;
break;
}
let range = max_val.saturating_sub(cur).saturating_sub(1);
palette_bits = palette_bits.min(ceil_log2_av1(range));
idx += 1;
}
if ok {
return Ok(extra);
}
}
Err(Error::PartitionWalkOutOfRange)
}
#[allow(clippy::too_many_arguments)]
pub fn write_palette_entries_uv(
writer: &mut SymbolWriter,
bit_depth: u8,
palette_size_uv: usize,
palette_colors_u: &[u16],
palette_colors_v: &[u16],
delta_encode_v: bool,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<(), Error> {
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(Error::PartitionWalkOutOfRange);
}
if !(2..=PALETTE_COLORS).contains(&palette_size_uv) {
return Err(Error::PartitionWalkOutOfRange);
}
if palette_colors_u.len() < palette_size_uv {
return Err(Error::PartitionWalkOutOfRange);
}
if palette_colors_v.len() < palette_size_uv {
return Err(Error::PartitionWalkOutOfRange);
}
let bd = bit_depth as u32;
let max_val: u32 = 1u32 << bd;
for (i, &c) in palette_colors_u.iter().take(palette_size_uv).enumerate() {
if (c as u32) >= max_val {
return Err(Error::PartitionWalkOutOfRange);
}
if i > 0 && (c as u32) < palette_colors_u[i - 1] as u32 {
return Err(Error::PartitionWalkOutOfRange);
}
}
for &c in palette_colors_v.iter().take(palette_size_uv) {
if (c as u32) >= max_val {
return Err(Error::PartitionWalkOutOfRange);
}
}
let mut cache_buf = [0u16; 2 * PALETTE_COLORS];
let cache_n = walker.get_palette_cache(1, mi_row, mi_col, &mut cache_buf);
let mut idx: usize = 0;
{
let mut i = 0usize;
while i < cache_n && idx < palette_size_uv {
let use_cache = if cache_buf[i] == palette_colors_u[idx] {
1u32
} else {
0u32
};
writer.write_literal(1, use_cache)?;
if use_cache != 0 {
idx += 1;
}
i += 1;
}
}
if idx < palette_size_uv {
let v = palette_colors_u[idx] as u32;
writer.write_literal(bd, v)?;
idx += 1;
}
if idx < palette_size_uv {
let min_bits = bd - 3;
let start_idx = idx;
let extra = pick_palette_extra_bits_u(palette_colors_u, palette_size_uv, start_idx, bd)?;
writer.write_literal(2, extra)?;
let mut palette_bits = min_bits + extra;
while idx < palette_size_uv {
let prev = palette_colors_u[idx - 1] as u32;
let cur = palette_colors_u[idx] as u32;
let delta_raw = cur - prev;
if palette_bits == 0 {
if delta_raw != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(0, 0)?;
} else {
if delta_raw >= (1u32 << palette_bits) {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(palette_bits, delta_raw)?;
}
let range = max_val.saturating_sub(cur);
palette_bits = palette_bits.min(ceil_log2_av1(range));
idx += 1;
}
}
writer.write_literal(1, if delta_encode_v { 1 } else { 0 })?;
if delta_encode_v {
let min_bits_v = bd - 4;
let extra_v = pick_palette_extra_bits_v(palette_colors_v, palette_size_uv, bd)?;
writer.write_literal(2, extra_v)?;
let palette_bits_v = min_bits_v + extra_v;
let first = palette_colors_v[0] as u32;
writer.write_literal(bd, first)?;
for i in 1..palette_size_uv {
let prev = palette_colors_v[i - 1] as i32;
let cur = palette_colors_v[i] as i32;
let signed_delta = pick_v_signed_delta(prev, cur, max_val as i32, palette_bits_v)?;
let magnitude = signed_delta.unsigned_abs();
if palette_bits_v == 0 {
if magnitude != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(0, 0)?;
} else {
if magnitude >= (1u32 << palette_bits_v) {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(palette_bits_v, magnitude)?;
}
if magnitude != 0 {
let sign_bit = if signed_delta < 0 { 1u32 } else { 0u32 };
writer.write_literal(1, sign_bit)?;
}
}
} else {
for &c in palette_colors_v.iter().take(palette_size_uv) {
writer.write_literal(bd, c as u32)?;
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_palette_tokens_plane(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
plane: PalettePlane,
palette_size: usize,
block_width: usize,
block_height: usize,
onscreen_width: usize,
onscreen_height: usize,
color_map: &[u8],
stride: usize,
) -> Result<(), Error> {
if !(2..=PALETTE_COLORS).contains(&palette_size) {
return Err(Error::InvalidPaletteWalkArgs);
}
if onscreen_width == 0 || onscreen_height == 0 {
return Err(Error::InvalidPaletteWalkArgs);
}
if onscreen_width > block_width || onscreen_height > block_height {
return Err(Error::InvalidPaletteWalkArgs);
}
if stride < block_width {
return Err(Error::InvalidPaletteWalkArgs);
}
if color_map.len() < block_height.saturating_mul(stride) {
return Err(Error::InvalidPaletteWalkArgs);
}
if (color_map[0] as usize) >= palette_size {
return Err(Error::InvalidPaletteWalkArgs);
}
writer.write_ns(palette_size as u32, color_map[0] as u32)?;
let mut work = vec![0u8; block_height * stride];
work[0] = color_map[0];
for i in 1..(onscreen_height + onscreen_width - 1) {
let j_hi = core::cmp::min(i, onscreen_width - 1);
let j_lo = i.saturating_sub(onscreen_height - 1);
let mut j = j_hi;
loop {
let r = i - j;
let c = j;
let desired = color_map[r * stride + c];
if (desired as usize) >= palette_size {
return Err(Error::InvalidPaletteWalkArgs);
}
let pcc = get_palette_color_context(&work, stride, r, c, palette_size)
.ok_or(Error::InvalidPaletteWalkArgs)?;
let ctx = palette_color_ctx(pcc.color_context_hash)
.ok_or(Error::PaletteColorContextUnmapped)?;
let idx = pcc
.color_order
.iter()
.take(palette_size)
.position(|&v| v == desired)
.ok_or(Error::InvalidPaletteWalkArgs)?;
let cdf = match plane {
PalettePlane::Y => cdfs.palette_y_color_cdf(palette_size, ctx),
PalettePlane::Uv => cdfs.palette_uv_color_cdf(palette_size, ctx),
}
.ok_or(Error::InvalidPaletteWalkArgs)?;
writer.write_symbol(idx as u32, cdf)?;
work[r * stride + c] = desired;
if j == j_lo {
break;
}
j -= 1;
}
}
Ok(())
}
fn pick_palette_extra_bits_u(
palette_colors_u: &[u16],
palette_size_uv: usize,
start_idx: usize,
bd: u32,
) -> Result<u32, Error> {
let min_bits = bd - 3;
let max_val: u32 = 1u32 << bd;
for extra in 0..=3u32 {
let mut palette_bits = min_bits + extra;
let mut ok = true;
let mut idx = start_idx;
while idx < palette_size_uv {
let prev = palette_colors_u[idx - 1] as u32;
let cur = palette_colors_u[idx] as u32;
let delta_raw = cur - prev;
let capacity = if palette_bits >= 32 {
u32::MAX
} else {
1u32 << palette_bits
};
if palette_bits == 0 {
if delta_raw != 0 {
ok = false;
break;
}
} else if delta_raw >= capacity {
ok = false;
break;
}
let range = max_val.saturating_sub(cur);
palette_bits = palette_bits.min(ceil_log2_av1(range));
idx += 1;
}
if ok {
return Ok(extra);
}
}
Err(Error::PartitionWalkOutOfRange)
}
fn pick_palette_extra_bits_v(
palette_colors_v: &[u16],
palette_size_uv: usize,
bd: u32,
) -> Result<u32, Error> {
let min_bits = bd - 4;
let max_val: i32 = 1i32 << bd;
for extra in 0..=3u32 {
let palette_bits = min_bits + extra;
let mut ok = true;
for i in 1..palette_size_uv {
let prev = palette_colors_v[i - 1] as i32;
let cur = palette_colors_v[i] as i32;
if pick_v_signed_delta(prev, cur, max_val, palette_bits).is_err() {
ok = false;
break;
}
}
if ok {
return Ok(extra);
}
}
Err(Error::PartitionWalkOutOfRange)
}
fn pick_v_signed_delta(prev: i32, cur: i32, max_val: i32, palette_bits: u32) -> Result<i32, Error> {
let capacity: u32 = if palette_bits == 0 {
1
} else if palette_bits >= 32 {
u32::MAX
} else {
1u32 << palette_bits
};
let natural = cur - prev;
let candidates = [natural, natural + max_val, natural - max_val];
let mut best: Option<i32> = None;
for &cand in &candidates {
let mag = cand.unsigned_abs();
if mag >= capacity {
continue;
}
let mut val = prev + cand;
if val < 0 {
val += max_val;
}
if val >= max_val {
val -= max_val;
}
if val != cur {
continue;
}
match best {
None => best = Some(cand),
Some(b) => {
if cand.unsigned_abs() < b.unsigned_abs() {
best = Some(cand);
}
}
}
}
best.ok_or(Error::PartitionWalkOutOfRange)
}
#[allow(clippy::too_many_arguments)]
pub fn write_intra_block_mode_info(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
angle_delta_y: i8,
angle_delta_uv: i8,
cfl_allowed: bool,
has_chroma: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
use_filter_intra: u8,
filter_intra_mode: Option<u8>,
above_palette_y: bool,
left_palette_y: bool,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if has_chroma {
let uv = uv_mode.ok_or(Error::PartitionWalkOutOfRange)?;
if (uv as usize) >= UV_INTRA_MODES_CFL_ALLOWED {
return Err(Error::PartitionWalkOutOfRange);
}
if (uv as usize) == 13 {
return Err(Error::PartitionWalkOutOfRange);
}
} else if uv_mode.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
let size_group_ctx = crate::cdf::size_group(mi_size);
write_y_mode(writer, cdfs, y_mode, size_group_ctx)?;
write_intra_angle_info_y(writer, cdfs, angle_delta_y, mi_size, y_mode)?;
if has_chroma {
let uv = uv_mode.expect("validated above");
write_intra_uv_mode(writer, cdfs, y_mode, uv, cfl_allowed)?;
write_intra_angle_info_uv(writer, cdfs, angle_delta_uv, mi_size, uv)?;
} else {
if angle_delta_uv != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
}
write_palette_mode_info(
writer,
cdfs,
0,
0,
mi_size,
y_mode,
uv_mode,
has_chroma,
allow_screen_content_tools,
above_palette_y,
left_palette_y,
)?;
write_filter_intra_mode_info(
writer,
cdfs,
use_filter_intra,
filter_intra_mode,
mi_size,
y_mode,
0,
enable_filter_intra,
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_palette_mode_info_with_entries(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
has_palette_y: u8,
has_palette_uv: u8,
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
has_chroma: bool,
allow_screen_content_tools: bool,
above_palette_y: bool,
left_palette_y: bool,
bit_depth: u8,
palette_size_y: usize,
palette_colors_y: &[u16],
palette_size_uv: usize,
palette_colors_u: &[u16],
palette_colors_v: &[u16],
delta_encode_v: bool,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if let Some(uv) = uv_mode {
if (uv as usize) >= UV_INTRA_MODES_CFL_ALLOWED {
return Err(Error::PartitionWalkOutOfRange);
}
}
if has_palette_y > 1 || has_palette_uv > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let bw = block_width(mi_size) as u32;
let bh = block_height(mi_size) as u32;
let outer_gate = allow_screen_content_tools && mi_size >= BLOCK_8X8 && bw <= 64 && bh <= 64;
if !outer_gate {
if has_palette_y != 0 || has_palette_uv != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let bsize_ctx = mi_width_log2(mi_size) + mi_height_log2(mi_size) - 2;
debug_assert!(
bsize_ctx < PALETTE_BLOCK_SIZE_CONTEXTS,
"§5.11.46 bsizeCtx must be in 0..PALETTE_BLOCK_SIZE_CONTEXTS"
);
if y_mode == 0 {
let ctx_y = palette_y_mode_ctx(above_palette_y, left_palette_y);
let row = cdfs.palette_y_mode_cdf(bsize_ctx, ctx_y);
writer.write_symbol(has_palette_y as u32, row)?;
if has_palette_y == 1 {
if !(2..=PALETTE_COLORS).contains(&palette_size_y) {
return Err(Error::PartitionWalkOutOfRange);
}
let row_sz = cdfs.palette_y_size_cdf(bsize_ctx);
let minus_2 = (palette_size_y - 2) as u32;
writer.write_symbol(minus_2, row_sz)?;
write_palette_entries_y(
writer,
bit_depth,
palette_size_y,
palette_colors_y,
walker,
mi_row,
mi_col,
)?;
}
} else if has_palette_y != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
if has_chroma && uv_mode == Some(0) {
let ps_y = if y_mode == 0 && has_palette_y == 1 {
palette_size_y
} else {
0
};
let ctx_uv = palette_uv_mode_ctx(ps_y);
let row = cdfs.palette_uv_mode_cdf(ctx_uv);
writer.write_symbol(has_palette_uv as u32, row)?;
if has_palette_uv == 1 {
if !(2..=PALETTE_COLORS).contains(&palette_size_uv) {
return Err(Error::PartitionWalkOutOfRange);
}
let row_sz = cdfs.palette_uv_size_cdf(bsize_ctx);
let minus_2 = (palette_size_uv - 2) as u32;
writer.write_symbol(minus_2, row_sz)?;
write_palette_entries_uv(
writer,
bit_depth,
palette_size_uv,
palette_colors_u,
palette_colors_v,
delta_encode_v,
walker,
mi_row,
mi_col,
)?;
}
} else if has_palette_uv != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_intra_block_mode_info_with_palette(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
angle_delta_y: i8,
angle_delta_uv: i8,
cfl_allowed: bool,
has_chroma: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
use_filter_intra: u8,
filter_intra_mode: Option<u8>,
above_palette_y: bool,
left_palette_y: bool,
bit_depth: u8,
has_palette_y: u8,
palette_size_y: usize,
palette_colors_y: &[u16],
has_palette_uv: u8,
palette_size_uv: usize,
palette_colors_u: &[u16],
palette_colors_v: &[u16],
delta_encode_v: bool,
cfl_alpha_u: Option<i8>,
cfl_alpha_v: Option<i8>,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<(), Error> {
check_intra_mode_info_inputs(
mi_size,
y_mode,
uv_mode,
cfl_allowed,
has_chroma,
cfl_alpha_u,
cfl_alpha_v,
)?;
let size_group_ctx = crate::cdf::size_group(mi_size);
write_y_mode(writer, cdfs, y_mode, size_group_ctx)?;
write_intra_mode_info_tail(
writer,
cdfs,
mi_size,
y_mode,
uv_mode,
angle_delta_y,
angle_delta_uv,
cfl_allowed,
has_chroma,
allow_screen_content_tools,
enable_filter_intra,
use_filter_intra,
filter_intra_mode,
above_palette_y,
left_palette_y,
bit_depth,
has_palette_y,
palette_size_y,
palette_colors_y,
has_palette_uv,
palette_size_uv,
palette_colors_u,
palette_colors_v,
delta_encode_v,
cfl_alpha_u,
cfl_alpha_v,
walker,
mi_row,
mi_col,
)
}
fn check_intra_mode_info_inputs(
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
cfl_allowed: bool,
has_chroma: bool,
cfl_alpha_u: Option<i8>,
cfl_alpha_v: Option<i8>,
) -> Result<(), Error> {
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if (y_mode as usize) >= INTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
if has_chroma {
let uv = uv_mode.ok_or(Error::PartitionWalkOutOfRange)?;
if (uv as usize) >= UV_INTRA_MODES_CFL_ALLOWED {
return Err(Error::PartitionWalkOutOfRange);
}
if (uv as usize) == 13 {
if cfl_alpha_u.is_none() || cfl_alpha_v.is_none() {
return Err(Error::PartitionWalkOutOfRange);
}
if !cfl_allowed {
return Err(Error::PartitionWalkOutOfRange);
}
} else if cfl_alpha_u.is_some() || cfl_alpha_v.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
} else if uv_mode.is_some() {
return Err(Error::PartitionWalkOutOfRange);
} else if cfl_alpha_u.is_some() || cfl_alpha_v.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_intra_mode_info_tail(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
angle_delta_y: i8,
angle_delta_uv: i8,
cfl_allowed: bool,
has_chroma: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
use_filter_intra: u8,
filter_intra_mode: Option<u8>,
above_palette_y: bool,
left_palette_y: bool,
bit_depth: u8,
has_palette_y: u8,
palette_size_y: usize,
palette_colors_y: &[u16],
has_palette_uv: u8,
palette_size_uv: usize,
palette_colors_u: &[u16],
palette_colors_v: &[u16],
delta_encode_v: bool,
cfl_alpha_u: Option<i8>,
cfl_alpha_v: Option<i8>,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<(), Error> {
write_intra_angle_info_y(writer, cdfs, angle_delta_y, mi_size, y_mode)?;
if has_chroma {
let uv = uv_mode.expect("validated above");
write_intra_uv_mode(writer, cdfs, y_mode, uv, cfl_allowed)?;
if (uv as usize) == 13 {
let alpha_u = cfl_alpha_u.expect("validated above");
let alpha_v = cfl_alpha_v.expect("validated above");
write_cfl_alphas(writer, cdfs, alpha_u, alpha_v)?;
}
write_intra_angle_info_uv(writer, cdfs, angle_delta_uv, mi_size, uv)?;
} else if angle_delta_uv != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
write_palette_mode_info_with_entries(
writer,
cdfs,
has_palette_y,
has_palette_uv,
mi_size,
y_mode,
uv_mode,
has_chroma,
allow_screen_content_tools,
above_palette_y,
left_palette_y,
bit_depth,
palette_size_y,
palette_colors_y,
palette_size_uv,
palette_colors_u,
palette_colors_v,
delta_encode_v,
walker,
mi_row,
mi_col,
)?;
let palette_size_y_for_filter = if y_mode == 0 && has_palette_y == 1 {
palette_size_y as u32
} else {
0
};
write_filter_intra_mode_info(
writer,
cdfs,
use_filter_intra,
filter_intra_mode,
mi_size,
y_mode,
palette_size_y_for_filter,
enable_filter_intra,
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_intra_frame_else_arm(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
mi_size: usize,
y_mode: u8,
uv_mode: Option<u8>,
angle_delta_y: i8,
angle_delta_uv: i8,
cfl_allowed: bool,
has_chroma: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
use_filter_intra: u8,
filter_intra_mode: Option<u8>,
above_palette_y: bool,
left_palette_y: bool,
bit_depth: u8,
has_palette_y: u8,
palette_size_y: usize,
palette_colors_y: &[u16],
has_palette_uv: u8,
palette_size_uv: usize,
palette_colors_u: &[u16],
palette_colors_v: &[u16],
delta_encode_v: bool,
cfl_alpha_u: Option<i8>,
cfl_alpha_v: Option<i8>,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
abovemode_ctx: usize,
leftmode_ctx: usize,
) -> Result<(), Error> {
check_intra_mode_info_inputs(
mi_size,
y_mode,
uv_mode,
cfl_allowed,
has_chroma,
cfl_alpha_u,
cfl_alpha_v,
)?;
write_intra_frame_y_mode(writer, cdfs, y_mode, abovemode_ctx, leftmode_ctx)?;
write_intra_mode_info_tail(
writer,
cdfs,
mi_size,
y_mode,
uv_mode,
angle_delta_y,
angle_delta_uv,
cfl_allowed,
has_chroma,
allow_screen_content_tools,
enable_filter_intra,
use_filter_intra,
filter_intra_mode,
above_palette_y,
left_palette_y,
bit_depth,
has_palette_y,
palette_size_y,
palette_colors_y,
has_palette_uv,
palette_size_uv,
palette_colors_u,
palette_colors_v,
delta_encode_v,
cfl_alpha_u,
cfl_alpha_v,
walker,
mi_row,
mi_col,
)
}
pub fn write_inter_block_mode_info_bootstrap(
skip_mode: u8,
skip_mode_frame: [i8; 2],
seg_ref_frame_active: bool,
seg_ref_frame_data: i8,
seg_skip_active: bool,
seg_globalmv_active: bool,
) -> Result<(i8, i8), Error> {
if skip_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let in_ref_range = |v: i8| (0..=7).contains(&v);
if skip_mode == 1 {
if !in_ref_range(skip_mode_frame[0]) || !in_ref_range(skip_mode_frame[1]) {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok((skip_mode_frame[0], skip_mode_frame[1]));
}
if seg_ref_frame_active {
if !in_ref_range(seg_ref_frame_data) {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok((seg_ref_frame_data, -1));
}
if seg_skip_active || seg_globalmv_active {
return Ok((1, -1));
}
Err(Error::PartitionWalkOutOfRange)
}
pub fn write_comp_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
comp_mode: u8,
mi_size: usize,
reference_select: bool,
ctx: usize,
) -> Result<(), Error> {
if comp_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if mi_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
let bw4 = NUM_4X4_BLOCKS_WIDE[mi_size];
let bh4 = NUM_4X4_BLOCKS_HIGH[mi_size];
if reference_select && bw4.min(bh4) >= 2 {
if ctx >= COMP_INTER_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.comp_mode_cdf(ctx);
writer.write_symbol(comp_mode as u32, cdf)
} else {
if comp_mode != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
pub fn write_compound_ref_frames(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
ref_frame: [i32; 2],
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
) -> Result<(), Error> {
let ref0 = ref_frame[0];
let ref1 = ref_frame[1];
if !(1..=7).contains(&ref0) || !(1..=7).contains(&ref1) {
return Err(Error::PartitionWalkOutOfRange);
}
let unidir = is_samedir_ref_pair(ref0, ref1);
if unidir {
if !matches!((ref0, ref1), (5, 7) | (1, 2) | (1, 3) | (1, 4)) {
return Err(Error::PartitionWalkOutOfRange);
}
} else if ref0 > 4 {
return Err(Error::PartitionWalkOutOfRange);
}
let cnt = |frame_type: i32| {
count_refs(
frame_type,
avail_u,
above_ref_frame,
avail_l,
left_ref_frame,
)
};
let crt_ctx = comp_ref_type_ctx(
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
);
let row = cdfs.comp_ref_type_cdf(crt_ctx);
writer.write_symbol(!unidir as u32, row)?;
if unidir {
let ucr_ctx = ref_count_ctx(cnt(1) + cnt(2) + cnt(3) + cnt(4), cnt(5) + cnt(6) + cnt(7));
let uni_comp_ref = ((ref0, ref1) == (5, 7)) as u32;
let row = cdfs.uni_comp_ref_cdf(ucr_ctx, 0);
writer.write_symbol(uni_comp_ref, row)?;
if uni_comp_ref == 0 {
let p1_ctx = ref_count_ctx(cnt(2), cnt(3) + cnt(4));
let uni_comp_ref_p1 = (ref1 != 2) as u32;
let row = cdfs.uni_comp_ref_cdf(p1_ctx, 1);
writer.write_symbol(uni_comp_ref_p1, row)?;
if uni_comp_ref_p1 == 1 {
let p2_ctx = ref_count_ctx(cnt(3), cnt(4));
let uni_comp_ref_p2 = (ref1 == 4) as u32;
let row = cdfs.uni_comp_ref_cdf(p2_ctx, 2);
writer.write_symbol(uni_comp_ref_p2, row)?;
}
}
} else {
let cr_ctx = ref_count_ctx(cnt(1) + cnt(2), cnt(3) + cnt(4));
let comp_ref = (ref0 >= 3) as u32;
let row = cdfs.comp_ref_cdf(cr_ctx, 0);
writer.write_symbol(comp_ref, row)?;
if comp_ref == 0 {
let p1_ctx = ref_count_ctx(cnt(1), cnt(2));
let comp_ref_p1 = (ref0 == 2) as u32;
let row = cdfs.comp_ref_cdf(p1_ctx, 1);
writer.write_symbol(comp_ref_p1, row)?;
} else {
let p2_ctx = ref_count_ctx(cnt(3), cnt(4));
let comp_ref_p2 = (ref0 == 4) as u32;
let row = cdfs.comp_ref_cdf(p2_ctx, 2);
writer.write_symbol(comp_ref_p2, row)?;
}
let bw_ctx = ref_count_ctx(cnt(5) + cnt(6), cnt(7));
let comp_bwdref = (ref1 == 7) as u32;
let row = cdfs.comp_bwd_ref_cdf(bw_ctx, 0);
writer.write_symbol(comp_bwdref, row)?;
if comp_bwdref == 0 {
let p1_ctx = ref_count_ctx(cnt(5), cnt(6));
let comp_bwdref_p1 = (ref1 == 6) as u32;
let row = cdfs.comp_bwd_ref_cdf(p1_ctx, 1);
writer.write_symbol(comp_bwdref_p1, row)?;
}
}
Ok(())
}
pub fn write_single_ref_frames(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
ref_frame: [i32; 2],
avail_u: bool,
avail_l: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
) -> Result<(), Error> {
let ref0 = ref_frame[0];
if !(1..=7).contains(&ref0) || ref_frame[1] != -1 {
return Err(Error::PartitionWalkOutOfRange);
}
let cnt = |frame_type: i32| {
count_refs(
frame_type,
avail_u,
above_ref_frame,
avail_l,
left_ref_frame,
)
};
let p1_ctx = ref_count_ctx(cnt(1) + cnt(2) + cnt(3) + cnt(4), cnt(5) + cnt(6) + cnt(7));
let single_ref_p1 = (ref0 >= 5) as u32;
let row = cdfs.single_ref_cdf(p1_ctx, 0);
writer.write_symbol(single_ref_p1, row)?;
if single_ref_p1 != 0 {
let p2_ctx = ref_count_ctx(cnt(5) + cnt(6), cnt(7));
let single_ref_p2 = (ref0 == 7) as u32;
let row = cdfs.single_ref_cdf(p2_ctx, 1);
writer.write_symbol(single_ref_p2, row)?;
if single_ref_p2 == 0 {
let p6_ctx = ref_count_ctx(cnt(5), cnt(6));
let single_ref_p6 = (ref0 == 6) as u32;
let row = cdfs.single_ref_cdf(p6_ctx, 5);
writer.write_symbol(single_ref_p6, row)?;
}
} else {
let p3_ctx = ref_count_ctx(cnt(1) + cnt(2), cnt(3) + cnt(4));
let single_ref_p3 = (ref0 >= 3) as u32;
let row = cdfs.single_ref_cdf(p3_ctx, 2);
writer.write_symbol(single_ref_p3, row)?;
if single_ref_p3 != 0 {
let p5_ctx = ref_count_ctx(cnt(3), cnt(4));
let single_ref_p5 = (ref0 == 4) as u32;
let row = cdfs.single_ref_cdf(p5_ctx, 4);
writer.write_symbol(single_ref_p5, row)?;
} else {
let p4_ctx = ref_count_ctx(cnt(1), cnt(2));
let single_ref_p4 = (ref0 == 2) as u32;
let row = cdfs.single_ref_cdf(p4_ctx, 3);
writer.write_symbol(single_ref_p4, row)?;
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_ref_frames(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
ref_frame: [i32; 2],
mi_size: usize,
skip_mode: u8,
skip_mode_frame: [i8; 2],
seg_ref_frame_active: bool,
seg_ref_frame_data: i8,
seg_skip_active: bool,
seg_globalmv_active: bool,
reference_select: bool,
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
) -> Result<(), Error> {
if skip_mode != 0 || seg_ref_frame_active || seg_skip_active || seg_globalmv_active {
let (forced0, forced1) = write_inter_block_mode_info_bootstrap(
skip_mode,
skip_mode_frame,
seg_ref_frame_active,
seg_ref_frame_data,
seg_skip_active,
seg_globalmv_active,
)?;
if ref_frame != [forced0 as i32, forced1 as i32] {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let comp_mode: u8 = match ref_frame[1] {
-1 => 0,
1..=7 => 1,
_ => return Err(Error::PartitionWalkOutOfRange),
};
let ctx = comp_mode_ctx(
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
);
write_comp_mode(writer, cdfs, comp_mode, mi_size, reference_select, ctx)?;
if comp_mode == 1 {
write_compound_ref_frames(
writer,
cdfs,
ref_frame,
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
)
} else {
write_single_ref_frames(
writer,
cdfs,
ref_frame,
avail_u,
avail_l,
above_ref_frame,
left_ref_frame,
)
}
}
pub fn write_inter_single_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
new_mv_context: usize,
zero_mv_context: usize,
ref_mv_context: usize,
) -> Result<(), Error> {
if !matches!(
y_mode,
MODE_NEWMV | MODE_GLOBALMV | MODE_NEARESTMV | MODE_NEARMV
) {
return Err(Error::PartitionWalkOutOfRange);
}
if new_mv_context >= NEW_MV_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let new_mv = (y_mode != MODE_NEWMV) as u32;
let row = cdfs.new_mv_cdf(new_mv_context);
writer.write_symbol(new_mv, row)?;
if new_mv == 0 {
return Ok(());
}
if zero_mv_context >= ZERO_MV_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let zero_mv = (y_mode != MODE_GLOBALMV) as u32;
let row = cdfs.zero_mv_cdf(zero_mv_context);
writer.write_symbol(zero_mv, row)?;
if zero_mv == 0 {
return Ok(());
}
if ref_mv_context >= REF_MV_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let ref_mv = (y_mode == MODE_NEARMV) as u32;
let row = cdfs.ref_mv_cdf(ref_mv_context);
writer.write_symbol(ref_mv, row)?;
Ok(())
}
pub fn write_compound_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
compound_mode_context: usize,
) -> Result<(), Error> {
if !(MODE_NEAREST_NEARESTMV..=MODE_NEW_NEWMV).contains(&y_mode) {
return Err(Error::PartitionWalkOutOfRange);
}
if compound_mode_context >= COMPOUND_MODE_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let compound_mode = (y_mode - MODE_NEAREST_NEARESTMV) as u32;
debug_assert!(
(compound_mode as usize) < COMPOUND_MODES,
"§5.11.23 compound_mode in 0..COMPOUND_MODES"
);
let row = cdfs.compound_mode_cdf(compound_mode_context);
writer.write_symbol(compound_mode, row)?;
Ok(())
}
#[inline]
fn has_nearmv(y_mode: u8) -> bool {
matches!(
y_mode,
MODE_NEARMV | MODE_NEAR_NEARMV | MODE_NEAR_NEWMV | MODE_NEW_NEARMV
)
}
pub fn write_drl_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
ref_mv_idx: u32,
num_mv_found: u32,
drl_ctx_stack: &[u32],
) -> Result<(), Error> {
if (ref_mv_idx as usize) >= MAX_REF_MV_STACK_SIZE {
return Err(Error::PartitionWalkOutOfRange);
}
if (num_mv_found as usize) > MAX_REF_MV_STACK_SIZE {
return Err(Error::PartitionWalkOutOfRange);
}
let start: u32 = if y_mode == MODE_NEWMV || y_mode == MODE_NEW_NEWMV {
if ref_mv_idx > 2 {
return Err(Error::PartitionWalkOutOfRange);
}
0
} else if has_nearmv(y_mode) {
if !(1..=3).contains(&ref_mv_idx) {
return Err(Error::PartitionWalkOutOfRange);
}
1
} else {
if ref_mv_idx != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
};
let mut reconstructed = start;
let mut idx = start;
while idx < start + 2 {
if num_mv_found > idx + 1 {
let slot = idx as usize;
let &ctx = drl_ctx_stack
.get(slot)
.ok_or(Error::PartitionWalkOutOfRange)?;
if ctx as usize >= DRL_MODE_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let drl_mode: u32 = (idx != ref_mv_idx) as u32;
let row = cdfs.drl_mode_cdf(ctx as usize);
writer.write_symbol(drl_mode, row)?;
if drl_mode == 0 {
reconstructed = idx;
break;
}
reconstructed = idx + 1;
}
idx += 1;
}
if reconstructed != ref_mv_idx {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_read_mv_component(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
mv_ctx: usize,
comp: usize,
diff: i32,
force_integer_mv: bool,
allow_high_precision_mv: bool,
) -> Result<(), Error> {
if mv_ctx >= MV_CONTEXTS || comp >= MV_COMPS {
return Err(Error::PartitionWalkOutOfRange);
}
if diff == 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let mv_sign: u32 = (diff < 0) as u32;
let mag: u32 = diff.unsigned_abs();
let offset: u32 = mag - 1;
let sign_row = cdfs.mv_sign_cdf(mv_ctx, comp);
writer.write_symbol(mv_sign, sign_row)?;
let mv_class: u32 = if offset < (1 << 4) {
MV_CLASS_0 as u32
} else {
floor_log2(offset) - 3
};
if mv_class as usize >= MV_CLASSES {
return Err(Error::PartitionWalkOutOfRange);
}
let class_row = cdfs.mv_class_cdf(mv_ctx, comp);
writer.write_symbol(mv_class, class_row)?;
if mv_class == MV_CLASS_0 as u32 {
let mv_class0_bit: u32 = (offset >> 3) & 0x1;
let mv_class0_fr: u32 = (offset >> 1) & 0x3;
let mv_class0_hp: u32 = offset & 0x1;
let bit_row = cdfs.mv_class0_bit_cdf(mv_ctx, comp);
writer.write_symbol(mv_class0_bit, bit_row)?;
if force_integer_mv {
if mv_class0_fr != 3 {
return Err(Error::PartitionWalkOutOfRange);
}
} else {
let fr_row = cdfs.mv_class0_fr_cdf(mv_ctx, comp, mv_class0_bit as usize);
writer.write_symbol(mv_class0_fr, fr_row)?;
}
if allow_high_precision_mv {
let hp_row = cdfs.mv_class0_hp_cdf(mv_ctx, comp);
writer.write_symbol(mv_class0_hp, hp_row)?;
} else if mv_class0_hp != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
} else {
let base: u32 = (CLASS0_SIZE as u32) << (mv_class + 2);
let x: u32 = offset - base;
let d: u32 = x >> 3;
let mv_fr: u32 = (x >> 1) & 0x3;
let mv_hp: u32 = x & 0x1;
for i in 0..(mv_class as usize) {
let mv_bit: u32 = (d >> i) & 0x1;
let bit_row = cdfs.mv_bit_cdf(mv_ctx, comp, i);
writer.write_symbol(mv_bit, bit_row)?;
}
if force_integer_mv {
if mv_fr != 3 {
return Err(Error::PartitionWalkOutOfRange);
}
} else {
let fr_row = cdfs.mv_fr_cdf(mv_ctx, comp);
writer.write_symbol(mv_fr, fr_row)?;
}
if allow_high_precision_mv {
let hp_row = cdfs.mv_hp_cdf(mv_ctx, comp);
writer.write_symbol(mv_hp, hp_row)?;
} else if mv_hp != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
}
Ok(())
}
pub fn write_read_mv(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
mv: [i32; 2],
pred_mv: [i32; 2],
use_intrabc: bool,
force_integer_mv: bool,
allow_high_precision_mv: bool,
) -> Result<(), Error> {
let mv_ctx: usize = if use_intrabc { MV_INTRABC_CONTEXT } else { 0 };
let diff0: i32 = mv[0].wrapping_sub(pred_mv[0]);
let diff1: i32 = mv[1].wrapping_sub(pred_mv[1]);
let mv_joint: u32 = match (diff0 != 0, diff1 != 0) {
(false, false) => MV_JOINT_ZERO as u32,
(false, true) => MV_JOINT_HNZVZ as u32,
(true, false) => MV_JOINT_HZVNZ as u32,
(true, true) => MV_JOINT_HNZVNZ as u32,
};
let joint_row = cdfs.mv_joint_cdf(mv_ctx);
writer.write_symbol(mv_joint, joint_row)?;
if diff0 != 0 {
write_read_mv_component(
writer,
cdfs,
mv_ctx,
0,
diff0,
force_integer_mv,
allow_high_precision_mv,
)?;
}
if diff1 != 0 {
write_read_mv_component(
writer,
cdfs,
mv_ctx,
1,
diff1,
force_integer_mv,
allow_high_precision_mv,
)?;
}
Ok(())
}
pub fn assign_mv_pred_mv(
mv_stack: &FindMvStackResult,
y_mode: u8,
ref_list: u8,
ref_mv_idx: u32,
) -> Result<[i32; 2], Error> {
if !(MODE_NEARESTMV..=MODE_NEW_NEWMV).contains(&y_mode) || ref_list > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let comp_mode = crate::cdf::get_mode(y_mode, ref_list as usize);
if comp_mode == MODE_GLOBALMV {
return Ok(mv_stack.global_mvs[ref_list as usize]);
}
let mut pos: u32 = if comp_mode == MODE_NEARESTMV {
0
} else {
ref_mv_idx
};
if comp_mode == MODE_NEWMV && mv_stack.num_mv_found <= 1 {
pos = 0;
}
if (pos >= 2 && pos >= mv_stack.num_mv_found) || pos as usize >= MAX_REF_MV_STACK_SIZE {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(mv_stack.ref_stack_mv[pos as usize][ref_list as usize])
}
pub fn assign_mv_pred_mv_intrabc(
mv_stack: &FindMvStackResult,
use_128x128_superblock: bool,
mi_row: u32,
mi_row_start: u32,
) -> Result<[i32; 2], Error> {
if mi_row < mi_row_start {
return Err(Error::PartitionWalkOutOfRange);
}
let mut pred_mv = mv_stack.ref_stack_mv[0][0];
if pred_mv == [0, 0] {
pred_mv = mv_stack.ref_stack_mv[1][0];
}
if pred_mv == [0, 0] {
let sb_size = if use_128x128_superblock {
BLOCK_128X128
} else {
BLOCK_64X64
};
let sb_size4 = NUM_4X4_BLOCKS_HIGH[sb_size] as u32;
if mi_row < mi_row_start + sb_size4 {
pred_mv = [
0,
-((sb_size4 as i32 * MI_SIZE as i32 + INTRABC_DELAY_PIXELS as i32) * 8),
];
} else {
pred_mv = [-(sb_size4 as i32 * MI_SIZE as i32 * 8), 0];
}
}
Ok(pred_mv)
}
pub fn write_use_intrabc(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
use_intrabc: u8,
allow_intrabc: bool,
) -> Result<(), Error> {
if use_intrabc > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if allow_intrabc {
let row = cdfs.intrabc_cdf();
writer.write_symbol(u32::from(use_intrabc), row)?;
} else if use_intrabc != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[derive(Debug, Clone, Copy)]
pub struct IntrabcArmInputs<'a> {
pub mv: [i32; 2],
pub mv_stack: &'a FindMvStackResult,
pub use_128x128_superblock: bool,
pub mi_row: u32,
pub mi_row_start: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IntrabcBlockInfo {
pub is_inter: u8,
pub y_mode: u8,
pub uv_mode: u8,
pub motion_mode: u8,
pub compound_type: u8,
pub palette_size_y: u8,
pub palette_size_uv: u8,
pub interp_filter: [u8; 2],
pub mv: [i32; 2],
pub pred_mv: [i32; 2],
}
pub fn write_intra_frame_intrabc_arm(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
allow_intrabc: bool,
intrabc: Option<&IntrabcArmInputs<'_>>,
) -> Result<Option<IntrabcBlockInfo>, Error> {
let use_intrabc = u8::from(intrabc.is_some());
write_use_intrabc(writer, cdfs, use_intrabc, allow_intrabc)?;
let Some(inputs) = intrabc else {
return Ok(None);
};
let pred_mv = assign_mv_pred_mv_intrabc(
inputs.mv_stack,
inputs.use_128x128_superblock,
inputs.mi_row,
inputs.mi_row_start,
)?;
write_read_mv(
writer, cdfs, inputs.mv, pred_mv, true,
true, false,
)?;
Ok(Some(IntrabcBlockInfo {
is_inter: 1,
y_mode: DC_PRED as u8,
uv_mode: DC_PRED as u8,
motion_mode: MOTION_MODE_SIMPLE,
compound_type: COMPOUND_AVERAGE,
palette_size_y: 0,
palette_size_uv: 0,
interp_filter: [BILINEAR, BILINEAR],
mv: inputs.mv,
pred_mv,
}))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterBlockModeInfoTail {
pub interintra: InterIntraReadout,
pub enable_interintra_compound: bool,
pub motion_mode: u8,
pub num_samples: u32,
pub is_motion_mode_switchable: bool,
pub allow_warped_motion: bool,
pub is_scaled_per_ref: [bool; 7],
pub has_overlappable: bool,
pub compound_type: CompoundTypeReadout,
pub enable_masked_compound: bool,
pub enable_jnt_comp: bool,
pub dist_equal: bool,
pub above_comp_group_idx: u8,
pub left_comp_group_idx: u8,
pub above_compound_idx: u8,
pub left_compound_idx: u8,
pub interp_filter: InterpolationFilterReadout,
pub interpolation_filter: u8,
pub enable_dual_filter: bool,
pub above_interp_filters: [u8; 2],
pub left_interp_filters: [u8; 2],
pub gm_type: [i32; 8],
}
impl InterBlockModeInfoTail {
pub fn bit_silent() -> Self {
Self {
interintra: InterIntraReadout {
interintra: 0,
interintra_mode: None,
wedge_interintra: None,
wedge_index: None,
},
enable_interintra_compound: false,
motion_mode: MOTION_MODE_SIMPLE,
num_samples: 0,
is_motion_mode_switchable: false,
allow_warped_motion: false,
is_scaled_per_ref: [false; 7],
has_overlappable: false,
compound_type: CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: COMPOUND_AVERAGE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
},
enable_masked_compound: false,
enable_jnt_comp: false,
dist_equal: false,
above_comp_group_idx: 0,
left_comp_group_idx: 0,
above_compound_idx: 1,
left_compound_idx: 1,
interp_filter: InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [false, false],
},
interpolation_filter: EIGHTTAP,
enable_dual_filter: false,
above_interp_filters: [EIGHTTAP, EIGHTTAP],
left_interp_filters: [EIGHTTAP, EIGHTTAP],
gm_type: [0; 8],
}
}
}
impl Default for InterBlockModeInfoTail {
fn default() -> Self {
Self::bit_silent()
}
}
#[allow(clippy::too_many_arguments)]
pub fn write_inter_mode_mv_prefix(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
ref_frame: [i32; 2],
y_mode: u8,
mv: [[i32; 2]; 2],
ref_mv_idx: u32,
mv_stack: &FindMvStackResult,
mi_size: usize,
skip_mode: u8,
skip_mode_frame: [i8; 2],
seg_ref_frame_active: bool,
seg_ref_frame_data: i8,
seg_skip_active: bool,
seg_globalmv_active: bool,
reference_select: bool,
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
force_integer_mv: bool,
allow_high_precision_mv: bool,
) -> Result<(), Error> {
write_ref_frames(
writer,
cdfs,
ref_frame,
mi_size,
skip_mode,
skip_mode_frame,
seg_ref_frame_active,
seg_ref_frame_data,
seg_skip_active,
seg_globalmv_active,
reference_select,
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
)?;
let is_compound = ref_frame[1] > 0;
if skip_mode != 0 {
if y_mode != MODE_NEAREST_NEARESTMV {
return Err(Error::PartitionWalkOutOfRange);
}
} else if seg_skip_active || seg_globalmv_active {
if y_mode != MODE_GLOBALMV {
return Err(Error::PartitionWalkOutOfRange);
}
} else if is_compound {
let cm_ctx = compound_mode_ctx(
mv_stack.ref_mv_context as usize,
mv_stack.new_mv_context as usize,
);
write_compound_mode(writer, cdfs, y_mode, cm_ctx)?;
} else {
write_inter_single_mode(
writer,
cdfs,
y_mode,
mv_stack.new_mv_context as usize,
mv_stack.zero_mv_context as usize,
mv_stack.ref_mv_context as usize,
)?;
}
write_drl_mode(
writer,
cdfs,
y_mode,
ref_mv_idx,
mv_stack.num_mv_found,
&mv_stack.drl_ctx_stack,
)?;
let list_count: u8 = if is_compound { 2 } else { 1 };
for i in 0..list_count {
if crate::cdf::get_mode(y_mode, i as usize) == MODE_NEWMV {
let pred_mv = assign_mv_pred_mv(mv_stack, y_mode, i, ref_mv_idx)?;
write_read_mv(
writer,
cdfs,
mv[i as usize],
pred_mv,
false,
force_integer_mv,
allow_high_precision_mv,
)?;
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_inter_block_mode_info(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
ref_frame: [i32; 2],
y_mode: u8,
mv: [[i32; 2]; 2],
ref_mv_idx: u32,
mv_stack: &FindMvStackResult,
mi_size: usize,
skip_mode: u8,
skip_mode_frame: [i8; 2],
seg_ref_frame_active: bool,
seg_ref_frame_data: i8,
seg_skip_active: bool,
seg_globalmv_active: bool,
reference_select: bool,
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
force_integer_mv: bool,
allow_high_precision_mv: bool,
tail: &InterBlockModeInfoTail,
) -> Result<(), Error> {
if skip_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
write_inter_mode_mv_prefix(
writer,
cdfs,
ref_frame,
y_mode,
mv,
ref_mv_idx,
mv_stack,
mi_size,
skip_mode,
skip_mode_frame,
seg_ref_frame_active,
seg_ref_frame_data,
seg_skip_active,
seg_globalmv_active,
reference_select,
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
force_integer_mv,
allow_high_precision_mv,
)?;
let is_compound = ref_frame[1] > 0;
write_interintra_mode(
writer,
cdfs,
&tail.interintra,
mi_size,
skip_mode,
is_compound,
tail.enable_interintra_compound,
)?;
let ref_frame = if tail.interintra.interintra == 1 {
[ref_frame[0], 0]
} else {
ref_frame
};
write_motion_mode(
writer,
cdfs,
tail.motion_mode,
mi_size,
skip_mode,
is_compound,
ref_frame,
y_mode,
tail.num_samples,
tail.is_motion_mode_switchable,
tail.allow_warped_motion,
force_integer_mv,
tail.gm_type,
tail.is_scaled_per_ref,
tail.has_overlappable,
)?;
write_compound_type(
writer,
cdfs,
&tail.compound_type,
mi_size,
skip_mode,
is_compound,
tail.interintra.interintra,
tail.interintra.wedge_interintra.unwrap_or(0),
tail.enable_masked_compound,
tail.enable_jnt_comp,
tail.dist_equal,
avail_u,
avail_l,
above_single,
left_single,
above_ref_frame[0] == 7,
left_ref_frame[0] == 7,
tail.above_comp_group_idx,
tail.left_comp_group_idx,
tail.above_compound_idx,
tail.left_compound_idx,
)?;
write_interpolation_filter(
writer,
cdfs,
&tail.interp_filter,
mi_size,
skip_mode,
is_compound,
ref_frame,
y_mode,
tail.motion_mode,
tail.interpolation_filter,
tail.enable_dual_filter,
tail.gm_type,
avail_u,
avail_l,
above_ref_frame,
left_ref_frame,
tail.above_interp_filters,
tail.left_interp_filters,
)?;
Ok(())
}
pub fn write_interintra_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
readout: &InterIntraReadout,
mi_size: usize,
skip_mode: u8,
is_compound: bool,
enable_interintra_compound: bool,
) -> Result<(), Error> {
if skip_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let gate_open = skip_mode == 0
&& enable_interintra_compound
&& !is_compound
&& (BLOCK_8X8..=BLOCK_32X32).contains(&mi_size);
if !gate_open {
if readout.interintra != 0
|| readout.interintra_mode.is_some()
|| readout.wedge_interintra.is_some()
|| readout.wedge_index.is_some()
{
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if readout.interintra > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let ctx = interintra_ctx(mi_size).ok_or(Error::PartitionWalkOutOfRange)?;
let row = cdfs
.inter_intra_cdf(ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(readout.interintra as u32, row)?;
if readout.interintra == 0 {
if readout.interintra_mode.is_some()
|| readout.wedge_interintra.is_some()
|| readout.wedge_index.is_some()
{
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let ii_mode = readout
.interintra_mode
.ok_or(Error::PartitionWalkOutOfRange)?;
if (ii_mode as usize) >= INTERINTRA_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
let wedge_ii = readout
.wedge_interintra
.ok_or(Error::PartitionWalkOutOfRange)?;
if wedge_ii > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if (wedge_ii == 1) != readout.wedge_index.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
if let Some(wi) = readout.wedge_index {
if (wi as usize) >= WEDGE_TYPES {
return Err(Error::PartitionWalkOutOfRange);
}
}
let row = cdfs
.inter_intra_mode_cdf(ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(ii_mode as u32, row)?;
let row = cdfs
.wedge_inter_intra_cdf(mi_size)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(wedge_ii as u32, row)?;
if let Some(wi) = readout.wedge_index {
let row = cdfs
.wedge_index_cdf(mi_size)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(wi as u32, row)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_motion_mode(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
motion_mode: u8,
mi_size: usize,
skip_mode: u8,
is_compound: bool,
ref_frame: [i32; 2],
y_mode: u8,
num_samples: u32,
is_motion_mode_switchable: bool,
allow_warped_motion: bool,
force_integer_mv: bool,
gm_type: [i32; 8],
is_scaled_per_ref: [bool; 7],
has_overlappable: bool,
) -> Result<(), Error> {
if skip_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let forced_simple = skip_mode != 0
|| !is_motion_mode_switchable
|| core::cmp::min(block_width(mi_size), block_height(mi_size)) < 8
|| (!force_integer_mv
&& (y_mode == MODE_GLOBALMV || y_mode == MODE_GLOBAL_GLOBALMV)
&& (0..8).contains(&ref_frame[0])
&& gm_type[ref_frame[0] as usize] > GM_TYPE_TRANSLATION)
|| is_compound
|| ref_frame[1] == 0
|| !has_overlappable;
if forced_simple {
if motion_mode != MOTION_MODE_SIMPLE {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let ref0 = ref_frame[0];
let scaled = if (1..=7).contains(&ref0) {
is_scaled_per_ref[(ref0 - 1) as usize]
} else {
false
};
if force_integer_mv || num_samples == 0 || !allow_warped_motion || scaled {
let use_obmc: u32 = match motion_mode {
MOTION_MODE_SIMPLE => 0,
MOTION_MODE_OBMC => 1,
_ => return Err(Error::PartitionWalkOutOfRange),
};
let row = cdfs
.use_obmc_cdf(mi_size)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(use_obmc, row)?;
} else {
if (motion_mode as usize) >= MOTION_MODES {
return Err(Error::PartitionWalkOutOfRange);
}
let row = cdfs
.motion_mode_cdf(mi_size)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(motion_mode as u32, row)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_compound_type(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
readout: &CompoundTypeReadout,
mi_size: usize,
skip_mode: u8,
is_compound: bool,
interintra: u8,
wedge_interintra: u8,
enable_masked_compound: bool,
enable_jnt_comp: bool,
dist_equal: bool,
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_ref_0_altref: bool,
left_ref_0_altref: bool,
above_comp_group_idx: u8,
left_comp_group_idx: u8,
above_compound_idx: u8,
left_compound_idx: u8,
) -> Result<(), Error> {
if skip_mode > 1
|| above_comp_group_idx > 1
|| left_comp_group_idx > 1
|| above_compound_idx > 1
|| left_compound_idx > 1
{
return Err(Error::PartitionWalkOutOfRange);
}
if skip_mode != 0 {
if readout.comp_group_idx != 0
|| readout.compound_idx != 1
|| readout.compound_type != COMPOUND_AVERAGE
|| readout.wedge_index.is_some()
|| readout.wedge_sign.is_some()
|| readout.mask_type.is_some()
{
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if !is_compound {
let expected = if interintra != 0 {
if wedge_interintra != 0 {
COMPOUND_WEDGE
} else {
COMPOUND_INTRA
}
} else {
COMPOUND_AVERAGE
};
if readout.comp_group_idx != 0
|| readout.compound_idx != 1
|| readout.compound_type != expected
|| readout.wedge_index.is_some()
|| readout.wedge_sign.is_some()
|| readout.mask_type.is_some()
{
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let n = wedge_bits(mi_size);
if enable_masked_compound {
if readout.comp_group_idx > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let ctx = comp_group_idx_ctx(
avail_u,
above_single,
above_comp_group_idx,
above_ref_0_altref,
avail_l,
left_single,
left_comp_group_idx,
left_ref_0_altref,
);
let row = cdfs.comp_group_idx_cdf(ctx);
writer.write_symbol(readout.comp_group_idx as u32, row)?;
} else if readout.comp_group_idx != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
if readout.comp_group_idx == 0 {
if enable_jnt_comp {
if readout.compound_idx > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let expected = if readout.compound_idx != 0 {
COMPOUND_AVERAGE
} else {
COMPOUND_DISTANCE
};
if readout.compound_type != expected {
return Err(Error::PartitionWalkOutOfRange);
}
let ctx = compound_idx_ctx(
dist_equal,
avail_u,
above_single,
above_compound_idx,
above_ref_0_altref,
avail_l,
left_single,
left_compound_idx,
left_ref_0_altref,
);
let row = cdfs.compound_idx_cdf(ctx);
writer.write_symbol(readout.compound_idx as u32, row)?;
} else if readout.compound_idx != 1 || readout.compound_type != COMPOUND_AVERAGE {
return Err(Error::PartitionWalkOutOfRange);
}
} else {
if readout.compound_idx != 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if n == 0 {
if readout.compound_type != COMPOUND_DIFFWTD {
return Err(Error::PartitionWalkOutOfRange);
}
} else {
if (readout.compound_type as usize) >= COMPOUND_TYPES {
return Err(Error::PartitionWalkOutOfRange);
}
let row = cdfs
.compound_type_cdf(mi_size)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(readout.compound_type as u32, row)?;
}
}
if readout.compound_type == COMPOUND_WEDGE {
let wi = readout.wedge_index.ok_or(Error::PartitionWalkOutOfRange)?;
let ws = readout.wedge_sign.ok_or(Error::PartitionWalkOutOfRange)?;
if (wi as usize) >= WEDGE_TYPES || ws > 1 || readout.mask_type.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
let row = cdfs
.wedge_index_cdf(mi_size)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(wi as u32, row)?;
writer.write_literal(1, ws as u32)?;
} else if readout.compound_type == COMPOUND_DIFFWTD {
let mt = readout.mask_type.ok_or(Error::PartitionWalkOutOfRange)?;
if mt > 1 || readout.wedge_index.is_some() || readout.wedge_sign.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_literal(1, mt as u32)?;
} else if readout.wedge_index.is_some()
|| readout.wedge_sign.is_some()
|| readout.mask_type.is_some()
{
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_interpolation_filter(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
readout: &InterpolationFilterReadout,
mi_size: usize,
skip_mode: u8,
is_compound: bool,
ref_frame: [i32; 2],
y_mode: u8,
motion_mode: u8,
interpolation_filter: u8,
enable_dual_filter: bool,
gm_type: [i32; 8],
avail_u: bool,
avail_l: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
above_interp_filters: [u8; 2],
left_interp_filters: [u8; 2],
) -> Result<(), Error> {
if skip_mode > 1 || mi_size >= BLOCK_SIZES || interpolation_filter > SWITCHABLE {
return Err(Error::PartitionWalkOutOfRange);
}
if !(0..=7).contains(&ref_frame[0]) || !(-1..=7).contains(&ref_frame[1]) {
return Err(Error::PartitionWalkOutOfRange);
}
if above_interp_filters.iter().any(|&f| f > BILINEAR)
|| left_interp_filters.iter().any(|&f| f > BILINEAR)
{
return Err(Error::PartitionWalkOutOfRange);
}
if interpolation_filter != SWITCHABLE {
if readout.interp_filter != [interpolation_filter, interpolation_filter]
|| readout.read_from_bitstream != [false, false]
{
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let large = core::cmp::min(block_width(mi_size), block_height(mi_size)) >= 8;
let needs = if skip_mode != 0 || motion_mode == MOTION_MODE_WARPED_CAUSAL {
false
} else if large && y_mode == MODE_GLOBALMV {
let r0 = ref_frame[0];
(0..8).contains(&r0) && gm_type[r0 as usize] == GM_TYPE_TRANSLATION
} else if large && y_mode == MODE_GLOBAL_GLOBALMV {
let r0 = ref_frame[0];
let r1 = ref_frame[1];
let t0 = (0..8).contains(&r0) && gm_type[r0 as usize] == GM_TYPE_TRANSLATION;
let t1 = (0..8).contains(&r1) && gm_type[r1 as usize] == GM_TYPE_TRANSLATION;
t0 || t1
} else {
true
};
let dir_count = if enable_dual_filter { 2usize } else { 1 };
for dir in 0..dir_count {
if !needs {
if readout.interp_filter[dir] != EIGHTTAP || readout.read_from_bitstream[dir] {
return Err(Error::PartitionWalkOutOfRange);
}
continue;
}
if !readout.read_from_bitstream[dir]
|| (readout.interp_filter[dir] as usize) >= INTERP_FILTERS
{
return Err(Error::PartitionWalkOutOfRange);
}
let above_type = if avail_u
&& (above_ref_frame[0] == ref_frame[0] || above_ref_frame[1] == ref_frame[0])
{
above_interp_filters[dir] as usize
} else {
INTERP_FILTER_NONE
};
let left_type = if avail_l
&& (left_ref_frame[0] == ref_frame[0] || left_ref_frame[1] == ref_frame[0])
{
left_interp_filters[dir] as usize
} else {
INTERP_FILTER_NONE
};
let ctx = interp_filter_ctx(above_type, left_type, dir as u32, is_compound)
.ok_or(Error::PartitionWalkOutOfRange)?;
let row = cdfs
.interp_filter_cdf(ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(readout.interp_filter[dir] as u32, row)?;
}
if !enable_dual_filter
&& (readout.interp_filter[1] != readout.interp_filter[0] || readout.read_from_bitstream[1])
{
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[inline]
fn floor_log2(mut x: u32) -> u32 {
debug_assert!(x != 0, "§4.7 FloorLog2 requires x != 0");
let mut s: u32 = 0;
while x != 0 {
x >>= 1;
s += 1;
}
s - 1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cdf::{
cfl_allowed_for_uv_mode, comp_mode_ctx, intra_mode_ctx, is_inter_ctx, size_group, skip_ctx,
skip_mode_ctx, PartitionWalker, TileGeometry, BLOCK_16X16, BLOCK_4X4, BLOCK_4X8, BLOCK_8X4,
BLOCK_8X8, DC_PRED, MODE_GLOBAL_GLOBALMV, MODE_NEAREST_NEWMV, MODE_NEAR_NEARMV,
MODE_NEAR_NEWMV, MODE_NEW_NEARESTMV, MODE_NEW_NEARMV, V_PRED,
};
use crate::symbol_decoder::SymbolDecoder;
const DC_PRED_U8: u8 = DC_PRED as u8;
const V_PRED_U8: u8 = V_PRED as u8;
fn fresh_walker_and_cdfs() -> (PartitionWalker, TileCdfContext) {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
let cdfs = TileCdfContext::new_from_defaults();
(walker, cdfs)
}
#[test]
fn write_skip_seg_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip(&mut writer, &mut enc_cdfs, 1, 0, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, true)
.unwrap();
assert_eq!(skip, 1, "§5.11.11 seg short-circuit returns skip = 1");
}
#[test]
fn write_skip_zero_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = skip_ctx(0, 0);
write_skip(&mut writer, &mut enc_cdfs, 0, ctx, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
assert_eq!(skip, 0);
}
#[test]
fn write_skip_one_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = skip_ctx(0, 0);
write_skip(&mut writer, &mut enc_cdfs, 1, ctx, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
assert_eq!(skip, 1);
}
#[test]
fn write_skip_rejects_skip_zero_on_seg_arm() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_skip(&mut writer, &mut enc_cdfs, 0, 0, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_frame_y_mode_dc_pred_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let abovemode_ctx = intra_mode_ctx(DC_PRED);
let leftmode_ctx = intra_mode_ctx(DC_PRED);
write_intra_frame_y_mode(
&mut writer,
&mut enc_cdfs,
DC_PRED_U8,
abovemode_ctx,
leftmode_ctx,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let y_mode = walker
.decode_intra_frame_y_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(y_mode, DC_PRED_U8);
}
#[test]
fn write_intra_frame_y_mode_v_pred_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let abovemode_ctx = intra_mode_ctx(DC_PRED);
let leftmode_ctx = intra_mode_ctx(DC_PRED);
write_intra_frame_y_mode(
&mut writer,
&mut enc_cdfs,
V_PRED_U8,
abovemode_ctx,
leftmode_ctx,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let y_mode = walker
.decode_intra_frame_y_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(y_mode, V_PRED_U8);
}
#[test]
fn write_intra_frame_y_mode_rejects_out_of_range() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_frame_y_mode(&mut writer, &mut enc_cdfs, 13, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_y_mode_and_uv_mode_dc_pred_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let y_size_group = size_group(BLOCK_16X16);
write_y_mode(&mut writer, &mut enc_cdfs, DC_PRED_U8, y_size_group).unwrap();
let cfl_allowed = cfl_allowed_for_uv_mode(false, BLOCK_16X16, false, false);
write_intra_uv_mode(
&mut writer,
&mut enc_cdfs,
DC_PRED_U8,
DC_PRED_U8,
cfl_allowed,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8, "round-tripped y_mode");
assert_eq!(info.uv_mode, Some(DC_PRED_U8), "round-tripped uv_mode");
assert_eq!(info.angle_delta_y, 0);
assert_eq!(info.angle_delta_uv, Some(0));
assert_eq!(info.cfl_alpha_u, None);
assert_eq!(info.cfl_alpha_v, None);
assert_eq!(info.has_palette_y, None);
assert_eq!(info.use_filter_intra, None);
}
#[test]
fn write_y_mode_only_monochrome_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = size_group(BLOCK_16X16);
write_y_mode(&mut writer, &mut enc_cdfs, DC_PRED_U8, ctx).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
false,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, None);
}
#[test]
fn write_intra_segment_id_disabled_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_intra_segment_id(&mut writer, &mut enc_cdfs, 0, 0, 0, 0, false, 0).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _) = walker
.decode_intra_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
0,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0);
}
#[test]
fn write_intra_segment_id_skip_short_circuit() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_intra_segment_id(&mut writer, &mut enc_cdfs, 0, 1, 0, 0, true, 7).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _) = walker
.decode_intra_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
1,
true,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0, "skip arm ⇒ segment_id = pred = 0 at origin");
}
#[test]
fn write_intra_segment_id_else_branch_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_segment_id(
&mut writer,
&mut enc_cdfs,
3,
0,
0,
0,
true,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _) = walker
.decode_intra_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 3);
}
#[test]
fn write_segment_id_skip_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_segment_id(
&mut writer,
&mut enc_cdfs,
0,
1,
0,
0,
7,
)
.unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let sid = walker
.decode_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
1,
7,
)
.unwrap();
assert_eq!(sid, 0);
assert_eq!(dec.position(), pos_before, "no S() read on skip path");
}
#[test]
fn write_segment_id_else_branch_at_origin_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_segment_id(
&mut writer,
&mut enc_cdfs,
5,
0,
0,
0,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let sid = walker
.decode_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
7,
)
.unwrap();
assert_eq!(sid, 5);
}
#[test]
fn write_segment_id_else_branch_upward_fan_round_trip() {
for sid_in in 0..8u8 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_segment_id(
&mut writer,
&mut enc_cdfs,
2,
0,
0,
0,
7,
)
.unwrap();
write_segment_id(
&mut writer,
&mut enc_cdfs,
sid_in,
0,
2,
0,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let sid_first = walker
.decode_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
7,
)
.unwrap();
assert_eq!(sid_first, 2);
let sid_out = walker
.decode_segment_id(
&mut dec,
&mut dec_cdfs,
4,
0,
BLOCK_16X16,
0,
7,
)
.unwrap();
assert_eq!(sid_out, sid_in, "round trip failed at sid = {}", sid_in);
}
}
#[test]
fn write_segment_id_else_branch_downward_fan_round_trip() {
for sid_in in 0..8u8 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_segment_id(
&mut writer,
&mut enc_cdfs,
5,
0,
0,
0,
7,
)
.unwrap();
write_segment_id(
&mut writer,
&mut enc_cdfs,
sid_in,
0,
5,
0,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let sid_first = walker
.decode_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
7,
)
.unwrap();
assert_eq!(sid_first, 5);
let sid_out = walker
.decode_segment_id(
&mut dec,
&mut dec_cdfs,
4,
0,
BLOCK_16X16,
0,
7,
)
.unwrap();
assert_eq!(sid_out, sid_in, "round trip failed at sid = {}", sid_in);
}
}
#[test]
fn write_segment_id_rejects_skip_arm_mismatch() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_segment_id(
&mut writer,
&mut enc_cdfs,
3,
1,
0, 0,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_segment_id_rejects_segment_id_out_of_alphabet() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_segment_id(
&mut writer,
&mut enc_cdfs,
5,
0,
0,
0,
3, )
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_segment_id_rejects_pred_out_of_alphabet() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_segment_id(
&mut writer,
&mut enc_cdfs,
0,
0,
5,
0,
3,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_segment_id_rejects_out_of_range_last_active() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_segment_id(
&mut writer,
&mut enc_cdfs,
0,
0,
0,
0,
8,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_segment_id_rejects_out_of_range_ctx() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_segment_id(
&mut writer,
&mut enc_cdfs,
0,
0,
0,
3,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn intra_prefix_plus_y_mode_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let skip_ctx_val = skip_ctx(0, 0);
write_skip(&mut writer, &mut enc_cdfs, 0, skip_ctx_val, false).unwrap();
write_intra_segment_id(&mut writer, &mut enc_cdfs, 0, 0, 0, 0, false, 0).unwrap();
let y_ctx = size_group(BLOCK_8X8);
write_y_mode(&mut writer, &mut enc_cdfs, DC_PRED_U8, y_ctx).unwrap();
let cfl_allowed = cfl_allowed_for_uv_mode(false, BLOCK_8X8, false, false);
write_intra_uv_mode(
&mut writer,
&mut enc_cdfs,
DC_PRED_U8,
DC_PRED_U8,
cfl_allowed,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_8X8, false)
.unwrap();
assert_eq!(skip, 0);
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _) = walker
.decode_intra_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_8X8,
skip,
false,
0,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0);
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_8X8,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, Some(DC_PRED_U8));
}
fn round_trip_cfl_alphas(alpha_u: i8, alpha_v: i8) -> (Option<i8>, Option<i8>) {
use crate::cdf::UV_CFL_PRED;
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_skip(&mut writer, &mut enc_cdfs, 0, skip_ctx(0, 0), false).unwrap();
write_intra_segment_id(&mut writer, &mut enc_cdfs, 0, 0, 0, 0, false, 0).unwrap();
let y_ctx = size_group(BLOCK_16X16);
write_y_mode(&mut writer, &mut enc_cdfs, DC_PRED_U8, y_ctx).unwrap();
let cfl_allowed = cfl_allowed_for_uv_mode(false, BLOCK_16X16, false, false);
write_intra_uv_mode(
&mut writer,
&mut enc_cdfs,
DC_PRED_U8,
UV_CFL_PRED as u8,
cfl_allowed,
)
.unwrap();
write_cfl_alphas(&mut writer, &mut enc_cdfs, alpha_u, alpha_v).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let _skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.uv_mode, Some(UV_CFL_PRED as u8));
(info.cfl_alpha_u, info.cfl_alpha_v)
}
#[test]
fn write_cfl_alphas_round_trip_pos_pos() {
let (au, av) = round_trip_cfl_alphas(1, 1);
assert_eq!(au, Some(1));
assert_eq!(av, Some(1));
}
#[test]
fn write_cfl_alphas_round_trip_neg_neg() {
let (au, av) = round_trip_cfl_alphas(-3, -7);
assert_eq!(au, Some(-3));
assert_eq!(av, Some(-7));
}
#[test]
fn write_cfl_alphas_round_trip_zero_u_pos_v() {
let (au, av) = round_trip_cfl_alphas(0, 4);
assert_eq!(au, Some(0));
assert_eq!(av, Some(4));
}
#[test]
fn write_cfl_alphas_round_trip_neg_u_zero_v() {
let (au, av) = round_trip_cfl_alphas(-2, 0);
assert_eq!(au, Some(-2));
assert_eq!(av, Some(0));
}
#[test]
fn write_cfl_alphas_round_trip_max_magnitudes() {
let (au, av) = round_trip_cfl_alphas(16, -16);
assert_eq!(au, Some(16));
assert_eq!(av, Some(-16));
}
#[test]
fn write_cfl_alphas_rejects_both_zero() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_cfl_alphas(&mut writer, &mut cdfs, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_cfl_alphas_rejects_out_of_range_magnitude() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_cfl_alphas(&mut writer, &mut cdfs, 17, 1).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_is_inter_skip_mode_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_is_inter(&mut writer, &mut enc_cdfs, 1, 0, 1, false, false, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
1,
false,
false,
false,
)
.unwrap();
assert_eq!(is_inter, 1, "§5.11.20 Arm 1: skip_mode = 1 ⇒ is_inter = 1");
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the skip_mode arm"
);
}
#[test]
fn write_is_inter_seg_ref_frame_intra_routes_to_zero_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_is_inter(&mut writer, &mut enc_cdfs, 0, 0, 0, true, false, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
false,
false,
)
.unwrap();
assert_eq!(
is_inter, 0,
"Arm 2: FeatureData == INTRA_FRAME ⇒ is_inter = 0"
);
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_is_inter_seg_ref_frame_inter_routes_to_one_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_is_inter(&mut writer, &mut enc_cdfs, 1, 0, 0, true, true, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
)
.unwrap();
assert_eq!(
is_inter, 1,
"Arm 2: FeatureData != INTRA_FRAME ⇒ is_inter = 1"
);
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_is_inter_seg_globalmv_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_is_inter(&mut writer, &mut enc_cdfs, 1, 0, 0, false, false, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
true,
)
.unwrap();
assert_eq!(is_inter, 1, "Arm 3: SEG_LVL_GLOBALMV ⇒ is_inter = 1");
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_is_inter_zero_else_branch_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = is_inter_ctx(None, None);
write_is_inter(&mut writer, &mut enc_cdfs, 0, ctx, 0, false, false, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(is_inter, 0);
}
#[test]
fn write_is_inter_one_else_branch_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = is_inter_ctx(None, None);
write_is_inter(&mut writer, &mut enc_cdfs, 1, ctx, 0, false, false, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(is_inter, 1);
}
#[test]
fn write_is_inter_else_branch_round_trip_at_origin_both_is_inter_values() {
for is_inter_val in [0u8, 1u8] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = is_inter_ctx(None, None);
assert_eq!(ctx, 0, "origin neighbours unavailable ⇒ ctx = 0");
assert!(ctx < IS_INTER_CONTEXTS);
write_is_inter(
&mut writer,
&mut enc_cdfs,
is_inter_val,
ctx,
0,
false,
false,
false,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let decoded = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(decoded, is_inter_val);
}
}
#[test]
fn write_is_inter_accepts_every_8_3_2_ctx() {
let cases: &[(Option<bool>, Option<bool>)] = &[
(None, None),
(Some(true), None),
(Some(false), None),
(None, Some(true)),
(None, Some(false)),
(Some(true), Some(true)),
(Some(true), Some(false)),
(Some(false), Some(true)),
(Some(false), Some(false)),
];
for &(above, left) in cases {
let ctx = is_inter_ctx(above, left);
assert!(ctx < IS_INTER_CONTEXTS);
for is_inter_val in [0u8, 1u8] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_is_inter(
&mut writer,
&mut cdfs,
is_inter_val,
ctx,
0,
false,
false,
false,
)
.expect("§8.3.2 ctx admissible on Arm 4");
let _bytes = writer.finish();
}
}
}
#[test]
fn write_is_inter_rejects_out_of_range_is_inter() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_is_inter(&mut writer, &mut cdfs, 2, 0, 0, false, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_is_inter_rejects_zero_on_skip_mode_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_is_inter(&mut writer, &mut cdfs, 0, 0, 1, false, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_is_inter_rejects_arm2_mismatch() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_is_inter(&mut writer, &mut cdfs, 1, 0, 0, true, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_is_inter_rejects_zero_on_globalmv_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_is_inter(&mut writer, &mut cdfs, 0, 0, 0, false, false, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_is_inter_rejects_out_of_range_ctx_on_else_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_is_inter(
&mut writer,
&mut cdfs,
0,
IS_INTER_CONTEXTS,
0,
false,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_is_inter_arm1_skips_later_arm_checks() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_is_inter(
&mut writer,
&mut cdfs,
1,
999, 1, true,
false,
true,
)
.unwrap();
}
#[test]
fn write_skip_mode_seg_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip_mode(&mut writer, &mut enc_cdfs, 0, 0, BLOCK_16X16, true, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, true, true)
.unwrap();
assert_eq!(skip_mode, 0, "§5.11.10 seg-arm: any-active ⇒ skip_mode = 0");
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the seg short-circuit arm"
);
}
#[test]
fn write_skip_mode_present_false_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip_mode(&mut writer, &mut enc_cdfs, 0, 0, BLOCK_16X16, false, false).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false, false)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_skip_mode_small_width_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip_mode(&mut writer, &mut enc_cdfs, 0, 0, BLOCK_4X8, false, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_4X8, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_skip_mode_small_height_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip_mode(&mut writer, &mut enc_cdfs, 0, 0, BLOCK_8X4, false, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_8X4, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_skip_mode_small_4x4_short_circuit_writes_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip_mode(&mut writer, &mut enc_cdfs, 0, 0, BLOCK_4X4, false, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_4X4, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
#[test]
fn write_skip_mode_zero_else_branch_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = skip_mode_ctx(0, 0);
assert_eq!(ctx, 0, "origin neighbours unavailable ⇒ ctx = 0");
write_skip_mode(&mut writer, &mut enc_cdfs, 0, ctx, BLOCK_16X16, false, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
}
#[test]
fn write_skip_mode_one_else_branch_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = skip_mode_ctx(0, 0);
write_skip_mode(&mut writer, &mut enc_cdfs, 1, ctx, BLOCK_16X16, false, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(skip_mode, 1);
}
#[test]
fn write_skip_mode_accepts_every_8_3_2_ctx() {
let cases: &[(u8, u8)] = &[(0, 0), (0, 1), (1, 0), (1, 1)];
for &(above, left) in cases {
let ctx = skip_mode_ctx(above, left);
assert!(ctx < SKIP_MODE_CONTEXTS);
for skip_mode_val in [0u8, 1u8] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_skip_mode(
&mut writer,
&mut cdfs,
skip_mode_val,
ctx,
BLOCK_16X16,
false,
true,
)
.expect("§8.3.2 ctx admissible on the fall-through arm");
let _bytes = writer.finish();
}
}
}
#[test]
fn write_skip_mode_rejects_out_of_range_skip_mode() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_skip_mode(&mut writer, &mut cdfs, 2, 0, BLOCK_16X16, false, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_skip_mode_rejects_out_of_range_sub_size() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_skip_mode(&mut writer, &mut cdfs, 0, 0, BLOCK_SIZES, false, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_skip_mode_rejects_one_on_seg_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err =
write_skip_mode(&mut writer, &mut cdfs, 1, 0, BLOCK_16X16, true, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_skip_mode_rejects_one_on_present_false_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err =
write_skip_mode(&mut writer, &mut cdfs, 1, 0, BLOCK_16X16, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_skip_mode_rejects_one_on_small_block_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err =
write_skip_mode(&mut writer, &mut cdfs, 1, 0, BLOCK_4X4, false, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_skip_mode_rejects_out_of_range_ctx_on_else_arm() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_skip_mode(
&mut writer,
&mut cdfs,
0,
SKIP_MODE_CONTEXTS,
BLOCK_16X16,
false,
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_skip_mode_short_circuit_skips_ctx_check() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_skip_mode(
&mut writer,
&mut cdfs,
0,
999, BLOCK_16X16,
true, true,
)
.unwrap();
}
const LOSSLESS_ALL_FALSE: [bool; MAX_SEGMENTS] = [false; MAX_SEGMENTS];
fn pad_no_symbol(bytes: Vec<u8>) -> Vec<u8> {
if bytes.is_empty() {
vec![0u8]
} else {
bytes
}
}
#[test]
fn write_inter_segment_id_disabled_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
0,
0,
0,
0,
true,
0,
0,
false,
true,
true,
true,
0,
7,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let pos_before = dec.position();
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
true,
0,
true,
false,
true,
true,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(sid, 0);
assert!(!lossless);
assert_eq!(dec.position(), pos_before, "no S() read on disabled arm");
}
#[test]
fn write_inter_segment_id_no_update_map_adopts_predicted() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
4,
0,
0,
0,
false,
0,
0,
true,
false,
false,
false,
4,
7,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let pos_before = dec.position();
let (sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
0,
false,
true,
false,
false,
4,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(sid, 4);
assert_eq!(dec.position(), pos_before, "no S() read on !update_map arm");
}
#[test]
fn write_inter_segment_id_pre_skip_early_exit_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
0,
0,
0,
0,
true,
0,
0,
true,
true,
false,
false,
3,
7,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let pos_before = dec.position();
let (sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
true,
0,
false,
true,
true,
false,
3,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(sid, 0);
assert_eq!(
dec.position(),
pos_before,
"no S() read on pre-skip early-exit"
);
}
#[test]
fn write_inter_segment_id_arm4_skip_branch_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
0,
0,
0,
0,
false,
1,
0,
true,
true,
true,
true,
0,
7,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let pos_before = dec.position();
let (sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
1,
true,
true,
true,
true,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(sid, 0);
assert_eq!(
dec.position(),
pos_before,
"Arm 4: no S() read (inner write_segment_id short-circuits on skip=1)"
);
}
#[test]
fn write_inter_segment_id_temporal_update_adopt_round_trip() {
for predicted in 0..8u8 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
predicted,
0, 0,
0,
false,
0,
1,
true,
true,
true,
true,
predicted,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let (sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
0,
true,
true,
true,
true,
predicted,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(
sid, predicted,
"adopt round trip failed at predicted = {}",
predicted
);
}
}
#[test]
fn write_inter_segment_id_temporal_update_literal_round_trip() {
for sid_in in 0..8u8 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
sid_in,
0,
0,
0,
false,
0,
0,
true,
true,
true,
true,
0,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let (sid_out, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
0,
true,
true,
true,
true,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(
sid_out, sid_in,
"literal round trip failed at sid = {}",
sid_in
);
}
}
#[test]
fn write_inter_segment_id_fallthrough_round_trip() {
for sid_in in 0..8u8 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
sid_in,
0,
0,
0,
false,
0,
0,
true,
true,
false,
true,
0,
7,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let (sid_out, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
0,
true,
true,
true,
false,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
assert_eq!(
sid_out, sid_in,
"arm 6 fall-through round trip failed at sid = {}",
sid_in
);
}
}
#[test]
fn write_inter_segment_id_arm1_rejects_nonzero_segment_id() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
3,
0,
0,
0,
true,
0,
0,
false,
true,
true,
true,
0,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_arm2_rejects_segment_id_mismatch() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
2,
0,
0,
0,
false,
0,
0,
true,
false,
false,
false,
5,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_arm3_rejects_nonzero_segment_id() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
3,
0,
0,
0,
true,
0,
0,
true,
true,
false,
false,
3,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_arm4_rejects_pred_mismatch() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
3,
1, 0,
0,
false,
1,
0,
true,
true,
true,
true,
0,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_arm5_adopt_rejects_predicted_mismatch() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
4,
0,
0,
0,
false,
0,
1,
true,
true,
true,
true,
2,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_arm5_rejects_out_of_range_seg_id_predicted() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
0,
0,
0,
0,
false,
0,
2,
true,
true,
true,
true,
0,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_arm5_rejects_out_of_range_seg_pred_ctx() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
0,
0,
0,
SEGMENT_ID_PREDICTED_CONTEXTS,
false,
0,
0,
true,
true,
true,
true,
0,
7,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_rejects_out_of_range_last_active() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
0,
0,
0,
0,
false,
0,
0,
true,
true,
false,
true,
0,
MAX_SEGMENTS as u8,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_rejects_segment_id_out_of_alphabet() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
5,
0,
0,
0,
false,
0,
0,
true,
true,
false,
true,
0,
3,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_rejects_pred_out_of_alphabet() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
0,
5,
0,
0,
false,
0,
0,
true,
true,
false,
true,
0,
3,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_rejects_predicted_segment_id_out_of_alphabet() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_segment_id(
&mut writer,
&mut cdfs,
0,
0,
0,
0,
false,
0,
0,
true,
true,
false,
true,
5,
3,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_segment_id_accepts_every_seg_pred_ctx() {
for ctx in 0..SEGMENT_ID_PREDICTED_CONTEXTS {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_segment_id(
&mut writer,
&mut enc_cdfs,
3,
0,
0,
ctx,
false,
0,
1,
true,
true,
true,
true,
3,
7,
)
.unwrap();
let _bytes = writer.finish();
}
}
#[allow(clippy::too_many_arguments)]
fn replay_inter_frame_mode_info_prefix(
bytes: Vec<u8>,
cdf_disable_update: bool,
sub_size: usize,
seg_id_pre_skip: bool,
segmentation_enabled: bool,
segmentation_update_map: bool,
segmentation_temporal_update: bool,
predicted_segment_id: u8,
last_active_seg_id: u8,
skip_mode_present: bool,
seg_skip_mode_off: bool,
seg_skip_active: bool,
seg_ref_frame_active: bool,
seg_ref_frame_is_inter: bool,
seg_globalmv_active: bool,
) -> (u8, u8, u8, u8) {
let pad = pad_no_symbol(bytes);
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), cdf_disable_update).unwrap();
let (mut segment_id, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
sub_size,
true,
0,
seg_id_pre_skip,
segmentation_enabled,
segmentation_update_map,
segmentation_temporal_update,
predicted_segment_id,
last_active_seg_id,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
let skip_mode = walker
.decode_skip_mode(
&mut dec,
&mut dec_cdfs,
0,
0,
sub_size,
seg_skip_mode_off,
skip_mode_present,
)
.unwrap();
let skip: u8 = if skip_mode != 0 {
1
} else {
walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, sub_size, seg_skip_active)
.unwrap()
};
if !seg_id_pre_skip {
let (sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
sub_size,
false,
skip,
seg_id_pre_skip,
segmentation_enabled,
segmentation_update_map,
segmentation_temporal_update,
predicted_segment_id,
last_active_seg_id,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
segment_id = sid;
}
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
sub_size,
skip_mode,
seg_ref_frame_active,
seg_ref_frame_is_inter,
seg_globalmv_active,
)
.unwrap();
(segment_id, skip_mode, skip, is_inter)
}
#[test]
fn write_inter_frame_mode_info_prefix_segless_round_trip() {
for (skip_in, is_inter_in) in [(0u8, 0u8), (0u8, 1u8), (1u8, 0u8), (1u8, 1u8)]
.iter()
.copied()
{
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
skip_in,
is_inter_in,
0,
0,
0,
skip_ctx(0, 0),
0,
0,
is_inter_ctx(None, None),
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
false,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap();
assert_eq!(prefix.segment_id, 0);
assert_eq!(prefix.skip_mode, 0);
assert_eq!(prefix.skip, skip_in);
assert_eq!(prefix.is_inter, is_inter_in);
assert!(!prefix.lossless);
let (sid, skip_mode, skip, is_inter) = replay_inter_frame_mode_info_prefix(
writer.finish(),
false,
BLOCK_16X16,
true,
false,
false,
false,
0,
7,
false,
false,
false,
false,
false,
false,
);
assert_eq!(sid, 0);
assert_eq!(skip_mode, 0);
assert_eq!(skip, skip_in);
assert_eq!(is_inter, is_inter_in);
}
}
#[test]
fn write_inter_frame_mode_info_prefix_skip_mode_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
true,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap();
assert_eq!(prefix.segment_id, 0);
assert_eq!(prefix.skip_mode, 1);
assert_eq!(prefix.skip, 1);
assert_eq!(prefix.is_inter, 1);
let (sid, skip_mode, skip, is_inter) = replay_inter_frame_mode_info_prefix(
writer.finish(),
false,
BLOCK_16X16,
true,
false,
false,
false,
0,
7,
true,
false,
false,
false,
false,
false,
);
assert_eq!(sid, 0);
assert_eq!(skip_mode, 1);
assert_eq!(skip, 1);
assert_eq!(is_inter, 1);
}
#[test]
fn write_inter_frame_mode_info_prefix_post_skip_arm_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
3,
0,
0,
1,
0,
1,
0,
0,
0,
0,
0,
false,
false,
false,
false,
false,
true,
true,
true,
false,
3,
7,
true,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap();
assert_eq!(prefix.segment_id, 3);
assert_eq!(prefix.skip_mode, 0);
assert_eq!(prefix.skip, 0);
assert_eq!(prefix.is_inter, 1);
let (sid, skip_mode, skip, is_inter) = replay_inter_frame_mode_info_prefix(
writer.finish(),
false,
BLOCK_16X16,
false,
true,
true,
true,
3,
7,
true,
false,
false,
false,
false,
false,
);
assert_eq!(sid, 3);
assert_eq!(skip_mode, 0);
assert_eq!(skip, 0);
assert_eq!(is_inter, 1);
}
#[test]
fn write_inter_frame_mode_info_prefix_seg_ref_frame_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
2,
0,
1,
1, 0,
1,
0,
0,
0,
0,
0,
true,
true,
true,
true,
false,
true,
true,
true,
true,
2,
7,
true,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap();
assert_eq!(prefix.segment_id, 2);
assert_eq!(prefix.skip_mode, 0);
assert_eq!(prefix.skip, 1);
assert_eq!(prefix.is_inter, 1);
let (sid, skip_mode, skip, is_inter) = replay_inter_frame_mode_info_prefix(
writer.finish(),
false,
BLOCK_16X16,
true,
true,
true,
true,
2,
7,
true,
true,
true,
true,
true,
false,
);
assert_eq!(sid, 2);
assert_eq!(skip_mode, 0);
assert_eq!(skip, 1);
assert_eq!(is_inter, 1);
}
#[test]
fn write_inter_frame_mode_info_prefix_rejects_out_of_range_last_active() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_frame_mode_info_prefix(
&mut writer,
&mut cdfs,
BLOCK_16X16,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
MAX_SEGMENTS as u8,
false,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_frame_mode_info_prefix_rejects_out_of_range_skip() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_frame_mode_info_prefix(
&mut writer,
&mut cdfs,
BLOCK_16X16,
0,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
false,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_frame_mode_info_prefix_rejects_skip_mode_mismatch() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_inter_frame_mode_info_prefix(
&mut writer,
&mut cdfs,
BLOCK_16X16,
0,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
true,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_frame_mode_info_prefix_surfaces_lossless_lookup() {
let mut lossless = [false; MAX_SEGMENTS];
lossless[3] = true;
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
3,
0,
0,
0,
0,
1, 0,
skip_ctx(0, 0),
0,
0,
is_inter_ctx(None, None),
false,
false,
false,
false,
false,
true, true, true, true, 3,
7,
false, &lossless,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap();
assert_eq!(prefix.segment_id, 3);
assert!(
prefix.lossless,
"LosslessArray[3] == true ⇒ lossless = true"
);
}
#[test]
fn write_inter_frame_mode_info_prefix_delta_site_default_no_bits() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
0,
1,
0,
0,
0,
skip_ctx(0, 0),
0,
0,
is_inter_ctx(None, None),
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
false,
&LOSSLESS_ALL_FALSE,
&InterFrameDeltaSiteInputs::default(),
)
.unwrap();
assert_eq!(prefix.cdef_idx, None);
assert_eq!(prefix.reduced_delta_q_index, 0);
assert_eq!(prefix.reduced_delta_lf, [0; FRAME_LF_COUNT]);
assert_eq!(prefix.is_inter, 1);
}
#[test]
fn write_inter_frame_mode_info_prefix_cdef_first_leaf_round_trip() {
for cdef_value in [0i8, 2, 5, 7] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let delta_inputs = InterFrameDeltaSiteInputs {
cdef_idx: cdef_value,
cdef_idx_prior_stamp: -1,
cdef_bits: 3,
enable_cdef: true,
..Default::default()
};
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
0,
1,
0,
0,
0,
skip_ctx(0, 0),
0,
0,
is_inter_ctx(None, None),
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
false,
&LOSSLESS_ALL_FALSE,
&delta_inputs,
)
.unwrap();
assert_eq!(prefix.cdef_idx, Some(cdef_value));
let bytes = writer.finish();
let pad = pad_no_symbol(bytes);
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (_sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
true,
0,
true,
false,
false,
false,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
let _ = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false, false)
.unwrap();
let _ = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
let decoded_cdef = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 3)
.unwrap();
assert_eq!(decoded_cdef, cdef_value);
let _ = walker
.decode_delta_qindex(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
0,
)
.unwrap();
let _ = walker
.decode_delta_lf(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
false,
false,
0,
)
.unwrap();
let decoded_is_inter = walker
.decode_is_inter(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(decoded_is_inter, 1);
}
}
#[test]
fn write_inter_frame_mode_info_prefix_delta_qindex_literal_round_trip() {
for reduced in [1i32, -1, 2, -2] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let delta_inputs = InterFrameDeltaSiteInputs {
reduced_delta_q_index: reduced,
read_deltas: true,
delta_q_res: 1,
..Default::default()
};
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
0,
1,
0,
0,
0,
skip_ctx(0, 0),
0,
0,
is_inter_ctx(None, None),
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
false,
&LOSSLESS_ALL_FALSE,
&delta_inputs,
)
.unwrap();
assert_eq!(prefix.reduced_delta_q_index, reduced);
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
walker.set_current_q_index(120);
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let (_sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
true,
0,
true,
false,
false,
false,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
let _ = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false, false)
.unwrap();
let _ = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
let _ = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, false, false, 0)
.unwrap();
let q_post = walker
.decode_delta_qindex(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
false,
1,
)
.unwrap();
let expected = (120i32 + (reduced << 1)).clamp(1, 255);
assert_eq!(q_post, expected);
}
}
#[test]
fn write_inter_frame_mode_info_prefix_delta_lf_single_round_trip() {
for reduced in [1i32, -1, 3] {
let mut row = [0i32; FRAME_LF_COUNT];
row[0] = reduced;
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let delta_inputs = InterFrameDeltaSiteInputs {
reduced_delta_lf: row,
read_deltas: true,
delta_lf_present: true,
delta_lf_multi: false,
delta_lf_res: 1,
..Default::default()
};
let prefix = write_inter_frame_mode_info_prefix(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
0,
1,
0,
0,
0,
skip_ctx(0, 0),
0,
0,
is_inter_ctx(None, None),
false,
false,
false,
false,
false,
false,
false,
false,
true,
0,
7,
false,
&LOSSLESS_ALL_FALSE,
&delta_inputs,
)
.unwrap();
assert_eq!(prefix.reduced_delta_lf[0], reduced);
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let (_sid, _) = walker
.decode_inter_segment_id(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
true,
0,
true,
false,
false,
false,
0,
7,
&LOSSLESS_ALL_FALSE,
)
.unwrap();
let _ = walker
.decode_skip_mode(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false, false)
.unwrap();
let _ = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
let _ = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, false, false, 0)
.unwrap();
let _ = walker
.decode_delta_qindex(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
false,
0,
)
.unwrap();
let lf_post = walker
.decode_delta_lf(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
1,
)
.unwrap();
let expected = (reduced << 1).clamp(-63, 63);
assert_eq!(lf_post[0], expected);
}
}
#[test]
fn write_cdef_short_circuits_emit_no_bits() {
let arms: [(&str, u8, bool, bool, bool); 4] = [
("skip", 1, false, true, false),
("coded_lossless", 0, true, true, false),
("disabled", 0, false, false, false),
("allow_intrabc", 0, false, true, true),
];
for (label, skip, coded_lossless, enable_cdef, allow_intrabc) in arms {
let mut writer = SymbolWriter::new(true);
let stamped = write_cdef(
&mut writer,
5, -1,
3,
skip,
coded_lossless,
enable_cdef,
allow_intrabc,
false,
)
.unwrap();
assert!(stamped.is_none(), "{label} short-circuit ⇒ no stamp");
let bytes = pad_no_symbol(writer.finish());
let (mut walker, _) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let pos_before = dec.position();
let value = walker
.decode_cdef(
&mut dec,
0,
0,
BLOCK_16X16,
skip,
coded_lossless,
enable_cdef,
allow_intrabc,
3,
)
.unwrap();
assert_eq!(value, -1, "{label}: §5.11.55 sentinel survives");
assert_eq!(
dec.position(),
pos_before,
"{label}: no bit consumed by decoder"
);
}
}
#[test]
fn write_cdef_first_leaf_round_trip_l3() {
for cdef_value in [0i8, 1, 3, 5, 7] {
let mut writer = SymbolWriter::new(true);
let stamped = write_cdef(
&mut writer,
cdef_value,
-1,
3,
0,
false,
true,
false,
false,
)
.unwrap();
assert_eq!(stamped, Some(cdef_value));
let bytes = writer.finish();
let (mut walker, _) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let decoded = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 3)
.unwrap();
assert_eq!(decoded, cdef_value);
}
}
#[test]
fn write_cdef_first_leaf_cdef_bits_zero_no_bits() {
let mut writer = SymbolWriter::new(true);
let stamped = write_cdef(
&mut writer,
0,
-1,
0,
0,
false,
true,
false,
false,
)
.unwrap();
assert_eq!(stamped, Some(0));
let bytes = pad_no_symbol(writer.finish());
let (mut walker, _) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), true).unwrap();
let pos_before = dec.position();
let decoded = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 0)
.unwrap();
assert_eq!(decoded, 0);
assert_eq!(dec.position(), pos_before, "L(0) emits zero bits");
}
fn baseline_writer_bytes() -> Vec<u8> {
SymbolWriter::new(true).finish()
}
#[test]
fn write_cdef_anchor_already_stamped_no_bits() {
let mut writer = SymbolWriter::new(true);
let stamped = write_cdef(
&mut writer,
4,
4,
3,
0,
false,
true,
false,
true,
)
.unwrap();
assert!(stamped.is_none(), "no new stamp on second leaf");
assert_eq!(
writer.finish(),
baseline_writer_bytes(),
"no bits emitted on second leaf"
);
}
#[test]
fn write_cdef_rejects_out_of_range() {
let mut writer = SymbolWriter::new(true);
let err = write_cdef(&mut writer, 0, -1, 4, 0, false, true, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_cdef(&mut writer, 8, -1, 3, 0, false, true, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_cdef(
&mut writer,
5,
4,
3,
0,
false,
true,
false,
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_cdef(&mut writer, 1, -1, 0, 0, false, true, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_cdef(&mut writer, -2, -1, 3, 0, false, true, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn round_trip_delta_q(
reduced: i32,
base_q: i32,
delta_q_res: u8,
sub_size: usize,
) -> (i32, Vec<u8>) {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_delta_qindex(
&mut writer,
&mut enc_cdfs,
sub_size,
reduced,
0,
true,
false,
delta_q_res,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
walker.set_current_q_index(base_q);
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let q = walker
.decode_delta_qindex(
&mut dec,
&mut dec_cdfs,
0,
0,
sub_size,
0,
true,
false,
delta_q_res,
)
.unwrap();
(q, bytes)
}
#[test]
fn write_delta_qindex_literal_branch_round_trip() {
for (reduced, base, expected) in [
(0i32, 100i32, 100i32),
(1, 100, 101),
(2, 100, 102),
(-1, 100, 99),
(-2, 100, 98),
] {
let (q, _bytes) = round_trip_delta_q(reduced, base, 0, BLOCK_16X16);
assert_eq!(q, expected, "reduced={reduced} base={base}");
}
}
#[test]
fn write_delta_qindex_literal_branch_with_shift() {
let (q, _) = round_trip_delta_q(1, 50, 2, BLOCK_16X16);
assert_eq!(q, 54, "50 + (1 << 2)");
let (q, _) = round_trip_delta_q(-1, 200, 2, BLOCK_16X16);
assert_eq!(q, 196, "200 + (-1 << 2)");
}
#[test]
fn write_delta_qindex_escape_branch_round_trip() {
for (reduced, base, expected_q) in [
(3i32, 100, 103),
(-3i32, 100, 97),
(17i32, 100, 117),
(-17i32, 100, 83),
(511i32, 100, 255), (-511i32, 100, 1), ] {
let (q, _) = round_trip_delta_q(reduced, base, 0, BLOCK_16X16);
assert_eq!(q, expected_q, "reduced={reduced} base={base}");
}
}
#[test]
fn write_delta_qindex_sb_skip_short_circuit() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_64X64,
0,
1,
true,
false,
0,
)
.unwrap();
assert_eq!(writer.finish(), baseline_writer_bytes());
}
#[test]
fn write_delta_qindex_read_deltas_false_short_circuit() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
false,
false,
0,
)
.unwrap();
assert_eq!(writer.finish(), baseline_writer_bytes());
}
#[test]
fn write_delta_qindex_rejects_out_of_range() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_SIZES,
0,
0,
true,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
0,
0,
true,
false,
4,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
512,
0,
true,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
5,
0,
false,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_delta_qindex(
&mut writer,
&mut enc_cdfs,
BLOCK_64X64,
5,
1,
true,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_delta_lf_single_round_trip() {
let reduced = [2i32, 0, 0, 0];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&reduced,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(row[0], 2);
assert_eq!(&row[1..], &[0; 3]);
}
#[test]
fn write_delta_lf_multi_round_trip_4_lanes() {
let reduced = [1i32, -2, 3, -1];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&reduced,
0,
true,
true,
true,
false,
false,
0,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
true,
false,
false,
0,
)
.unwrap();
assert_eq!(row, [1, -2, 3, -1]);
}
#[test]
fn write_delta_lf_multi_mono_chrome_two_lanes() {
let reduced = [1i32, -1, 0, 0];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&reduced,
0,
true,
true,
true,
true,
false,
0,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
true,
true,
false,
0,
)
.unwrap();
assert_eq!(row[0], 1);
assert_eq!(row[1], -1);
assert_eq!(row[2], 0);
assert_eq!(row[3], 0);
}
#[test]
fn write_delta_lf_escape_branch_round_trip() {
let reduced = [50i32, 0, 0, 0];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&reduced,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
let bytes = pad_no_symbol(writer.finish());
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(row[0], 50);
}
#[test]
fn write_delta_lf_short_circuits_emit_no_bits() {
let zeros = [0i32; FRAME_LF_COUNT];
let baseline = baseline_writer_bytes();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_64X64,
&zeros,
1,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(writer.finish(), baseline);
let mut writer = SymbolWriter::new(true);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&zeros,
0,
false,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(writer.finish(), baseline);
let mut writer = SymbolWriter::new(true);
write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&zeros,
0,
true,
false,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(writer.finish(), baseline);
}
#[test]
fn write_delta_lf_rejects_out_of_range() {
let zeros = [0i32; FRAME_LF_COUNT];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_SIZES,
&zeros,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&zeros,
0,
true,
true,
false,
false,
false,
4,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let oversize = [600i32, 0, 0, 0];
let mut writer = SymbolWriter::new(true);
let err = write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&oversize,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let nonzero = [3i32, 0, 0, 0];
let mut writer = SymbolWriter::new(true);
let err = write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&nonzero,
0,
false,
true,
false,
false,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let tail_nonzero = [0i32, 1, 0, 0];
let mut writer = SymbolWriter::new(true);
let err = write_delta_lf(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
&tail_nonzero,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn round_trip_angle_info(
y_mode: u8,
angle_delta_y: i8,
uv_mode: u8,
angle_delta_uv: i8,
) -> (i8, Option<i8>) {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_skip(&mut writer, &mut enc_cdfs, 0, skip_ctx(0, 0), false).unwrap();
write_intra_segment_id(&mut writer, &mut enc_cdfs, 0, 0, 0, 0, false, 0).unwrap();
let y_ctx = size_group(BLOCK_16X16);
write_y_mode(&mut writer, &mut enc_cdfs, y_mode, y_ctx).unwrap();
write_intra_angle_info_y(
&mut writer,
&mut enc_cdfs,
angle_delta_y,
BLOCK_16X16,
y_mode,
)
.unwrap();
let cfl_allowed = cfl_allowed_for_uv_mode(false, BLOCK_16X16, false, false);
write_intra_uv_mode(&mut writer, &mut enc_cdfs, y_mode, uv_mode, cfl_allowed).unwrap();
write_intra_angle_info_uv(
&mut writer,
&mut enc_cdfs,
angle_delta_uv,
BLOCK_16X16,
uv_mode,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let _skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, y_mode);
assert_eq!(info.uv_mode, Some(uv_mode));
(info.angle_delta_y, info.angle_delta_uv)
}
#[test]
fn write_intra_angle_info_y_non_directional_writes_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_intra_angle_info_y(&mut writer, &mut cdfs, 0, BLOCK_16X16, DC_PRED_U8).unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(
bytes, pristine,
"non-directional ⇒ §5.11.42 must emit no symbols"
);
}
#[test]
fn write_intra_angle_info_y_small_block_writes_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_intra_angle_info_y(&mut writer, &mut cdfs, 0, BLOCK_4X4, V_PRED_U8).unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(
bytes, pristine,
"MiSize < BLOCK_8X8 ⇒ §5.11.42 must emit no symbols"
);
}
#[test]
fn write_intra_angle_info_y_rejects_nonzero_on_short_circuit() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_angle_info_y(&mut writer, &mut cdfs, 1, BLOCK_16X16, DC_PRED_U8)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_angle_info_y_rejects_out_of_range_delta() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_angle_info_y(&mut writer, &mut cdfs, 4, BLOCK_16X16, V_PRED_U8)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_intra_angle_info_y(&mut writer, &mut cdfs, -4, BLOCK_16X16, V_PRED_U8)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_angle_info_y_round_trip_all_deltas() {
for delta in -3i8..=3 {
let (dy, duv) = round_trip_angle_info(V_PRED_U8, delta, DC_PRED_U8, 0);
assert_eq!(dy, delta, "§5.11.42 round-trip mismatch at delta={}", delta);
assert_eq!(
duv,
Some(0),
"§5.11.43 short-circuit must reconstruct AngleDeltaUV = 0"
);
}
}
#[test]
fn write_intra_angle_info_uv_non_directional_writes_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_intra_angle_info_uv(&mut writer, &mut cdfs, 0, BLOCK_16X16, DC_PRED_U8).unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(bytes, pristine);
}
#[test]
fn write_intra_angle_info_uv_cfl_pred_writes_no_bits() {
use crate::cdf::UV_CFL_PRED;
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_intra_angle_info_uv(&mut writer, &mut cdfs, 0, BLOCK_16X16, UV_CFL_PRED as u8)
.unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(bytes, pristine);
}
#[test]
fn write_intra_angle_info_uv_rejects_invalid_inputs() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_angle_info_uv(&mut writer, &mut cdfs, 1, BLOCK_16X16, DC_PRED_U8)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_intra_angle_info_uv(&mut writer, &mut cdfs, 4, BLOCK_16X16, V_PRED_U8)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err =
write_intra_angle_info_uv(&mut writer, &mut cdfs, 0, BLOCK_16X16, 14).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_angle_info_uv_round_trip_all_deltas() {
for delta in -3i8..=3 {
let (dy, duv) = round_trip_angle_info(V_PRED_U8, 0, V_PRED_U8, delta);
assert_eq!(dy, 0);
assert_eq!(
duv,
Some(delta),
"§5.11.43 round-trip mismatch at delta={}",
delta
);
}
}
#[test]
fn write_intra_angle_info_y_and_uv_round_trip_independent_deltas() {
let cases: &[(i8, i8)] = &[(1, -2), (-3, 3), (2, 0), (0, -1)];
for &(y_delta, uv_delta) in cases {
let (dy, duv) = round_trip_angle_info(V_PRED_U8, y_delta, V_PRED_U8, uv_delta);
assert_eq!(dy, y_delta);
assert_eq!(duv, Some(uv_delta));
}
}
#[test]
fn write_filter_intra_mode_info_gate_off_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
0,
None,
BLOCK_16X16,
DC_PRED_U8,
0,
false,
)
.unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(bytes, pristine);
}
#[test]
fn write_filter_intra_mode_info_y_mode_not_dc_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
0,
None,
BLOCK_16X16,
V_PRED_U8,
0,
true,
)
.unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(bytes, pristine);
}
#[test]
fn write_filter_intra_mode_info_palette_active_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
0,
None,
BLOCK_16X16,
DC_PRED_U8,
2,
true,
)
.unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(bytes, pristine);
}
#[test]
fn write_filter_intra_mode_info_large_block_no_bits() {
use crate::cdf::BLOCK_64X64;
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
0,
None,
BLOCK_64X64,
DC_PRED_U8,
0,
true,
)
.unwrap();
let bytes = writer.finish();
let pristine = SymbolWriter::new(true).finish();
assert_eq!(bytes, pristine);
}
#[test]
fn write_filter_intra_mode_info_rejects_nonzero_on_gate_off() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
1,
Some(0),
BLOCK_16X16,
DC_PRED_U8,
0,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut writer = SymbolWriter::new(true);
let err = write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
0,
Some(0),
BLOCK_16X16,
DC_PRED_U8,
0,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_filter_intra_mode_info_rejects_use_without_mode() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
1,
None,
BLOCK_16X16,
DC_PRED_U8,
0,
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_filter_intra_mode_info_rejects_mode_without_use() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
0,
Some(0),
BLOCK_16X16,
DC_PRED_U8,
0,
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_filter_intra_mode_info_rejects_out_of_range_mode() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_filter_intra_mode_info(
&mut writer,
&mut cdfs,
1,
Some(5),
BLOCK_16X16,
DC_PRED_U8,
0,
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn round_trip_filter_intra_mode_info(
use_filter_intra: u8,
filter_intra_mode: Option<u8>,
) -> (Option<u8>, Option<u8>) {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_skip(&mut writer, &mut enc_cdfs, 0, skip_ctx(0, 0), false).unwrap();
write_intra_segment_id(&mut writer, &mut enc_cdfs, 0, 0, 0, 0, false, 0).unwrap();
let y_ctx = size_group(BLOCK_16X16);
write_y_mode(&mut writer, &mut enc_cdfs, DC_PRED_U8, y_ctx).unwrap();
write_intra_angle_info_y(&mut writer, &mut enc_cdfs, 0, BLOCK_16X16, DC_PRED_U8).unwrap();
let cfl_allowed = cfl_allowed_for_uv_mode(false, BLOCK_16X16, false, false);
write_intra_uv_mode(
&mut writer,
&mut enc_cdfs,
DC_PRED_U8,
DC_PRED_U8,
cfl_allowed,
)
.unwrap();
write_intra_angle_info_uv(&mut writer, &mut enc_cdfs, 0, BLOCK_16X16, DC_PRED_U8).unwrap();
write_filter_intra_mode_info(
&mut writer,
&mut enc_cdfs,
use_filter_intra,
filter_intra_mode,
BLOCK_16X16,
DC_PRED_U8,
0,
true,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let _skip = walker
.decode_skip(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, Some(DC_PRED_U8));
(info.use_filter_intra, info.filter_intra_mode)
}
#[test]
fn write_filter_intra_mode_info_round_trip_use_zero() {
let (use_fi, mode) = round_trip_filter_intra_mode_info(0, None);
assert_eq!(use_fi, Some(0));
assert_eq!(mode, None);
}
#[test]
fn write_filter_intra_mode_info_round_trip_use_one_all_modes() {
for mode in 0u8..(INTRA_FILTER_MODES as u8) {
let (use_fi, recovered) = round_trip_filter_intra_mode_info(1, Some(mode));
assert_eq!(use_fi, Some(1));
assert_eq!(
recovered,
Some(mode),
"§5.11.24 round-trip mismatch at mode={}",
mode
);
}
}
#[test]
fn write_palette_mode_info_gate_off_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let baseline = SymbolWriter::new(false).finish().len();
write_palette_mode_info(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
false,
false,
false,
)
.unwrap();
assert_eq!(
writer.finish().len(),
baseline,
"§5.11.46 gate-off path emits zero additional bits"
);
}
#[test]
fn write_palette_mode_info_small_block_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let baseline = SymbolWriter::new(false).finish().len();
write_palette_mode_info(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_4X4,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap();
assert_eq!(writer.finish().len(), baseline);
}
#[test]
fn write_palette_mode_info_y_mode_not_dc_skips_luma_arm() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_palette_mode_info(
&mut writer,
&mut enc_cdfs,
0,
0,
BLOCK_16X16,
V_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
let _ = info;
}
#[test]
fn write_palette_mode_info_rejects_has_palette_y_one() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_palette_mode_info(
&mut writer,
&mut cdfs,
1,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PaletteEntriesUnsupported));
}
#[test]
fn write_palette_mode_info_rejects_has_palette_uv_one() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_palette_mode_info(
&mut writer,
&mut cdfs,
0,
1,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PaletteEntriesUnsupported));
}
#[test]
fn write_palette_mode_info_rejects_out_of_range_has_palette_y() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_palette_mode_info(
&mut writer,
&mut cdfs,
2,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_mode_info_rejects_out_of_range_mi_size() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_palette_mode_info(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_SIZES,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_mode_info_rejects_out_of_range_y_mode() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_palette_mode_info(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_16X16,
INTRA_MODES as u8,
Some(DC_PRED_U8),
true,
true,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_mode_info_rejects_out_of_range_uv_mode() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_palette_mode_info(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(UV_INTRA_MODES_CFL_ALLOWED as u8),
true,
true,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn fresh_palette_walker() -> PartitionWalker {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
PartitionWalker::new(32, 32, geom).unwrap()
}
#[test]
fn write_palette_entries_y_size_2_round_trip_8bit() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 0;
entries[1] = 1;
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 8, 2, &entries, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 8, 2, &walker, 0, 0).unwrap();
assert_eq!(decoded[0], 0);
assert_eq!(decoded[1], 1);
for &slot in decoded.iter().skip(2) {
assert_eq!(slot, 0, "§5.11.46 unused slots stay zero");
}
}
#[test]
fn write_palette_entries_y_size_3_round_trip_8bit_ascending_deltas() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 10;
entries[1] = 50;
entries[2] = 200;
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 8, 3, &entries, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 8, 3, &walker, 0, 0).unwrap();
assert_eq!(decoded[0], 10);
assert_eq!(decoded[1], 50);
assert_eq!(decoded[2], 200);
}
#[test]
fn write_palette_entries_y_max_size_round_trip_8bit_refinement() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
let vals: [u16; 8] = [1, 30, 60, 90, 120, 180, 220, 250];
entries[..8].copy_from_slice(&vals);
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 8, 8, &entries, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 16]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 8, 8, &walker, 0, 0).unwrap();
assert_eq!(&decoded[..8], &vals);
}
#[test]
fn write_palette_entries_y_size_3_round_trip_10bit() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 100;
entries[1] = 500;
entries[2] = 900;
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 10, 3, &entries, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 10, 3, &walker, 0, 0).unwrap();
assert_eq!(decoded[0], 100);
assert_eq!(decoded[1], 500);
assert_eq!(decoded[2], 900);
}
#[test]
fn write_palette_entries_y_size_2_round_trip_12bit() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 1000;
entries[1] = 3000;
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 12, 2, &entries, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 12, 2, &walker, 0, 0).unwrap();
assert_eq!(decoded[0], 1000);
assert_eq!(decoded[1], 3000);
}
#[test]
fn write_palette_entries_y_cache_hit_round_trip() {
let mut walker = fresh_palette_walker();
walker.stamp_palette_for_test(0, 0, 0, &[11, 22]).unwrap();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 11;
entries[1] = 22;
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 8, 2, &entries, &walker, 0, 1).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 4]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 8, 2, &walker, 0, 1).unwrap();
assert_eq!(decoded[0], 11);
assert_eq!(decoded[1], 22);
}
#[test]
fn write_palette_entries_y_partial_cache_round_trip() {
let mut walker = fresh_palette_walker();
walker.stamp_palette_for_test(0, 0, 0, &[11, 22]).unwrap();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 11;
entries[1] = 99;
let mut writer = SymbolWriter::new(true);
write_palette_entries_y(&mut writer, 8, 2, &entries, &walker, 0, 1).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 4]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let decoded = crate::cdf::read_palette_entries_y(&mut dec, 8, 2, &walker, 0, 1).unwrap();
assert_eq!(decoded[0], 11);
assert_eq!(decoded[1], 99);
}
#[test]
fn write_palette_entries_y_rejects_descending() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 50;
entries[1] = 10;
let mut writer = SymbolWriter::new(true);
let err = write_palette_entries_y(&mut writer, 8, 2, &entries, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_y_rejects_duplicate() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 10;
entries[1] = 10;
let mut writer = SymbolWriter::new(true);
let err = write_palette_entries_y(&mut writer, 8, 2, &entries, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_y_rejects_above_bit_depth_range() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 100;
entries[1] = 256;
let mut writer = SymbolWriter::new(true);
let err = write_palette_entries_y(&mut writer, 8, 2, &entries, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_y_rejects_size_below_two() {
let walker = fresh_palette_walker();
let entries = [0u16; crate::cdf::PALETTE_COLORS];
let mut writer = SymbolWriter::new(true);
let err = write_palette_entries_y(&mut writer, 8, 1, &entries, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_y_rejects_size_above_max() {
let walker = fresh_palette_walker();
let entries = [0u16; crate::cdf::PALETTE_COLORS];
let mut writer = SymbolWriter::new(true);
let err = write_palette_entries_y(&mut writer, 8, 9, &entries, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_y_rejects_invalid_bit_depth() {
let walker = fresh_palette_walker();
let mut entries = [0u16; crate::cdf::PALETTE_COLORS];
entries[0] = 0;
entries[1] = 1;
let mut writer = SymbolWriter::new(true);
let err = write_palette_entries_y(&mut writer, 9, 2, &entries, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_uv_size_2_round_trip_8bit_direct_v_arm() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 0;
u[1] = 0;
v[0] = 0;
v[1] = 0;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, false, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 2, &walker, 0, 0).unwrap();
assert_eq!(u_dec[0], 0);
assert_eq!(u_dec[1], 0);
assert_eq!(v_dec[0], 0);
assert_eq!(v_dec[1], 0);
}
#[test]
fn write_palette_entries_uv_size_3_round_trip_8bit_direct_v_arm() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 10;
u[1] = 50;
u[2] = 200;
v[0] = 123;
v[1] = 7;
v[2] = 200;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 3, &u, &v, false, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 16]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 3, &walker, 0, 0).unwrap();
assert_eq!(u_dec[0], 10);
assert_eq!(u_dec[1], 50);
assert_eq!(u_dec[2], 200);
assert_eq!(v_dec[0], 123);
assert_eq!(v_dec[1], 7);
assert_eq!(v_dec[2], 200);
}
#[test]
fn write_palette_entries_uv_size_3_round_trip_8bit_delta_v_arm() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 10;
u[1] = 50;
u[2] = 200;
v[0] = 100;
v[1] = 110;
v[2] = 90;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 3, &u, &v, true, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 16]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 3, &walker, 0, 0).unwrap();
assert_eq!(u_dec[0], 10);
assert_eq!(u_dec[1], 50);
assert_eq!(u_dec[2], 200);
assert_eq!(v_dec[0], 100);
assert_eq!(v_dec[1], 110);
assert_eq!(v_dec[2], 90);
}
#[test]
fn write_palette_entries_uv_v_delta_arm_modular_wrap_round_trip() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 0;
u[1] = 1;
v[0] = 10;
v[1] = 250;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, true, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (_, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 2, &walker, 0, 0).unwrap();
assert_eq!(v_dec[0], 10);
assert_eq!(v_dec[1], 250);
}
#[test]
fn write_palette_entries_uv_max_size_round_trip_8bit_delta_v_arm() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
let u_vals: [u16; 8] = [1, 30, 60, 90, 120, 180, 220, 250];
u[..8].copy_from_slice(&u_vals);
let v_vals: [u16; 8] = [128, 65, 192, 70, 200, 100, 156, 35];
v[..8].copy_from_slice(&v_vals);
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 8, &u, &v, true, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 32]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 8, &walker, 0, 0).unwrap();
assert_eq!(&u_dec[..8], &u_vals);
assert_eq!(&v_dec[..8], &v_vals);
}
#[test]
fn write_palette_entries_uv_size_3_round_trip_10bit_direct_v_arm() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 100;
u[1] = 500;
u[2] = 900;
v[0] = 800;
v[1] = 200;
v[2] = 600;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 10, 3, &u, &v, false, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 16]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 10, 3, &walker, 0, 0).unwrap();
assert_eq!(u_dec[0], 100);
assert_eq!(u_dec[1], 500);
assert_eq!(u_dec[2], 900);
assert_eq!(v_dec[0], 800);
assert_eq!(v_dec[1], 200);
assert_eq!(v_dec[2], 600);
}
#[test]
fn write_palette_entries_uv_size_2_round_trip_12bit_delta_v_arm() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 1000;
u[1] = 3000;
v[0] = 2000;
v[1] = 2500;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 12, 2, &u, &v, true, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 16]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 12, 2, &walker, 0, 0).unwrap();
assert_eq!(u_dec[0], 1000);
assert_eq!(u_dec[1], 3000);
assert_eq!(v_dec[0], 2000);
assert_eq!(v_dec[1], 2500);
}
#[test]
fn write_palette_entries_uv_cache_hit_round_trip() {
let mut walker = fresh_palette_walker();
walker.stamp_palette_for_test(1, 0, 0, &[11, 22]).unwrap();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 11;
u[1] = 22;
v[0] = 33;
v[1] = 44;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, false, &walker, 0, 1).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, v_dec) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 2, &walker, 0, 1).unwrap();
assert_eq!(u_dec[0], 11);
assert_eq!(u_dec[1], 22);
assert_eq!(v_dec[0], 33);
assert_eq!(v_dec[1], 44);
}
#[test]
fn write_palette_entries_uv_rejects_u_descending() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 50;
u[1] = 10;
v[0] = 0;
v[1] = 0;
let mut writer = SymbolWriter::new(true);
let err =
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, false, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_uv_u_duplicates_accepted() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 10;
u[1] = 10;
v[0] = 0;
v[1] = 0;
let mut writer = SymbolWriter::new(true);
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, false, &walker, 0, 0).unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() {
vec![0u8; 8]
} else {
bytes
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), true).unwrap();
let (u_dec, _) =
crate::cdf::read_palette_entries_uv(&mut dec, 8, 2, &walker, 0, 0).unwrap();
assert_eq!(u_dec[0], 10);
assert_eq!(u_dec[1], 10);
}
#[test]
fn write_palette_entries_uv_rejects_u_above_bit_depth_range() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 100;
u[1] = 256;
v[0] = 0;
v[1] = 0;
let mut writer = SymbolWriter::new(true);
let err =
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, false, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_uv_rejects_v_above_bit_depth_range() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 0;
u[1] = 1;
v[0] = 0;
v[1] = 256;
let mut writer = SymbolWriter::new(true);
let err =
write_palette_entries_uv(&mut writer, 8, 2, &u, &v, false, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_uv_rejects_size_below_two() {
let walker = fresh_palette_walker();
let u = [0u16; crate::cdf::PALETTE_COLORS];
let v = [0u16; crate::cdf::PALETTE_COLORS];
let mut writer = SymbolWriter::new(true);
let err =
write_palette_entries_uv(&mut writer, 8, 1, &u, &v, false, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_uv_rejects_size_above_max() {
let walker = fresh_palette_walker();
let u = [0u16; crate::cdf::PALETTE_COLORS];
let v = [0u16; crate::cdf::PALETTE_COLORS];
let mut writer = SymbolWriter::new(true);
let err =
write_palette_entries_uv(&mut writer, 8, 9, &u, &v, false, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_entries_uv_rejects_invalid_bit_depth() {
let walker = fresh_palette_walker();
let mut u = [0u16; crate::cdf::PALETTE_COLORS];
let mut v = [0u16; crate::cdf::PALETTE_COLORS];
u[0] = 0;
u[1] = 1;
v[0] = 0;
v[1] = 0;
let mut writer = SymbolWriter::new(true);
let err =
write_palette_entries_uv(&mut writer, 9, 2, &u, &v, false, &walker, 0, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_block_mode_info_dc_pred_full_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, Some(DC_PRED_U8));
assert_eq!(info.angle_delta_y, 0);
assert_eq!(info.angle_delta_uv, Some(0));
assert_eq!(info.cfl_alpha_u, None);
assert_eq!(info.cfl_alpha_v, None);
assert_eq!(info.has_palette_y, None);
assert_eq!(info.has_palette_uv, None);
assert_eq!(info.use_filter_intra, None);
assert_eq!(info.filter_intra_mode, None);
}
#[test]
fn write_intra_block_mode_info_directional_y_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
V_PRED_U8,
Some(DC_PRED_U8),
2,
0,
true,
true,
false,
false,
0,
None,
false,
false,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, V_PRED_U8);
assert_eq!(info.uv_mode, Some(DC_PRED_U8));
assert_eq!(info.angle_delta_y, 2);
assert_eq!(info.angle_delta_uv, Some(0));
}
#[test]
fn write_intra_block_mode_info_palette_gate_open_no_palette_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
true,
false,
0,
None,
false,
false,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, Some(DC_PRED_U8));
assert_eq!(info.has_palette_y, Some(0));
assert_eq!(info.has_palette_uv, Some(0));
}
#[test]
fn write_intra_block_mode_info_filter_intra_arm_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
false,
true,
1,
Some(3),
false,
false,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.use_filter_intra, Some(1));
assert_eq!(info.filter_intra_mode, Some(3));
}
#[test]
fn write_intra_block_mode_info_monochrome_round_trip() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
None,
0,
0,
true,
false,
false,
false,
0,
None,
false,
false,
)
.unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
false,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, None);
assert_eq!(info.angle_delta_uv, None);
}
#[test]
fn write_intra_block_mode_info_rejects_missing_uv_mode_when_has_chroma() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
None,
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_block_mode_info_rejects_uv_cfl_pred() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(13),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_block_mode_info_rejects_some_uv_when_monochrome() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let err = write_intra_block_mode_info(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
false,
false,
false,
0,
None,
false,
false,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_mode_info_with_entries_gate_off_no_bits() {
let walker = fresh_palette_walker();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let baseline = SymbolWriter::new(false).finish().len();
let dummy = [0u16; PALETTE_COLORS];
write_palette_mode_info_with_entries(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
false,
false,
false,
8,
0,
&dummy,
0,
&dummy,
&dummy,
false,
&walker,
0,
0,
)
.unwrap();
assert_eq!(
writer.finish().len(),
baseline,
"§5.11.46 gate-off path emits zero additional bits via the r264 leaf"
);
}
#[test]
fn write_palette_mode_info_with_entries_small_block_no_bits() {
let walker = fresh_palette_walker();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let baseline = SymbolWriter::new(false).finish().len();
let dummy = [0u16; PALETTE_COLORS];
write_palette_mode_info_with_entries(
&mut writer,
&mut cdfs,
0,
0,
BLOCK_4X4,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
8,
0,
&dummy,
0,
&dummy,
&dummy,
false,
&walker,
0,
0,
)
.unwrap();
assert_eq!(writer.finish().len(), baseline);
}
#[test]
fn write_palette_mode_info_with_entries_gate_off_rejects_has_palette_y_one() {
let walker = fresh_palette_walker();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let mut entries = [0u16; PALETTE_COLORS];
entries[0] = 0;
entries[1] = 1;
let err = write_palette_mode_info_with_entries(
&mut writer,
&mut cdfs,
1,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
false,
false,
false,
8,
2,
&entries,
0,
&entries,
&entries,
false,
&walker,
0,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_mode_info_with_entries_rejects_palette_size_y_out_of_range() {
let walker = fresh_palette_walker();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(true);
let entries = [0u16; PALETTE_COLORS];
let err = write_palette_mode_info_with_entries(
&mut writer,
&mut cdfs,
1,
0,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
true,
true,
false,
false,
8,
1,
&entries,
0,
&entries,
&entries,
false,
&walker,
0,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_palette_mode_info_with_entries_luma_arm_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let mut entries_y = [0u16; PALETTE_COLORS];
entries_y[0] = 0;
entries_y[1] = 1;
let dummy = [0u16; PALETTE_COLORS];
write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
true,
false,
0,
None,
false,
false,
8,
1,
2,
&entries_y,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.has_palette_y, Some(1));
assert_eq!(info.palette_size_y, Some(2));
let pal_y = info.palette_colors_y.expect("luma arm fired");
assert_eq!(pal_y[0], 0);
assert_eq!(pal_y[1], 1);
assert_eq!(info.has_palette_uv, Some(0));
}
#[test]
fn write_palette_mode_info_with_entries_chroma_direct_v_arm_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let mut entries_u = [0u16; PALETTE_COLORS];
entries_u[0] = 0;
entries_u[1] = 1;
let mut entries_v = [0u16; PALETTE_COLORS];
entries_v[0] = 5;
entries_v[1] = 7;
write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
true,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
1,
2,
&entries_u,
&entries_v,
false,
None,
None,
&walker_w,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.has_palette_y, Some(0));
assert_eq!(info.has_palette_uv, Some(1));
assert_eq!(info.palette_size_uv, Some(2));
let pal_u = info.palette_colors_u.expect("chroma arm fired");
assert_eq!(pal_u[0], 0);
assert_eq!(pal_u[1], 1);
let pal_v = info.palette_colors_v.expect("chroma arm fired");
assert_eq!(pal_v[0], 5);
assert_eq!(pal_v[1], 7);
}
#[test]
fn write_palette_mode_info_with_entries_both_arms_delta_v_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let mut entries_y = [0u16; PALETTE_COLORS];
entries_y[0] = 0;
entries_y[1] = 3;
let mut entries_u = [0u16; PALETTE_COLORS];
entries_u[0] = 0;
entries_u[1] = 1;
let mut entries_v = [0u16; PALETTE_COLORS];
entries_v[0] = 10;
entries_v[1] = 12;
write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
true,
false,
0,
None,
false,
false,
8,
1,
2,
&entries_y,
1,
2,
&entries_u,
&entries_v,
true,
None,
None,
&walker_w,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.has_palette_y, Some(1));
assert_eq!(info.palette_size_y, Some(2));
let pal_y = info.palette_colors_y.expect("luma arm fired");
assert_eq!(pal_y[0], 0);
assert_eq!(pal_y[1], 3);
assert_eq!(info.has_palette_uv, Some(1));
assert_eq!(info.palette_size_uv, Some(2));
let pal_u = info.palette_colors_u.expect("chroma arm fired");
assert_eq!(pal_u[0], 0);
assert_eq!(pal_u[1], 1);
let pal_v = info.palette_colors_v.expect("chroma arm fired");
assert_eq!(pal_v[0], 10);
assert_eq!(pal_v[1], 12);
}
#[test]
fn write_intra_block_mode_info_with_palette_filter_intra_closed_by_palette() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let mut entries_y = [0u16; PALETTE_COLORS];
entries_y[0] = 0;
entries_y[1] = 1;
let dummy = [0u16; PALETTE_COLORS];
write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
true,
true,
0,
None,
false,
false,
8,
1,
2,
&entries_y,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.has_palette_y, Some(1));
assert_eq!(info.use_filter_intra, None);
assert_eq!(info.filter_intra_mode, None);
}
#[test]
fn write_intra_block_mode_info_with_palette_cfl_arm_both_pos_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let uv_cfl_pred: u8 = 13;
write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(uv_cfl_pred),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
Some(1),
Some(2),
&walker_w,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, Some(uv_cfl_pred));
assert_eq!(info.cfl_alpha_u, Some(1));
assert_eq!(info.cfl_alpha_v, Some(2));
}
#[test]
fn write_intra_block_mode_info_with_palette_cfl_arm_zero_neg_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let uv_cfl_pred: u8 = 13;
write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(uv_cfl_pred),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
Some(0),
Some(-3),
&walker_w,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_block_mode_info(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.uv_mode, Some(uv_cfl_pred));
assert_eq!(info.cfl_alpha_u, Some(0));
assert_eq!(info.cfl_alpha_v, Some(-3));
}
#[test]
fn write_intra_block_mode_info_with_palette_cfl_arm_missing_alpha_rejected() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let uv_cfl_pred: u8 = 13;
let err = write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(uv_cfl_pred),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
None,
Some(1),
&walker_w,
0,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_block_mode_info_with_palette_non_cfl_with_alpha_rejected() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let err = write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
Some(1),
None,
&walker_w,
0,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_block_mode_info_with_palette_cfl_arm_cfl_not_allowed_rejected() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let uv_cfl_pred: u8 = 13;
let err = write_intra_block_mode_info_with_palette(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(uv_cfl_pred),
0,
0,
false,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
Some(1),
Some(1),
&walker_w,
0,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_intra_frame_else_arm_directional_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let cfl_allowed = cfl_allowed_for_uv_mode(false, BLOCK_16X16, false, false);
write_intra_frame_else_arm(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
V_PRED_U8,
Some(V_PRED_U8),
2,
-1,
cfl_allowed,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
0,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_frame_mode_info_else_arm(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, V_PRED_U8);
assert_eq!(info.angle_delta_y, 2);
assert_eq!(info.uv_mode, Some(V_PRED_U8));
assert_eq!(info.angle_delta_uv, Some(-1));
assert_eq!(info.ref_frame, [0, -1], "§5.11.7 prefix RefFrame pair");
assert_eq!(
enc_cdfs.intra_frame_y_mode_cdf(0, 0).to_vec(),
dec_cdfs.intra_frame_y_mode_cdf(0, 0).to_vec(),
"TileIntraFrameYModeCdf[0][0] adaptation must match"
);
}
#[test]
fn write_intra_frame_else_arm_neighbour_ctx_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let write_block = |writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
y_mode: u8,
mi_col: u32,
leftmode_ctx: usize|
-> Result<(), Error> {
write_intra_frame_else_arm(
writer,
cdfs,
BLOCK_8X8,
y_mode,
None,
0,
0,
false,
false,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
mi_col,
0,
leftmode_ctx,
)
};
write_block(&mut writer, &mut enc_cdfs, V_PRED_U8, 0, 0).unwrap();
write_block(
&mut writer,
&mut enc_cdfs,
DC_PRED_U8,
2,
intra_mode_ctx(V_PRED),
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let decode_block = |walker_r: &mut PartitionWalker,
dec: &mut SymbolDecoder<'_>,
dec_cdfs: &mut TileCdfContext,
mi_col: u32| {
walker_r
.decode_intra_frame_mode_info_else_arm(
dec, dec_cdfs, 0, mi_col, BLOCK_8X8, false, false, false, false, false, false,
false, false, 8,
)
.unwrap()
};
let info_a = decode_block(&mut walker_r, &mut dec, &mut dec_cdfs, 0);
assert_eq!(info_a.y_mode, V_PRED_U8);
let info_b = decode_block(&mut walker_r, &mut dec, &mut dec_cdfs, 2);
assert_eq!(info_b.y_mode, DC_PRED_U8);
assert_eq!(info_b.uv_mode, None);
assert_eq!(info_b.angle_delta_uv, None);
let ctx = intra_mode_ctx(V_PRED);
assert_eq!(
enc_cdfs.intra_frame_y_mode_cdf(0, ctx).to_vec(),
dec_cdfs.intra_frame_y_mode_cdf(0, ctx).to_vec(),
"neighbour-keyed TileIntraFrameYModeCdf row adaptation must match"
);
}
#[test]
fn write_intra_frame_else_arm_cfl_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
let uv_cfl_pred: u8 = 13;
write_intra_frame_else_arm(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(uv_cfl_pred),
0,
0,
true,
true,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
Some(-2),
Some(1),
&walker_w,
0,
0,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_frame_mode_info_else_arm(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, Some(uv_cfl_pred));
assert_eq!(info.cfl_alpha_u, Some(-2));
assert_eq!(info.cfl_alpha_v, Some(1));
}
#[test]
fn write_intra_frame_else_arm_palette_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let mut entries_y = [0u16; PALETTE_COLORS];
entries_y[0] = 0;
entries_y[1] = 1;
let dummy = [0u16; PALETTE_COLORS];
write_intra_frame_else_arm(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
DC_PRED_U8,
Some(DC_PRED_U8),
0,
0,
true,
true,
true,
true,
0,
None,
false,
false,
8,
1,
2,
&entries_y,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
0,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_frame_mode_info_else_arm(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_16X16,
false,
true,
true,
true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.has_palette_y, Some(1));
assert_eq!(info.palette_size_y, Some(2));
let colors = info.palette_colors_y.expect("entries decoded");
assert_eq!(&colors[..2], &[0, 1]);
assert_eq!(info.has_palette_uv, Some(0));
assert_eq!(info.use_filter_intra, None, "§5.11.24 gate closed");
assert_eq!(info.filter_intra_mode, None);
}
#[test]
fn write_intra_frame_else_arm_monochrome_filter_intra_round_trip() {
let walker_w = fresh_palette_walker();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let dummy = [0u16; PALETTE_COLORS];
write_intra_frame_else_arm(
&mut writer,
&mut enc_cdfs,
BLOCK_8X8,
DC_PRED_U8,
None,
0,
0,
false,
false,
false,
true,
1,
Some(3),
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
0,
0,
0,
)
.unwrap();
let bytes = writer.finish();
let (mut walker_r, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let info = walker_r
.decode_intra_frame_mode_info_else_arm(
&mut dec,
&mut dec_cdfs,
0,
0,
BLOCK_8X8,
false,
false,
false,
true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.y_mode, DC_PRED_U8);
assert_eq!(info.uv_mode, None);
assert_eq!(info.use_filter_intra, Some(1));
assert_eq!(info.filter_intra_mode, Some(3));
}
#[test]
fn write_intra_frame_else_arm_rejects_caller_bugs() {
let walker_w = fresh_palette_walker();
let dummy = [0u16; PALETTE_COLORS];
let run = |y_mode: u8, uv_mode: Option<u8>, has_chroma: bool, above_ctx: usize| {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_intra_frame_else_arm(
&mut writer,
&mut enc_cdfs,
BLOCK_16X16,
y_mode,
uv_mode,
0,
0,
true,
has_chroma,
false,
false,
0,
None,
false,
false,
8,
0,
0,
&dummy,
0,
0,
&dummy,
&dummy,
false,
None,
None,
&walker_w,
0,
0,
above_ctx,
0,
)
.unwrap_err()
};
assert!(matches!(
run(13, Some(DC_PRED_U8), true, 0),
Error::PartitionWalkOutOfRange
));
assert!(matches!(
run(DC_PRED_U8, Some(DC_PRED_U8), true, 5),
Error::PartitionWalkOutOfRange
));
assert!(matches!(
run(DC_PRED_U8, Some(DC_PRED_U8), false, 0),
Error::PartitionWalkOutOfRange
));
}
#[test]
fn write_inter_block_mode_info_bootstrap_skip_mode_arm_propagates_skip_mode_frame() {
let last_frame: i8 = 1; let golden_frame: i8 = 4; let pair = write_inter_block_mode_info_bootstrap(
1,
[last_frame, golden_frame],
true,
7,
true,
true,
)
.unwrap();
assert_eq!(pair, (last_frame, golden_frame));
}
#[test]
fn write_inter_block_mode_info_bootstrap_seg_ref_frame_arm_stamps_feature_data_and_none() {
let seg_data: i8 = 6;
let pair = write_inter_block_mode_info_bootstrap(
0,
[0, 0],
true,
seg_data,
true,
true,
)
.unwrap();
assert_eq!(pair, (seg_data, -1));
}
#[test]
fn write_inter_block_mode_info_bootstrap_seg_skip_only_arm_stamps_last_and_none() {
let pair = write_inter_block_mode_info_bootstrap(0, [0, 0], false, 0, true, false).unwrap();
assert_eq!(pair, (1, -1));
}
#[test]
fn write_inter_block_mode_info_bootstrap_seg_globalmv_only_arm_stamps_last_and_none() {
let pair = write_inter_block_mode_info_bootstrap(0, [0, 0], false, 0, false, true).unwrap();
assert_eq!(pair, (1, -1));
}
#[test]
fn write_inter_block_mode_info_bootstrap_seg_skip_and_globalmv_arm_stamps_last_and_none() {
let pair = write_inter_block_mode_info_bootstrap(0, [0, 0], false, 0, true, true).unwrap();
assert_eq!(pair, (1, -1));
}
#[test]
fn write_inter_block_mode_info_bootstrap_skip_mode_priority_over_seg_arms() {
let pair = write_inter_block_mode_info_bootstrap(1, [2, 3], true, 4, true, true).unwrap();
assert_eq!(pair, (2, 3));
}
#[test]
fn write_inter_block_mode_info_bootstrap_seg_ref_frame_priority_over_seg_skip() {
let pair = write_inter_block_mode_info_bootstrap(0, [0, 0], true, 5, true, true).unwrap();
assert_eq!(pair, (5, -1));
}
#[test]
fn write_inter_block_mode_info_bootstrap_fall_through_else_rejected() {
let err =
write_inter_block_mode_info_bootstrap(0, [0, 0], false, 0, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_rejects_out_of_range_skip_mode() {
let err =
write_inter_block_mode_info_bootstrap(2, [1, 2], false, 0, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_rejects_skip_mode_frame_with_none_slot0() {
let err =
write_inter_block_mode_info_bootstrap(1, [-1, 2], true, 0, true, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_rejects_skip_mode_frame_with_none_slot1() {
let err =
write_inter_block_mode_info_bootstrap(1, [1, -1], true, 0, true, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_rejects_skip_mode_frame_above_altref() {
let err =
write_inter_block_mode_info_bootstrap(1, [8, 2], false, 0, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_rejects_seg_ref_frame_none() {
let err =
write_inter_block_mode_info_bootstrap(0, [0, 0], true, -1, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_rejects_seg_ref_frame_above_altref() {
let err =
write_inter_block_mode_info_bootstrap(0, [0, 0], true, 8, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_block_mode_info_bootstrap_seg_ref_frame_intra_frame_is_valid() {
let pair = write_inter_block_mode_info_bootstrap(0, [0, 0], true, 0, false, false).unwrap();
assert_eq!(pair, (0, -1));
}
#[test]
fn write_inter_block_mode_info_bootstrap_is_compound_derivation_per_arm() {
let (_, slot1) =
write_inter_block_mode_info_bootstrap(1, [1, 4], false, 0, false, false).unwrap();
assert!(slot1 > 0);
let (_, slot1) =
write_inter_block_mode_info_bootstrap(0, [0, 0], true, 3, false, false).unwrap();
assert!(slot1 <= 0);
let (_, slot1) =
write_inter_block_mode_info_bootstrap(0, [0, 0], false, 0, true, false).unwrap();
assert!(slot1 <= 0);
}
fn comp_mode_ctx_at_origin() -> usize {
comp_mode_ctx(false, false, true, true, false, false, [-1, -1], [-1, -1])
}
#[test]
fn write_comp_mode_single_reference_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = comp_mode_ctx_at_origin();
write_comp_mode(&mut writer, &mut enc_cdfs, 0, BLOCK_8X8, true, ctx).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = dec_cdfs.comp_mode_cdf(ctx);
let comp_mode = dec.read_symbol(row).unwrap();
assert_eq!(comp_mode, 0, "SINGLE_REFERENCE");
}
#[test]
fn write_comp_mode_compound_reference_round_trip_at_origin() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = comp_mode_ctx_at_origin();
write_comp_mode(&mut writer, &mut enc_cdfs, 1, BLOCK_8X8, true, ctx).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = dec_cdfs.comp_mode_cdf(ctx);
let comp_mode = dec.read_symbol(row).unwrap();
assert_eq!(comp_mode, 1, "COMPOUND_REFERENCE");
}
#[test]
fn write_comp_mode_reference_select_false_emits_no_bit() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = comp_mode_ctx_at_origin();
write_comp_mode(&mut writer, &mut enc_cdfs, 0, BLOCK_8X8, false, ctx).unwrap();
let bytes = writer.finish();
let mut pristine = TileCdfContext::new_from_defaults();
let expected: Vec<u16> = pristine.comp_mode_cdf(ctx).to_vec();
let actual: Vec<u16> = enc_cdfs.comp_mode_cdf(ctx).to_vec();
assert_eq!(actual, expected, "no S() emitted ⇒ comp_mode CDF unchanged");
assert!(bytes.len() <= 2, "no payload bits coded");
}
#[test]
fn write_comp_mode_small_block_emits_no_bit() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = comp_mode_ctx_at_origin();
write_comp_mode(&mut writer, &mut enc_cdfs, 0, BLOCK_4X4, true, ctx).unwrap();
let bytes = writer.finish();
assert!(bytes.len() <= 2, "no payload bits coded for a sub-8 block");
}
#[test]
fn write_comp_mode_rectangular_sub8_blocks_suppress_bit() {
for &mi_size in &[BLOCK_4X8, BLOCK_8X4] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let ctx = comp_mode_ctx_at_origin();
write_comp_mode(&mut writer, &mut enc_cdfs, 0, mi_size, true, ctx).unwrap();
let bytes = writer.finish();
assert!(bytes.len() <= 2, "Min(bw4,bh4) = 1 ⇒ no bit");
}
}
#[test]
fn write_comp_mode_rejects_out_of_range_value() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_comp_mode(&mut writer, &mut cdfs, 2, BLOCK_8X8, true, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_comp_mode_rejects_out_of_range_mi_size() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_comp_mode(&mut writer, &mut cdfs, 0, BLOCK_SIZES, true, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_comp_mode_rejects_compound_on_suppressed_path() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_comp_mode(&mut writer, &mut cdfs, 1, BLOCK_8X8, false, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_comp_mode(&mut writer, &mut cdfs, 1, BLOCK_4X4, true, 0).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_comp_mode_rejects_out_of_range_ctx_on_explicit_path() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_comp_mode(
&mut writer,
&mut cdfs,
0,
BLOCK_8X8,
true,
COMP_INTER_CONTEXTS,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_comp_mode_all_ctx_round_trip() {
for ctx in 0..COMP_INTER_CONTEXTS {
for value in 0u8..=1 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_comp_mode(&mut writer, &mut enc_cdfs, value, BLOCK_8X8, true, ctx).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let row = dec_cdfs.comp_mode_cdf(ctx);
let got = dec.read_symbol(row).unwrap() as u8;
assert_eq!(got, value, "ctx {ctx} value {value} round-trip");
}
}
}
#[derive(Clone, Copy)]
struct NeighbourOctet {
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
}
const ORIGIN: NeighbourOctet = NeighbourOctet {
avail_u: false,
avail_l: false,
above_single: true,
left_single: true,
above_intra: false,
left_intra: false,
above_ref_frame: [-1, -1],
left_ref_frame: [-1, -1],
};
const UNIDIR_PAIRS: [[i32; 2]; 4] = [[5, 7], [1, 2], [1, 3], [1, 4]];
const BIDIR_PAIRS: [[i32; 2]; 12] = [
[1, 5],
[1, 6],
[1, 7],
[2, 5],
[2, 6],
[2, 7],
[3, 5],
[3, 6],
[3, 7],
[4, 5],
[4, 6],
[4, 7],
];
fn read_compound_ref_frames_mirror(
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
n: NeighbourOctet,
) -> [i32; 2] {
let cnt = |ft: i32| {
count_refs(
ft,
n.avail_u,
n.above_ref_frame,
n.avail_l,
n.left_ref_frame,
)
};
let crt_ctx = comp_ref_type_ctx(
n.avail_u,
n.avail_l,
n.above_single,
n.left_single,
n.above_intra,
n.left_intra,
n.above_ref_frame,
n.left_ref_frame,
);
let comp_ref_type = dec.read_symbol(cdfs.comp_ref_type_cdf(crt_ctx)).unwrap();
if comp_ref_type == 0 {
let ucr_ctx =
ref_count_ctx(cnt(1) + cnt(2) + cnt(3) + cnt(4), cnt(5) + cnt(6) + cnt(7));
let uni = dec.read_symbol(cdfs.uni_comp_ref_cdf(ucr_ctx, 0)).unwrap();
if uni != 0 {
return [5, 7]; }
let p1_ctx = ref_count_ctx(cnt(2), cnt(3) + cnt(4));
let p1 = dec.read_symbol(cdfs.uni_comp_ref_cdf(p1_ctx, 1)).unwrap();
if p1 == 0 {
return [1, 2]; }
let p2_ctx = ref_count_ctx(cnt(3), cnt(4));
let p2 = dec.read_symbol(cdfs.uni_comp_ref_cdf(p2_ctx, 2)).unwrap();
if p2 != 0 {
[1, 4] } else {
[1, 3] }
} else {
let cr_ctx = ref_count_ctx(cnt(1) + cnt(2), cnt(3) + cnt(4));
let comp_ref = dec.read_symbol(cdfs.comp_ref_cdf(cr_ctx, 0)).unwrap();
let ref0 = if comp_ref == 0 {
let p1_ctx = ref_count_ctx(cnt(1), cnt(2));
let p1 = dec.read_symbol(cdfs.comp_ref_cdf(p1_ctx, 1)).unwrap();
if p1 != 0 {
2
} else {
1
}
} else {
let p2_ctx = ref_count_ctx(cnt(3), cnt(4));
let p2 = dec.read_symbol(cdfs.comp_ref_cdf(p2_ctx, 2)).unwrap();
if p2 != 0 {
4
} else {
3
}
};
let bw_ctx = ref_count_ctx(cnt(5) + cnt(6), cnt(7));
let bwdref = dec.read_symbol(cdfs.comp_bwd_ref_cdf(bw_ctx, 0)).unwrap();
let ref1 = if bwdref == 0 {
let p1_ctx = ref_count_ctx(cnt(5), cnt(6));
let p1 = dec.read_symbol(cdfs.comp_bwd_ref_cdf(p1_ctx, 1)).unwrap();
if p1 != 0 {
6
} else {
5
}
} else {
7
};
[ref0, ref1]
}
}
fn round_trip_pair(pair: [i32; 2], n: NeighbourOctet) {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_ref_frames(
&mut writer,
&mut enc_cdfs,
pair,
n.avail_u,
n.avail_l,
n.above_single,
n.left_single,
n.above_intra,
n.left_intra,
n.above_ref_frame,
n.left_ref_frame,
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = read_compound_ref_frames_mirror(&mut dec, &mut dec_cdfs, n);
assert_eq!(got, pair, "pair {pair:?} round-trip");
assert_ref_tables_eq(&mut enc_cdfs, &mut dec_cdfs);
}
fn assert_ref_tables_eq(a: &mut TileCdfContext, b: &mut TileCdfContext) {
for ctx in 0..crate::cdf::COMP_REF_TYPE_CONTEXTS {
assert_eq!(
a.comp_ref_type_cdf(ctx).to_vec(),
b.comp_ref_type_cdf(ctx).to_vec(),
"comp_ref_type ctx {ctx}"
);
}
for ctx in 0..crate::cdf::REF_CONTEXTS {
for p in 0..3 {
assert_eq!(
a.uni_comp_ref_cdf(ctx, p).to_vec(),
b.uni_comp_ref_cdf(ctx, p).to_vec(),
"uni_comp_ref ctx {ctx} p {p}"
);
assert_eq!(
a.comp_ref_cdf(ctx, p).to_vec(),
b.comp_ref_cdf(ctx, p).to_vec(),
"comp_ref ctx {ctx} p {p}"
);
}
for p in 0..2 {
assert_eq!(
a.comp_bwd_ref_cdf(ctx, p).to_vec(),
b.comp_bwd_ref_cdf(ctx, p).to_vec(),
"comp_bwdref ctx {ctx} p {p}"
);
}
}
}
#[test]
fn write_compound_ref_frames_unidir_pairs_round_trip_at_origin() {
for pair in UNIDIR_PAIRS {
round_trip_pair(pair, ORIGIN);
}
}
#[test]
fn write_compound_ref_frames_bidir_pairs_round_trip_at_origin() {
for pair in BIDIR_PAIRS {
round_trip_pair(pair, ORIGIN);
}
}
#[test]
fn write_compound_ref_frames_round_trip_with_neighbour_ctx() {
let octet_a = NeighbourOctet {
avail_u: true,
avail_l: true,
above_single: false,
left_single: true,
above_intra: false,
left_intra: false,
above_ref_frame: [1, 5],
left_ref_frame: [3, -1],
};
let octet_b = NeighbourOctet {
avail_u: true,
avail_l: true,
above_single: false,
left_single: true,
above_intra: false,
left_intra: true,
above_ref_frame: [5, 7],
left_ref_frame: [0, -1],
};
for n in [octet_a, octet_b] {
for pair in UNIDIR_PAIRS.iter().chain(BIDIR_PAIRS.iter()) {
round_trip_pair(*pair, n);
}
}
}
#[test]
fn write_compound_ref_frames_bwdref_altref_emits_two_symbols() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_ref_frames(
&mut writer,
&mut enc_cdfs,
[5, 7],
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut pristine = TileCdfContext::new_from_defaults();
assert_ne!(
enc_cdfs.comp_ref_type_cdf(2).to_vec(),
pristine.comp_ref_type_cdf(2).to_vec(),
"comp_ref_type coded"
);
assert_ne!(
enc_cdfs.uni_comp_ref_cdf(1, 0).to_vec(),
pristine.uni_comp_ref_cdf(1, 0).to_vec(),
"uni_comp_ref coded"
);
for p in 1..3 {
assert_eq!(
enc_cdfs.uni_comp_ref_cdf(1, p).to_vec(),
pristine.uni_comp_ref_cdf(1, p).to_vec(),
"uni_comp_ref p{p} NOT coded"
);
}
for p in 0..3 {
assert_eq!(
enc_cdfs.comp_ref_cdf(1, p).to_vec(),
pristine.comp_ref_cdf(1, p).to_vec(),
"comp_ref p{p} NOT coded"
);
}
}
#[test]
fn write_compound_ref_frames_last_last2_leaves_p2_untouched() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_ref_frames(
&mut writer,
&mut enc_cdfs,
[1, 2],
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut pristine = TileCdfContext::new_from_defaults();
assert_ne!(
enc_cdfs.uni_comp_ref_cdf(1, 1).to_vec(),
pristine.uni_comp_ref_cdf(1, 1).to_vec(),
"uni_comp_ref_p1 coded"
);
assert_eq!(
enc_cdfs.uni_comp_ref_cdf(1, 2).to_vec(),
pristine.uni_comp_ref_cdf(1, 2).to_vec(),
"uni_comp_ref_p2 NOT coded"
);
}
#[test]
fn write_compound_ref_frames_altref_arm_skips_bwdref_p1() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_ref_frames(
&mut writer,
&mut enc_cdfs,
[1, 7],
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut pristine = TileCdfContext::new_from_defaults();
assert_ne!(
enc_cdfs.comp_bwd_ref_cdf(1, 0).to_vec(),
pristine.comp_bwd_ref_cdf(1, 0).to_vec(),
"comp_bwdref coded"
);
assert_eq!(
enc_cdfs.comp_bwd_ref_cdf(1, 1).to_vec(),
pristine.comp_bwd_ref_cdf(1, 1).to_vec(),
"comp_bwdref_p1 NOT coded"
);
}
#[test]
fn write_compound_ref_frames_rejects_out_of_range_slots() {
for pair in [[0, 5], [-1, 5], [8, 5], [1, 0], [1, -1], [1, 8]] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_ref_frames(
&mut writer,
&mut cdfs,
pair,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"pair {pair:?} rejected"
);
}
}
#[test]
fn write_compound_ref_frames_rejects_unreachable_samedir_pairs() {
for pair in [[2, 3], [2, 4], [3, 4], [5, 6], [6, 7], [1, 1], [7, 5]] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_ref_frames(
&mut writer,
&mut cdfs,
pair,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"pair {pair:?} rejected"
);
}
}
#[test]
fn write_compound_ref_frames_rejects_reversed_bidir_pairs() {
for pair in [[5, 1], [6, 2], [7, 4]] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_ref_frames(
&mut writer,
&mut cdfs,
pair,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"pair {pair:?} rejected"
);
}
}
#[test]
fn write_compound_ref_frames_sequential_adaptation_round_trip() {
let sequence = [[1, 4], [5, 7], [3, 6], [1, 2], [4, 7], [1, 3], [2, 5]];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
for pair in sequence {
write_compound_ref_frames(
&mut writer,
&mut enc_cdfs,
pair,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
}
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
for pair in sequence {
let got = read_compound_ref_frames_mirror(&mut dec, &mut dec_cdfs, ORIGIN);
assert_eq!(got, pair, "sequential pair {pair:?}");
}
assert_ref_tables_eq(&mut enc_cdfs, &mut dec_cdfs);
}
fn read_single_ref_frames_mirror(
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
n: NeighbourOctet,
) -> [i32; 2] {
let cnt = |ft: i32| {
count_refs(
ft,
n.avail_u,
n.above_ref_frame,
n.avail_l,
n.left_ref_frame,
)
};
let p1_ctx = ref_count_ctx(cnt(1) + cnt(2) + cnt(3) + cnt(4), cnt(5) + cnt(6) + cnt(7));
let p1 = dec.read_symbol(cdfs.single_ref_cdf(p1_ctx, 0)).unwrap();
let ref0 = if p1 != 0 {
let p2_ctx = ref_count_ctx(cnt(5) + cnt(6), cnt(7));
let p2 = dec.read_symbol(cdfs.single_ref_cdf(p2_ctx, 1)).unwrap();
if p2 == 0 {
let p6_ctx = ref_count_ctx(cnt(5), cnt(6));
let p6 = dec.read_symbol(cdfs.single_ref_cdf(p6_ctx, 5)).unwrap();
if p6 != 0 {
6 } else {
5 }
} else {
7 }
} else {
let p3_ctx = ref_count_ctx(cnt(1) + cnt(2), cnt(3) + cnt(4));
let p3 = dec.read_symbol(cdfs.single_ref_cdf(p3_ctx, 2)).unwrap();
if p3 != 0 {
let p5_ctx = ref_count_ctx(cnt(3), cnt(4));
let p5 = dec.read_symbol(cdfs.single_ref_cdf(p5_ctx, 4)).unwrap();
if p5 != 0 {
4 } else {
3 }
} else {
let p4_ctx = ref_count_ctx(cnt(1), cnt(2));
let p4 = dec.read_symbol(cdfs.single_ref_cdf(p4_ctx, 3)).unwrap();
if p4 != 0 {
2 } else {
1 }
}
};
[ref0, -1]
}
fn assert_single_ref_tables_eq(a: &mut TileCdfContext, b: &mut TileCdfContext) {
for ctx in 0..crate::cdf::REF_CONTEXTS {
for p in 0..6 {
assert_eq!(
a.single_ref_cdf(ctx, p).to_vec(),
b.single_ref_cdf(ctx, p).to_vec(),
"single_ref ctx {ctx} p {p}"
);
}
}
}
fn round_trip_single_ref(ref0: i32, n: NeighbourOctet) {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_single_ref_frames(
&mut writer,
&mut enc_cdfs,
[ref0, -1],
n.avail_u,
n.avail_l,
n.above_ref_frame,
n.left_ref_frame,
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = read_single_ref_frames_mirror(&mut dec, &mut dec_cdfs, n);
assert_eq!(got, [ref0, -1], "single ref {ref0} round-trip");
assert_single_ref_tables_eq(&mut enc_cdfs, &mut dec_cdfs);
}
#[test]
fn write_single_ref_frames_all_refs_round_trip_at_origin() {
for ref0 in 1..=7 {
round_trip_single_ref(ref0, ORIGIN);
}
}
#[test]
fn write_single_ref_frames_round_trip_with_neighbour_ctx() {
let octet_a = NeighbourOctet {
avail_u: true,
avail_l: true,
above_single: false,
left_single: true,
above_intra: false,
left_intra: false,
above_ref_frame: [1, 5],
left_ref_frame: [3, -1],
};
let octet_b = NeighbourOctet {
avail_u: true,
avail_l: true,
above_single: false,
left_single: true,
above_intra: false,
left_intra: true,
above_ref_frame: [5, 7],
left_ref_frame: [0, -1],
};
for n in [octet_a, octet_b] {
for ref0 in 1..=7 {
round_trip_single_ref(ref0, n);
}
}
}
#[test]
fn write_single_ref_frames_altref_emits_two_symbols() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_single_ref_frames(
&mut writer,
&mut enc_cdfs,
[7, -1],
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut pristine = TileCdfContext::new_from_defaults();
for p in [0, 1] {
assert_ne!(
enc_cdfs.single_ref_cdf(1, p).to_vec(),
pristine.single_ref_cdf(1, p).to_vec(),
"single_ref p{} coded",
p + 1
);
}
for p in [2, 3, 4, 5] {
assert_eq!(
enc_cdfs.single_ref_cdf(1, p).to_vec(),
pristine.single_ref_cdf(1, p).to_vec(),
"single_ref p{} NOT coded",
p + 1
);
}
}
#[test]
fn write_single_ref_frames_bwdref_arm_codes_p6() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_single_ref_frames(
&mut writer,
&mut enc_cdfs,
[5, -1],
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut pristine = TileCdfContext::new_from_defaults();
assert_ne!(
enc_cdfs.single_ref_cdf(1, 5).to_vec(),
pristine.single_ref_cdf(1, 5).to_vec(),
"single_ref_p6 coded"
);
for p in [2, 3, 4] {
assert_eq!(
enc_cdfs.single_ref_cdf(1, p).to_vec(),
pristine.single_ref_cdf(1, p).to_vec(),
"single_ref p{} NOT coded",
p + 1
);
}
}
#[test]
fn write_single_ref_frames_forward_arm_skips_backward_rows() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_single_ref_frames(
&mut writer,
&mut enc_cdfs,
[1, -1],
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut pristine = TileCdfContext::new_from_defaults();
for p in [0, 2, 3] {
assert_ne!(
enc_cdfs.single_ref_cdf(1, p).to_vec(),
pristine.single_ref_cdf(1, p).to_vec(),
"single_ref p{} coded",
p + 1
);
}
for p in [1, 4, 5] {
assert_eq!(
enc_cdfs.single_ref_cdf(1, p).to_vec(),
pristine.single_ref_cdf(1, p).to_vec(),
"single_ref p{} NOT coded",
p + 1
);
}
}
#[test]
fn write_single_ref_frames_rejects_out_of_range() {
for pair in [[0, -1], [-1, -1], [8, -1], [1, 0], [1, 5], [3, 7]] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_single_ref_frames(
&mut writer,
&mut cdfs,
pair,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"pair {pair:?} rejected"
);
}
}
#[test]
fn write_single_ref_frames_sequential_adaptation_round_trip() {
let sequence = [1, 7, 3, 5, 2, 6, 4, 1, 7];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
for ref0 in sequence {
write_single_ref_frames(
&mut writer,
&mut enc_cdfs,
[ref0, -1],
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
}
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
for ref0 in sequence {
let got = read_single_ref_frames_mirror(&mut dec, &mut dec_cdfs, ORIGIN);
assert_eq!(got, [ref0, -1], "sequential ref {ref0}");
}
assert_single_ref_tables_eq(&mut enc_cdfs, &mut dec_cdfs);
}
fn read_ref_frames_arm4_mirror(
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
n: NeighbourOctet,
mi_size: usize,
reference_select: bool,
) -> [i32; 2] {
let bw4 = NUM_4X4_BLOCKS_WIDE[mi_size];
let bh4 = NUM_4X4_BLOCKS_HIGH[mi_size];
let comp_mode = if reference_select && bw4.min(bh4) >= 2 {
let ctx = comp_mode_ctx(
n.avail_u,
n.avail_l,
n.above_single,
n.left_single,
n.above_intra,
n.left_intra,
n.above_ref_frame,
n.left_ref_frame,
);
dec.read_symbol(cdfs.comp_mode_cdf(ctx)).unwrap()
} else {
0 };
if comp_mode == 1 {
read_compound_ref_frames_mirror(dec, cdfs, n)
} else {
read_single_ref_frames_mirror(dec, cdfs, n)
}
}
#[test]
fn write_ref_frames_arm1_verifies_skip_mode_pair() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_ref_frames(
&mut writer,
&mut cdfs,
[1, 7],
BLOCK_8X8,
1,
[1, 7],
false,
0,
false,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let bytes = writer.finish();
assert!(bytes.len() <= 2, "arm 1 emits no payload bits");
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_ref_frames(
&mut writer,
&mut cdfs,
[1, 5],
BLOCK_8X8,
1,
[1, 7],
false,
0,
false,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_ref_frames_arm2_arm3_verify_forced_pairs() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_ref_frames(
&mut writer,
&mut cdfs,
[4, -1],
BLOCK_8X8,
0,
[0, 0],
true,
4,
false,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_ref_frames(
&mut writer,
&mut cdfs,
[3, -1],
BLOCK_8X8,
0,
[0, 0],
true,
4,
false,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_ref_frames(
&mut writer,
&mut cdfs,
[1, -1],
BLOCK_8X8,
0,
[0, 0],
false,
0,
true,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_ref_frames(
&mut writer,
&mut cdfs,
[2, -1],
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
true,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_ref_frames_arm4_round_trips_all_targets() {
let singles = (1..=7).map(|r| [r, -1]);
let compounds = UNIDIR_PAIRS.iter().chain(BIDIR_PAIRS.iter()).copied();
for pair in singles.chain(compounds) {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_ref_frames(
&mut writer,
&mut enc_cdfs,
pair,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = read_ref_frames_arm4_mirror(&mut dec, &mut dec_cdfs, ORIGIN, BLOCK_8X8, true);
assert_eq!(got, pair, "arm-4 pair {pair:?} round-trip");
assert_ref_tables_eq(&mut enc_cdfs, &mut dec_cdfs);
assert_single_ref_tables_eq(&mut enc_cdfs, &mut dec_cdfs);
}
}
#[test]
fn write_ref_frames_suppressed_comp_mode_path() {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_ref_frames(
&mut writer,
&mut enc_cdfs,
[3, -1],
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
false,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap();
let bytes = writer.finish();
let mut pristine = TileCdfContext::new_from_defaults();
for ctx in 0..COMP_INTER_CONTEXTS {
assert_eq!(
enc_cdfs.comp_mode_cdf(ctx).to_vec(),
pristine.comp_mode_cdf(ctx).to_vec(),
"no comp_mode S() emitted"
);
}
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = read_ref_frames_arm4_mirror(&mut dec, &mut dec_cdfs, ORIGIN, BLOCK_8X8, false);
assert_eq!(got, [3, -1]);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_ref_frames(
&mut writer,
&mut cdfs,
[1, 7],
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
false,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_ref_frames_rejects_invalid_slot1() {
for slot1 in [0, -2, 8] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_ref_frames(
&mut writer,
&mut cdfs,
[1, slot1],
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
true,
false,
false,
true,
true,
false,
false,
[-1, -1],
[-1, -1],
)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"slot1 {slot1} rejected"
);
}
}
fn decode_inter_single_mode_mirror(
bytes: &[u8],
cdfs: &mut TileCdfContext,
disable_cdf_update: bool,
new_mv_context: usize,
zero_mv_context: usize,
ref_mv_context: usize,
) -> u8 {
let pad = if bytes.is_empty() {
vec![0u8]
} else {
bytes.to_vec()
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), disable_cdf_update).unwrap();
let new_mv_row = cdfs.new_mv_cdf(new_mv_context);
let new_mv = dec.read_symbol(new_mv_row).unwrap();
if new_mv == 0 {
return MODE_NEWMV;
}
let zero_mv_row = cdfs.zero_mv_cdf(zero_mv_context);
let zero_mv = dec.read_symbol(zero_mv_row).unwrap();
if zero_mv == 0 {
return MODE_GLOBALMV;
}
let ref_mv_row = cdfs.ref_mv_cdf(ref_mv_context);
let ref_mv = dec.read_symbol(ref_mv_row).unwrap();
if ref_mv == 0 {
MODE_NEARESTMV
} else {
MODE_NEARMV
}
}
#[test]
fn write_inter_single_mode_all_modes_round_trip_at_origin() {
for y_mode in [MODE_NEWMV, MODE_GLOBALMV, MODE_NEARESTMV, MODE_NEARMV] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_single_mode(&mut writer, &mut enc_cdfs, y_mode, 0, 0, 0).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_inter_single_mode_mirror(&bytes, &mut dec_cdfs, false, 0, 0, 0);
assert_eq!(recovered, y_mode, "YMode {y_mode} round-trips at origin");
assert_eq!(enc_cdfs.new_mv_cdf(0), dec_cdfs.new_mv_cdf(0));
if y_mode != MODE_NEWMV {
assert_eq!(enc_cdfs.zero_mv_cdf(0), dec_cdfs.zero_mv_cdf(0));
}
if matches!(y_mode, MODE_NEARESTMV | MODE_NEARMV) {
assert_eq!(enc_cdfs.ref_mv_cdf(0), dec_cdfs.ref_mv_cdf(0));
}
}
}
#[test]
fn write_inter_single_mode_all_modes_round_trip_nonzero_ctx() {
for y_mode in [MODE_NEWMV, MODE_GLOBALMV, MODE_NEARESTMV, MODE_NEARMV] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_single_mode(&mut writer, &mut enc_cdfs, y_mode, 5, 1, 4).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_inter_single_mode_mirror(&bytes, &mut dec_cdfs, false, 5, 1, 4);
assert_eq!(
recovered, y_mode,
"YMode {y_mode} round-trips at ctx (5,1,4)"
);
}
}
#[test]
fn write_inter_single_mode_emits_expected_symbol_counts() {
{
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_inter_single_mode(&mut wa, &mut a, MODE_NEWMV, 0, 0, 0).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let row = b.new_mv_cdf(0);
wb.write_symbol(0, row).unwrap();
let want = wb.finish();
assert_eq!(got, want, "NEWMV emits exactly one (new_mv = 0) symbol");
}
{
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_inter_single_mode(&mut wa, &mut a, MODE_GLOBALMV, 0, 0, 0).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let row = b.new_mv_cdf(0);
wb.write_symbol(1, row).unwrap();
let row = b.zero_mv_cdf(0);
wb.write_symbol(0, row).unwrap();
let want = wb.finish();
assert_eq!(got, want, "GLOBALMV emits new_mv = 1, zero_mv = 0");
}
{
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_inter_single_mode(&mut wa, &mut a, MODE_NEARMV, 0, 0, 0).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let row = b.new_mv_cdf(0);
wb.write_symbol(1, row).unwrap();
let row = b.zero_mv_cdf(0);
wb.write_symbol(1, row).unwrap();
let row = b.ref_mv_cdf(0);
wb.write_symbol(1, row).unwrap();
let want = wb.finish();
assert_eq!(
got, want,
"NEARMV emits new_mv = 1, zero_mv = 1, ref_mv = 1"
);
}
}
#[test]
fn write_inter_single_mode_rejects_invalid_y_mode() {
for y_mode in [0u8, 13, MODE_NEAREST_NEARESTMV, 25, 255] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_inter_single_mode(&mut writer, &mut cdfs, y_mode, 0, 0, 0).unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"y_mode {y_mode} rejected"
);
}
}
#[test]
fn write_inter_single_mode_rejects_bad_new_mv_ctx() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_inter_single_mode(&mut writer, &mut cdfs, MODE_NEWMV, NEW_MV_CONTEXTS, 0, 0)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_single_mode_zero_mv_ctx_only_validated_when_consulted() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_single_mode(&mut writer, &mut cdfs, MODE_NEWMV, 0, ZERO_MV_CONTEXTS, 0)
.unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_inter_single_mode(
&mut writer,
&mut cdfs,
MODE_GLOBALMV,
0,
ZERO_MV_CONTEXTS,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_single_mode_ref_mv_ctx_only_validated_when_consulted() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_single_mode(&mut writer, &mut cdfs, MODE_GLOBALMV, 0, 0, REF_MV_CONTEXTS)
.unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_inter_single_mode(&mut writer, &mut cdfs, MODE_NEARMV, 0, 0, REF_MV_CONTEXTS)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_inter_single_mode_sequential_cdf_adaptation_lockstep() {
let modes = [
MODE_NEWMV,
MODE_NEARMV,
MODE_GLOBALMV,
MODE_NEARESTMV,
MODE_NEWMV,
MODE_NEARMV,
MODE_NEARESTMV,
MODE_GLOBALMV,
];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
for &y_mode in &modes {
let mut writer = SymbolWriter::new(false);
write_inter_single_mode(&mut writer, &mut enc_cdfs, y_mode, 2, 1, 3).unwrap();
let bytes = writer.finish();
let recovered = decode_inter_single_mode_mirror(&bytes, &mut dec_cdfs, false, 2, 1, 3);
assert_eq!(recovered, y_mode);
}
assert_eq!(enc_cdfs.new_mv_cdf(2), dec_cdfs.new_mv_cdf(2));
assert_eq!(enc_cdfs.zero_mv_cdf(1), dec_cdfs.zero_mv_cdf(1));
assert_eq!(enc_cdfs.ref_mv_cdf(3), dec_cdfs.ref_mv_cdf(3));
}
fn decode_compound_mode_mirror(
bytes: &[u8],
cdfs: &mut TileCdfContext,
disable_cdf_update: bool,
compound_mode_context: usize,
) -> u8 {
let pad = if bytes.is_empty() {
vec![0u8]
} else {
bytes.to_vec()
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), disable_cdf_update).unwrap();
let row = cdfs.compound_mode_cdf(compound_mode_context);
let cm = dec.read_symbol(row).unwrap() as u8;
MODE_NEAREST_NEARESTMV + cm
}
#[test]
fn write_compound_mode_all_modes_round_trip_at_origin() {
for y_mode in MODE_NEAREST_NEARESTMV..=MODE_NEW_NEWMV {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_mode(&mut writer, &mut enc_cdfs, y_mode, 0).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_compound_mode_mirror(&bytes, &mut dec_cdfs, false, 0);
assert_eq!(recovered, y_mode, "YMode {y_mode} round-trips at origin");
assert_eq!(enc_cdfs.compound_mode_cdf(0), dec_cdfs.compound_mode_cdf(0));
}
}
#[test]
fn write_compound_mode_all_modes_round_trip_each_ctx() {
for ctx in 1..COMPOUND_MODE_CONTEXTS {
for y_mode in MODE_NEAREST_NEARESTMV..=MODE_NEW_NEWMV {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_mode(&mut writer, &mut enc_cdfs, y_mode, ctx).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_compound_mode_mirror(&bytes, &mut dec_cdfs, false, ctx);
assert_eq!(recovered, y_mode, "YMode {y_mode} round-trips at ctx {ctx}");
}
}
}
#[test]
fn write_compound_mode_emits_exactly_one_symbol() {
for y_mode in MODE_NEAREST_NEARESTMV..=MODE_NEW_NEWMV {
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_compound_mode(&mut wa, &mut a, y_mode, 0).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let cm = (y_mode - MODE_NEAREST_NEARESTMV) as u32;
let row = b.compound_mode_cdf(0);
wb.write_symbol(cm, row).unwrap();
let want = wb.finish();
assert_eq!(
got, want,
"YMode {y_mode} emits exactly one compound_mode symbol"
);
}
}
#[test]
fn write_compound_mode_rejects_invalid_y_mode() {
for y_mode in [0u8, 13, MODE_NEWMV, MODE_NEW_NEWMV + 1, 255] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_mode(&mut writer, &mut cdfs, y_mode, 0).unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"y_mode {y_mode} rejected"
);
}
}
#[test]
fn write_compound_mode_rejects_bad_ctx() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_mode(
&mut writer,
&mut cdfs,
MODE_NEAREST_NEARESTMV,
COMPOUND_MODE_CONTEXTS,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_compound_mode_sequential_cdf_adaptation_lockstep() {
let modes = [
MODE_NEAREST_NEARESTMV,
MODE_NEW_NEWMV,
MODE_NEAR_NEARMV,
MODE_GLOBAL_GLOBALMV,
MODE_NEAREST_NEWMV,
MODE_NEW_NEARMV,
MODE_NEW_NEARESTMV,
MODE_NEAR_NEWMV,
];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
for &y_mode in &modes {
let mut writer = SymbolWriter::new(false);
write_compound_mode(&mut writer, &mut enc_cdfs, y_mode, 4).unwrap();
let bytes = writer.finish();
let recovered = decode_compound_mode_mirror(&bytes, &mut dec_cdfs, false, 4);
assert_eq!(recovered, y_mode);
}
assert_eq!(enc_cdfs.compound_mode_cdf(4), dec_cdfs.compound_mode_cdf(4));
}
fn decode_drl_mode_mirror(
bytes: &[u8],
cdfs: &mut TileCdfContext,
disable_cdf_update: bool,
y_mode: u8,
num_mv_found: u32,
drl_ctx_stack: &[u32],
) -> u32 {
let pad = if bytes.is_empty() {
vec![0u8]
} else {
bytes.to_vec()
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), disable_cdf_update).unwrap();
let mut ref_mv_idx: u32 = 0;
let start = if y_mode == MODE_NEWMV || y_mode == MODE_NEW_NEWMV {
0
} else if matches!(
y_mode,
MODE_NEARMV | MODE_NEAR_NEARMV | MODE_NEAR_NEWMV | MODE_NEW_NEARMV
) {
ref_mv_idx = 1;
1
} else {
return 0;
};
let mut idx = start;
while idx < start + 2 {
if num_mv_found > idx + 1 {
let row = cdfs.drl_mode_cdf(drl_ctx_stack[idx as usize] as usize);
let drl = dec.read_symbol(row).unwrap();
if drl == 0 {
ref_mv_idx = idx;
break;
}
ref_mv_idx = idx + 1;
}
idx += 1;
}
ref_mv_idx
}
#[test]
fn write_drl_mode_newmv_arm_round_trips_all_depths() {
let ctx_stack = [2u32, 0, 1, 0, 0, 0, 0, 0];
for y_mode in [MODE_NEWMV, MODE_NEW_NEWMV] {
for num_mv_found in 1u32..=3 {
let max_idx = (num_mv_found - 1).min(2);
for ref_mv_idx in 0..=max_idx {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_drl_mode(
&mut writer,
&mut enc_cdfs,
y_mode,
ref_mv_idx,
num_mv_found,
&ctx_stack,
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_drl_mode_mirror(
&bytes,
&mut dec_cdfs,
false,
y_mode,
num_mv_found,
&ctx_stack,
);
assert_eq!(
recovered, ref_mv_idx,
"NEWMV-arm YMode {y_mode} RefMvIdx {ref_mv_idx} @ NumMvFound {num_mv_found}"
);
for c in 0..DRL_MODE_CONTEXTS {
assert_eq!(enc_cdfs.drl_mode_cdf(c), dec_cdfs.drl_mode_cdf(c));
}
}
}
}
}
#[test]
fn write_drl_mode_nearmv_arm_round_trips_all_depths() {
let ctx_stack = [0u32, 1, 2, 0, 0, 0, 0, 0];
for y_mode in [
MODE_NEARMV,
MODE_NEAR_NEARMV,
MODE_NEAR_NEWMV,
MODE_NEW_NEARMV,
] {
for num_mv_found in 1u32..=4 {
let max_idx = (num_mv_found - 1).clamp(1, 3);
for ref_mv_idx in 1..=max_idx {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_drl_mode(
&mut writer,
&mut enc_cdfs,
y_mode,
ref_mv_idx,
num_mv_found,
&ctx_stack,
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_drl_mode_mirror(
&bytes,
&mut dec_cdfs,
false,
y_mode,
num_mv_found,
&ctx_stack,
);
assert_eq!(
recovered, ref_mv_idx,
"NEAR-arm YMode {y_mode} RefMvIdx {ref_mv_idx} @ NumMvFound {num_mv_found}"
);
}
}
}
}
#[test]
fn write_drl_mode_non_coding_modes_emit_no_symbols() {
let ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
for y_mode in [
MODE_GLOBALMV,
MODE_NEARESTMV,
MODE_NEAREST_NEARESTMV,
MODE_GLOBAL_GLOBALMV,
] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_drl_mode(&mut writer, &mut enc_cdfs, y_mode, 0, 4, &ctx_stack).unwrap();
let bytes = writer.finish();
for c in 0..DRL_MODE_CONTEXTS {
assert_eq!(
enc_cdfs.drl_mode_cdf(c),
TileCdfContext::new_from_defaults().drl_mode_cdf(c),
"non-coding mode {y_mode} left DRL ctx {c} pristine"
);
}
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered =
decode_drl_mode_mirror(&bytes, &mut dec_cdfs, false, y_mode, 4, &ctx_stack);
assert_eq!(recovered, 0);
}
}
#[test]
fn write_drl_mode_shallow_stack_codes_nothing() {
let ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
for num_mv_found in 0u32..=1 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_drl_mode(
&mut writer,
&mut enc_cdfs,
MODE_NEWMV,
0,
num_mv_found,
&ctx_stack,
)
.unwrap();
for c in 0..DRL_MODE_CONTEXTS {
assert_eq!(
enc_cdfs.drl_mode_cdf(c),
TileCdfContext::new_from_defaults().drl_mode_cdf(c)
);
}
}
}
#[test]
fn write_drl_mode_uses_per_slot_context() {
let ctx_stack = [0u32, 1, 2, 0, 0, 0, 0, 0];
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_drl_mode(&mut wa, &mut a, MODE_NEWMV, 1, 3, &ctx_stack).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let row0 = b.drl_mode_cdf(0);
wb.write_symbol(1, row0).unwrap();
let row1 = b.drl_mode_cdf(1);
wb.write_symbol(0, row1).unwrap();
let want = wb.finish();
assert_eq!(got, want, "per-slot DRL contexts honoured");
}
#[test]
fn write_drl_mode_rejects_unreachable_index() {
let ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_drl_mode(&mut writer, &mut cdfs, MODE_NEWMV, 2, 2, &ctx_stack).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_drl_mode_rejects_out_of_window_index() {
let ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
{
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_drl_mode(&mut writer, &mut cdfs, MODE_NEWMV, 3, 5, &ctx_stack).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
{
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_drl_mode(&mut writer, &mut cdfs, MODE_NEARMV, 0, 5, &ctx_stack).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
{
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_drl_mode(&mut writer, &mut cdfs, MODE_NEARMV, 4, 5, &ctx_stack).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
}
#[test]
fn write_drl_mode_range_guards() {
{
let ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_drl_mode(
&mut writer,
&mut cdfs,
MODE_NEWMV,
MAX_REF_MV_STACK_SIZE as u32,
3,
&ctx_stack,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
{
let ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_drl_mode(
&mut writer,
&mut cdfs,
MODE_NEWMV,
0,
MAX_REF_MV_STACK_SIZE as u32 + 1,
&ctx_stack,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
{
let mut ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
ctx_stack[0] = DRL_MODE_CONTEXTS as u32;
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_drl_mode(&mut writer, &mut cdfs, MODE_NEWMV, 0, 3, &ctx_stack).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
}
#[test]
fn write_drl_mode_sequential_cdf_adaptation_lockstep() {
let ctx_stack = [0u32, 1, 2, 0, 0, 0, 0, 0];
let cases = [
(MODE_NEWMV, 0u32, 3u32),
(MODE_NEW_NEWMV, 1, 3),
(MODE_NEWMV, 2, 3),
(MODE_NEW_NEWMV, 0, 2),
(MODE_NEWMV, 1, 2),
];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
for &(y_mode, ref_mv_idx, num_mv_found) in &cases {
let mut writer = SymbolWriter::new(false);
write_drl_mode(
&mut writer,
&mut enc_cdfs,
y_mode,
ref_mv_idx,
num_mv_found,
&ctx_stack,
)
.unwrap();
let bytes = writer.finish();
let recovered = decode_drl_mode_mirror(
&bytes,
&mut dec_cdfs,
false,
y_mode,
num_mv_found,
&ctx_stack,
);
assert_eq!(recovered, ref_mv_idx);
}
for c in 0..DRL_MODE_CONTEXTS {
assert_eq!(enc_cdfs.drl_mode_cdf(c), dec_cdfs.drl_mode_cdf(c));
}
}
fn decode_read_mv_component_mirror(
dec: &mut SymbolDecoder,
cdfs: &mut TileCdfContext,
mv_ctx: usize,
comp: usize,
force_integer_mv: bool,
allow_high_precision_mv: bool,
) -> i32 {
let mv_sign = dec.read_symbol(cdfs.mv_sign_cdf(mv_ctx, comp)).unwrap();
let mv_class = dec.read_symbol(cdfs.mv_class_cdf(mv_ctx, comp)).unwrap();
let mag: u32 = if mv_class == MV_CLASS_0 as u32 {
let mv_class0_bit = dec
.read_symbol(cdfs.mv_class0_bit_cdf(mv_ctx, comp))
.unwrap();
let mv_class0_fr = if force_integer_mv {
3
} else {
dec.read_symbol(cdfs.mv_class0_fr_cdf(mv_ctx, comp, mv_class0_bit as usize))
.unwrap()
};
let mv_class0_hp = if allow_high_precision_mv {
dec.read_symbol(cdfs.mv_class0_hp_cdf(mv_ctx, comp))
.unwrap()
} else {
1
};
((mv_class0_bit << 3) | (mv_class0_fr << 1) | mv_class0_hp) + 1
} else {
let mut d: u32 = 0;
for i in 0..(mv_class as usize) {
let mv_bit = dec.read_symbol(cdfs.mv_bit_cdf(mv_ctx, comp, i)).unwrap();
d |= mv_bit << i;
}
let mut mag = (CLASS0_SIZE as u32) << (mv_class + 2);
let mv_fr = if force_integer_mv {
3
} else {
dec.read_symbol(cdfs.mv_fr_cdf(mv_ctx, comp)).unwrap()
};
let mv_hp = if allow_high_precision_mv {
dec.read_symbol(cdfs.mv_hp_cdf(mv_ctx, comp)).unwrap()
} else {
1
};
mag += ((d << 3) | (mv_fr << 1) | mv_hp) + 1;
mag
};
if mv_sign != 0 {
-(mag as i32)
} else {
mag as i32
}
}
fn decode_read_mv_mirror(
bytes: &[u8],
cdfs: &mut TileCdfContext,
pred_mv: [i32; 2],
use_intrabc: bool,
force_integer_mv: bool,
allow_high_precision_mv: bool,
) -> [i32; 2] {
let mv_ctx = if use_intrabc { MV_INTRABC_CONTEXT } else { 0 };
let mut dec = SymbolDecoder::init_symbol(bytes, bytes.len(), false).unwrap();
let mv_joint = dec.read_symbol(cdfs.mv_joint_cdf(mv_ctx)).unwrap() as u8;
let mut diff = [0i32; 2];
if mv_joint == MV_JOINT_HZVNZ || mv_joint == MV_JOINT_HNZVNZ {
diff[0] = decode_read_mv_component_mirror(
&mut dec,
cdfs,
mv_ctx,
0,
force_integer_mv,
allow_high_precision_mv,
);
}
if mv_joint == MV_JOINT_HNZVZ || mv_joint == MV_JOINT_HNZVNZ {
diff[1] = decode_read_mv_component_mirror(
&mut dec,
cdfs,
mv_ctx,
1,
force_integer_mv,
allow_high_precision_mv,
);
}
[pred_mv[0] + diff[0], pred_mv[1] + diff[1]]
}
#[test]
fn write_read_mv_round_trips_high_precision() {
let pred = [100, -50];
let targets = [
[100, -50], [100, -49], [101, -50], [101, -49], [84, -50], [100, 30], [1100, -1050], [50, -200], ];
for tgt in targets {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(&mut writer, &mut enc_cdfs, tgt, pred, false, false, true).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_read_mv_mirror(&bytes, &mut dec_cdfs, pred, false, false, true);
assert_eq!(recovered, tgt, "MV {tgt:?} round-trips at high precision");
assert_eq!(enc_cdfs.mv_joint_cdf(0), dec_cdfs.mv_joint_cdf(0));
}
}
#[test]
fn write_read_mv_round_trips_intrabc_context() {
let pred = [0, 0];
let tgt = [-40, 64];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(&mut writer, &mut enc_cdfs, tgt, pred, true, true, false).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_read_mv_mirror(&bytes, &mut dec_cdfs, pred, true, true, false);
assert_eq!(
recovered, tgt,
"MV round-trips under MvCtx = MV_INTRABC_CONTEXT"
);
assert_eq!(
enc_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT),
dec_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT)
);
}
#[test]
fn write_read_mv_round_trips_force_integer() {
let pred = [0, 0];
for &d0 in &[0i32, 8, -8, 64, -128] {
for &d1 in &[0i32, 8, 256, -512] {
let tgt = [d0, d1];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(&mut writer, &mut enc_cdfs, tgt, pred, false, true, false).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered =
decode_read_mv_mirror(&bytes, &mut dec_cdfs, pred, false, true, false);
assert_eq!(recovered, tgt, "integer MV {tgt:?} round-trips");
}
}
}
#[test]
fn write_read_mv_joint_selection_is_exhaustive() {
let pred = [10, 20];
let cases = [
([10, 20], MV_JOINT_ZERO),
([10, 21], MV_JOINT_HNZVZ),
([11, 20], MV_JOINT_HZVNZ),
([11, 21], MV_JOINT_HNZVNZ),
];
for (tgt, want_joint) in cases {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(&mut writer, &mut enc_cdfs, tgt, pred, false, false, true).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let joint = dec.read_symbol(dec_cdfs.mv_joint_cdf(0)).unwrap() as u8;
assert_eq!(joint, want_joint, "MV {tgt:?} ⇒ joint {want_joint}");
}
}
#[test]
fn write_read_mv_zero_joint_emits_only_joint_symbol() {
let pred = [7, -3];
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_read_mv(&mut wa, &mut a, pred, pred, false, false, true).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let row = b.mv_joint_cdf(0);
wb.write_symbol(MV_JOINT_ZERO as u32, row).unwrap();
let want = wb.finish();
assert_eq!(got, want, "MV_JOINT_ZERO writes a single joint symbol");
}
#[test]
fn write_read_mv_class_boundary_round_trips() {
let pred = [0, 0];
for d in [16i32, 17, -16, -17] {
let tgt = [d, 0];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(&mut writer, &mut enc_cdfs, tgt, pred, false, false, true).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_read_mv_mirror(&bytes, &mut dec_cdfs, pred, false, false, true);
assert_eq!(recovered, tgt, "class-boundary diff {d} round-trips");
}
}
#[test]
fn write_read_mv_component_rejects_zero_diff() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_read_mv_component(&mut writer, &mut cdfs, 0, 0, 0, false, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_read_mv_component_rejects_bad_indices() {
for (ctx, comp) in [(MV_CONTEXTS, 0), (0, MV_COMPS)] {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_read_mv_component(&mut writer, &mut cdfs, ctx, comp, 5, false, true)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
}
#[test]
fn write_read_mv_component_rejects_non_integer_under_force_integer() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_read_mv_component(&mut writer, &mut cdfs, 0, 0, 1, true, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_read_mv_component_rejects_non_hp_when_disallowed() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_read_mv_component(&mut writer, &mut cdfs, 0, 0, 3, false, false).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_read_mv_component_rejects_overlarge_magnitude() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_read_mv_component(&mut writer, &mut cdfs, 0, 0, 16385, false, true).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn mk_mv_stack(num_mv_found: u32) -> FindMvStackResult {
let mut ref_stack_mv = [[[0i32; 2]; 2]; MAX_REF_MV_STACK_SIZE];
#[allow(clippy::needless_range_loop)]
for idx in 0..MAX_REF_MV_STACK_SIZE {
ref_stack_mv[idx][0] = [100 + idx as i32 * 10, 200 + idx as i32 * 10];
ref_stack_mv[idx][1] = [300 + idx as i32 * 10, 400 + idx as i32 * 10];
}
FindMvStackResult {
num_mv_found,
new_mv_count: 0,
ref_stack_mv,
weight_stack: [0; MAX_REF_MV_STACK_SIZE],
global_mvs: [[7, 9], [11, 13]],
new_mv_context: 0,
ref_mv_context: 0,
zero_mv_context: 0,
drl_ctx_stack: [0; MAX_REF_MV_STACK_SIZE],
close_matches: 0,
total_matches: 0,
num_nearest: 0,
num_new: 0,
}
}
#[test]
fn assign_mv_pred_mv_globalmv_uses_global() {
let s = mk_mv_stack(0);
assert_eq!(assign_mv_pred_mv(&s, MODE_GLOBALMV, 0, 2).unwrap(), [7, 9]);
assert_eq!(
assign_mv_pred_mv(&s, MODE_GLOBAL_GLOBALMV, 1, 0).unwrap(),
[11, 13]
);
}
#[test]
fn assign_mv_pred_mv_nearestmv_uses_slot0() {
let s = mk_mv_stack(4);
for ref_mv_idx in 0..=3 {
assert_eq!(
assign_mv_pred_mv(&s, MODE_NEARESTMV, 0, ref_mv_idx).unwrap(),
[100, 200],
"NEARESTMV ignores RefMvIdx {ref_mv_idx}"
);
}
}
#[test]
fn assign_mv_pred_mv_nearmv_uses_ref_mv_idx() {
let s = mk_mv_stack(4);
assert_eq!(
assign_mv_pred_mv(&s, MODE_NEARMV, 0, 1).unwrap(),
[110, 210]
);
assert_eq!(
assign_mv_pred_mv(&s, MODE_NEARMV, 0, 2).unwrap(),
[120, 220]
);
assert_eq!(
assign_mv_pred_mv(&s, MODE_NEARMV, 0, 3).unwrap(),
[130, 230]
);
}
#[test]
fn assign_mv_pred_mv_newmv_collapses_on_short_stack() {
let deep = mk_mv_stack(2);
assert_eq!(
assign_mv_pred_mv(&deep, MODE_NEWMV, 0, 1).unwrap(),
[110, 210]
);
let shallow = mk_mv_stack(1);
assert_eq!(
assign_mv_pred_mv(&shallow, MODE_NEWMV, 0, 0).unwrap(),
[100, 200]
);
}
#[test]
fn assign_mv_pred_mv_compound_per_list() {
let s = mk_mv_stack(4);
assert_eq!(
assign_mv_pred_mv(&s, MODE_NEW_NEARMV, 0, 1).unwrap(),
[110, 210]
);
assert_eq!(
assign_mv_pred_mv(&s, MODE_NEW_NEARMV, 1, 1).unwrap(),
[310, 410]
);
}
#[test]
fn assign_mv_pred_mv_rejects_unreachable_pos() {
let s = mk_mv_stack(2);
assert!(matches!(
assign_mv_pred_mv(&s, MODE_NEARMV, 0, 2).unwrap_err(),
Error::PartitionWalkOutOfRange
));
let empty = mk_mv_stack(0);
assert_eq!(
assign_mv_pred_mv(&empty, MODE_NEARESTMV, 0, 0).unwrap(),
empty.ref_stack_mv[0][0]
);
assert_eq!(
assign_mv_pred_mv(&empty, MODE_NEWMV, 0, 0).unwrap(),
empty.ref_stack_mv[0][0]
);
assert!(matches!(
assign_mv_pred_mv(&empty, MODE_NEARMV, 0, 2).unwrap_err(),
Error::PartitionWalkOutOfRange
));
}
#[test]
fn assign_mv_pred_mv_rejects_bad_mode_or_ref_list() {
let s = mk_mv_stack(4);
assert!(matches!(
assign_mv_pred_mv(&s, DC_PRED_U8, 0, 0).unwrap_err(),
Error::PartitionWalkOutOfRange
));
assert!(matches!(
assign_mv_pred_mv(&s, MODE_NEW_NEWMV + 1, 0, 0).unwrap_err(),
Error::PartitionWalkOutOfRange
));
assert!(matches!(
assign_mv_pred_mv(&s, MODE_NEARMV, 2, 0).unwrap_err(),
Error::PartitionWalkOutOfRange
));
}
#[test]
fn assign_mv_pred_mv_feeds_write_read_mv() {
let s = mk_mv_stack(2);
let pred = assign_mv_pred_mv(&s, MODE_NEWMV, 0, 1).unwrap();
assert_eq!(pred, [110, 210]);
let target_mv = [pred[0] + 5, pred[1] - 9];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(
&mut writer,
&mut enc_cdfs,
target_mv,
pred,
false,
false,
true,
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_read_mv_mirror(&bytes, &mut dec_cdfs, pred, false, false, true);
assert_eq!(recovered, target_mv, "PredMv + diffMv reconstructs Mv");
}
fn mk_intrabc_mv_stack(
slot0: [i32; 2],
slot1: [i32; 2],
num_mv_found: u32,
) -> FindMvStackResult {
let mut s = mk_mv_stack(num_mv_found);
s.global_mvs = [[0, 0], [0, 0]];
s.ref_stack_mv = [[[0i32; 2]; 2]; MAX_REF_MV_STACK_SIZE];
s.ref_stack_mv[0][0] = slot0;
s.ref_stack_mv[1][0] = slot1;
s
}
#[test]
fn assign_mv_pred_mv_intrabc_uses_slot0_when_nonzero() {
let s = mk_intrabc_mv_stack([-64, 8], [16, -24], 2);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 16, 0).unwrap(),
[-64, 8]
);
let row_only = mk_intrabc_mv_stack([-8, 0], [16, -24], 2);
assert_eq!(
assign_mv_pred_mv_intrabc(&row_only, false, 16, 0).unwrap(),
[-8, 0]
);
}
#[test]
fn assign_mv_pred_mv_intrabc_falls_to_slot1() {
let s = mk_intrabc_mv_stack([0, 0], [16, -24], 2);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 16, 0).unwrap(),
[16, -24]
);
}
#[test]
fn assign_mv_pred_mv_intrabc_top_row_fallback() {
let s = mk_intrabc_mv_stack([0, 0], [0, 0], 0);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 0, 0).unwrap(),
[0, -2560]
);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 15, 0).unwrap(),
[0, -2560]
);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, true, 31, 0).unwrap(),
[0, -3072]
);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 40, 32).unwrap(),
[0, -2560]
);
}
#[test]
fn assign_mv_pred_mv_intrabc_up_fallback() {
let s = mk_intrabc_mv_stack([0, 0], [0, 0], 0);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 16, 0).unwrap(),
[-512, 0]
);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, true, 32, 0).unwrap(),
[-1024, 0]
);
assert_eq!(
assign_mv_pred_mv_intrabc(&s, false, 48, 32).unwrap(),
[-512, 0]
);
}
#[test]
fn assign_mv_pred_mv_intrabc_rejects_row_above_tile() {
let s = mk_intrabc_mv_stack([0, 0], [0, 0], 0);
assert!(matches!(
assign_mv_pred_mv_intrabc(&s, false, 31, 32).unwrap_err(),
Error::PartitionWalkOutOfRange
));
}
#[test]
fn assign_mv_pred_mv_intrabc_feeds_write_read_mv() {
for (s, mi_row, target_mv) in [
(
mk_intrabc_mv_stack([-64, 8], [0, 0], 2),
16,
[-64 + 16, 8 - 40],
),
(mk_intrabc_mv_stack([0, 0], [0, 0], 0), 0, [0, -2560 + 64]),
] {
let pred = assign_mv_pred_mv_intrabc(&s, false, mi_row, 0).unwrap();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_read_mv(
&mut writer,
&mut enc_cdfs,
target_mv,
pred,
true,
true,
false,
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let recovered = decode_read_mv_mirror(&bytes, &mut dec_cdfs, pred, true, true, false);
assert_eq!(recovered, target_mv, "intrabc Mv reconstructs");
assert_eq!(
enc_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT),
dec_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT)
);
}
}
#[test]
fn write_use_intrabc_round_trips_through_decode_twin() {
for target in [0u8, 1u8] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_use_intrabc(&mut writer, &mut enc_cdfs, target, true).unwrap();
let bytes = writer.finish();
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = walker
.decode_use_intrabc(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_8X8, true)
.unwrap();
assert_eq!(got, target, "use_intrabc = {target} round-trips");
assert_eq!(enc_cdfs.intrabc_cdf(), dec_cdfs.intrabc_cdf());
}
}
#[test]
fn write_use_intrabc_fall_through_no_bits_and_rejects() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_use_intrabc(&mut writer, &mut cdfs, 0, false).unwrap();
assert_eq!(
writer.finish(),
SymbolWriter::new(false).finish(),
"fall-through arm emits no bits"
);
assert_eq!(
cdfs.intrabc_cdf(),
TileCdfContext::new_from_defaults().intrabc_cdf()
);
let mut writer = SymbolWriter::new(false);
assert!(matches!(
write_use_intrabc(&mut writer, &mut cdfs, 1, false).unwrap_err(),
Error::PartitionWalkOutOfRange
));
for allow in [false, true] {
let mut writer = SymbolWriter::new(false);
assert!(matches!(
write_use_intrabc(&mut writer, &mut cdfs, 2, allow).unwrap_err(),
Error::PartitionWalkOutOfRange
));
}
}
#[test]
fn write_intra_frame_intrabc_arm_round_trips() {
for (stack, mi_row, target_mv) in [
(mk_intrabc_mv_stack([-64, 8], [0, 0], 2), 16u32, [-48, -40]),
(
mk_intrabc_mv_stack([0, 0], [0, 0], 0),
0u32,
[0, -2560 + 64],
),
] {
let inputs = IntrabcArmInputs {
mv: target_mv,
mv_stack: &stack,
use_128x128_superblock: false,
mi_row,
mi_row_start: 0,
};
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let info =
write_intra_frame_intrabc_arm(&mut writer, &mut enc_cdfs, true, Some(&inputs))
.unwrap()
.expect("intrabc arm fired");
let bytes = writer.finish();
let want_pred = assign_mv_pred_mv_intrabc(&stack, false, mi_row, 0).unwrap();
assert_eq!(
info,
IntrabcBlockInfo {
is_inter: 1,
y_mode: DC_PRED_U8,
uv_mode: DC_PRED_U8,
motion_mode: MOTION_MODE_SIMPLE,
compound_type: COMPOUND_AVERAGE,
palette_size_y: 0,
palette_size_uv: 0,
interp_filter: [BILINEAR, BILINEAR],
mv: target_mv,
pred_mv: want_pred,
}
);
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let use_intrabc = walker
.decode_use_intrabc(&mut dec, &mut dec_cdfs, mi_row, 0, BLOCK_16X16, true)
.unwrap();
assert_eq!(use_intrabc, 1);
let mv_joint = dec
.read_symbol(dec_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT))
.unwrap() as u8;
let mut diff = [0i32; 2];
if mv_joint == MV_JOINT_HZVNZ || mv_joint == MV_JOINT_HNZVNZ {
diff[0] = decode_read_mv_component_mirror(
&mut dec,
&mut dec_cdfs,
MV_INTRABC_CONTEXT,
0,
true,
false,
);
}
if mv_joint == MV_JOINT_HNZVZ || mv_joint == MV_JOINT_HNZVNZ {
diff[1] = decode_read_mv_component_mirror(
&mut dec,
&mut dec_cdfs,
MV_INTRABC_CONTEXT,
1,
true,
false,
);
}
let recovered = [want_pred[0] + diff[0], want_pred[1] + diff[1]];
assert_eq!(recovered, target_mv, "intrabc Mv reconstructs");
assert_eq!(enc_cdfs.intrabc_cdf(), dec_cdfs.intrabc_cdf());
assert_eq!(
enc_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT),
dec_cdfs.mv_joint_cdf(MV_INTRABC_CONTEXT)
);
}
}
#[test]
fn write_intra_frame_intrabc_arm_none_matches_leaf() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let info = write_intra_frame_intrabc_arm(&mut writer, &mut cdfs, true, None).unwrap();
assert!(info.is_none());
let bytes = writer.finish();
let mut leaf_cdfs = TileCdfContext::new_from_defaults();
let mut leaf_writer = SymbolWriter::new(false);
write_use_intrabc(&mut leaf_writer, &mut leaf_cdfs, 0, true).unwrap();
assert_eq!(bytes, leaf_writer.finish(), "byte-equal to the leaf");
let (mut walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = walker
.decode_use_intrabc(&mut dec, &mut dec_cdfs, 0, 0, BLOCK_8X8, true)
.unwrap();
assert_eq!(got, 0);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let info = write_intra_frame_intrabc_arm(&mut writer, &mut cdfs, false, None).unwrap();
assert!(info.is_none());
assert_eq!(writer.finish(), SymbolWriter::new(false).finish());
}
#[test]
fn write_intra_frame_intrabc_arm_rejects() {
let stack = mk_intrabc_mv_stack([-64, 8], [0, 0], 2);
let inputs = IntrabcArmInputs {
mv: [-48, -40],
mv_stack: &stack,
use_128x128_superblock: false,
mi_row: 16,
mi_row_start: 0,
};
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
assert!(matches!(
write_intra_frame_intrabc_arm(&mut writer, &mut cdfs, false, Some(&inputs))
.unwrap_err(),
Error::PartitionWalkOutOfRange
));
let above_tile = IntrabcArmInputs {
mi_row: 31,
mi_row_start: 32,
..inputs
};
let mut writer = SymbolWriter::new(false);
assert!(matches!(
write_intra_frame_intrabc_arm(&mut writer, &mut cdfs, true, Some(&above_tile))
.unwrap_err(),
Error::PartitionWalkOutOfRange
));
let fractional = IntrabcArmInputs {
mv: [-64, 12],
..inputs
};
let mut writer = SymbolWriter::new(false);
assert!(matches!(
write_intra_frame_intrabc_arm(&mut writer, &mut cdfs, true, Some(&fractional))
.unwrap_err(),
Error::PartitionWalkOutOfRange
));
}
#[allow(clippy::too_many_arguments)]
fn mirror_inter_block_mode_info(
bytes: &[u8],
cdfs: &mut TileCdfContext,
mv_stack: &FindMvStackResult,
skip_mode: u8,
skip_mode_frame: [i32; 2],
seg_skip_active: bool,
seg_globalmv_active: bool,
force_integer_mv: bool,
allow_high_precision_mv: bool,
) -> ([i32; 2], u8, u32, [[i32; 2]; 2]) {
let pad = if bytes.is_empty() {
vec![0u8]
} else {
bytes.to_vec()
};
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), false).unwrap();
let ref_frame: [i32; 2] = if skip_mode != 0 {
[skip_mode_frame[0], skip_mode_frame[1]]
} else if seg_skip_active || seg_globalmv_active {
[1, -1]
} else {
let p1_ctx = ref_count_ctx(0, 0);
let single_ref_p1 = dec.read_symbol(cdfs.single_ref_cdf(p1_ctx, 0)).unwrap() as u8;
let r0: i32 = if single_ref_p1 != 0 {
let single_ref_p2 = dec
.read_symbol(cdfs.single_ref_cdf(ref_count_ctx(0, 0), 1))
.unwrap() as u8;
if single_ref_p2 == 0 {
let single_ref_p6 = dec
.read_symbol(cdfs.single_ref_cdf(ref_count_ctx(0, 0), 5))
.unwrap() as u8;
if single_ref_p6 != 0 {
6
} else {
5
}
} else {
7
}
} else {
let single_ref_p3 = dec
.read_symbol(cdfs.single_ref_cdf(ref_count_ctx(0, 0), 2))
.unwrap() as u8;
if single_ref_p3 != 0 {
let single_ref_p5 = dec
.read_symbol(cdfs.single_ref_cdf(ref_count_ctx(0, 0), 4))
.unwrap() as u8;
if single_ref_p5 != 0 {
4
} else {
3
}
} else {
let single_ref_p4 = dec
.read_symbol(cdfs.single_ref_cdf(ref_count_ctx(0, 0), 3))
.unwrap() as u8;
if single_ref_p4 != 0 {
2
} else {
1
}
}
};
[r0, -1]
};
let is_compound = ref_frame[1] > 0;
let y_mode: u8 = if skip_mode != 0 {
MODE_NEAREST_NEARESTMV
} else if seg_skip_active || seg_globalmv_active {
MODE_GLOBALMV
} else if is_compound {
let cm_ctx = compound_mode_ctx(
mv_stack.ref_mv_context as usize,
mv_stack.new_mv_context as usize,
);
let cm = dec.read_symbol(cdfs.compound_mode_cdf(cm_ctx)).unwrap() as u8;
MODE_NEAREST_NEARESTMV + cm
} else {
let new_mv = dec
.read_symbol(cdfs.new_mv_cdf(mv_stack.new_mv_context as usize))
.unwrap();
if new_mv == 0 {
MODE_NEWMV
} else {
let zero_mv = dec
.read_symbol(cdfs.zero_mv_cdf(mv_stack.zero_mv_context as usize))
.unwrap();
if zero_mv == 0 {
MODE_GLOBALMV
} else {
let ref_mv = dec
.read_symbol(cdfs.ref_mv_cdf(mv_stack.ref_mv_context as usize))
.unwrap();
if ref_mv == 0 {
MODE_NEARESTMV
} else {
MODE_NEARMV
}
}
}
};
let mut ref_mv_idx: u32 = 0;
if y_mode == MODE_NEWMV || y_mode == MODE_NEW_NEWMV {
for idx in 0u32..2 {
if mv_stack.num_mv_found > idx + 1 {
let drl_ctx = mv_stack.drl_ctx_stack[idx as usize] as usize;
let drl = dec.read_symbol(cdfs.drl_mode_cdf(drl_ctx)).unwrap();
if drl == 0 {
ref_mv_idx = idx;
break;
}
ref_mv_idx = idx + 1;
}
}
} else if has_nearmv(y_mode) {
ref_mv_idx = 1;
for idx in 1u32..3 {
if mv_stack.num_mv_found > idx + 1 {
let drl_ctx = mv_stack.drl_ctx_stack[idx as usize] as usize;
let drl = dec.read_symbol(cdfs.drl_mode_cdf(drl_ctx)).unwrap();
if drl == 0 {
ref_mv_idx = idx;
break;
}
ref_mv_idx = idx + 1;
}
}
}
let list_count: u8 = if is_compound { 2 } else { 1 };
let mut mv: [[i32; 2]; 2] = [[0, 0], [0, 0]];
for i in 0..list_count {
let pred = assign_mv_pred_mv(mv_stack, y_mode, i, ref_mv_idx).unwrap();
if crate::cdf::get_mode(y_mode, i as usize) == MODE_NEWMV {
let mv_ctx = 0usize;
let mv_joint = dec.read_symbol(cdfs.mv_joint_cdf(mv_ctx)).unwrap() as u8;
let mut diff = [0i32; 2];
if mv_joint == MV_JOINT_HZVNZ || mv_joint == MV_JOINT_HNZVNZ {
diff[0] = decode_read_mv_component_mirror(
&mut dec,
cdfs,
mv_ctx,
0,
force_integer_mv,
allow_high_precision_mv,
);
}
if mv_joint == MV_JOINT_HNZVZ || mv_joint == MV_JOINT_HNZVNZ {
diff[1] = decode_read_mv_component_mirror(
&mut dec,
cdfs,
mv_ctx,
1,
force_integer_mv,
allow_high_precision_mv,
);
}
mv[i as usize] = [pred[0] + diff[0], pred[1] + diff[1]];
} else {
mv[i as usize] = pred;
}
}
(ref_frame, y_mode, ref_mv_idx, mv)
}
#[allow(clippy::type_complexity)]
fn empty_neighbour_inputs() -> (bool, bool, bool, bool, bool, bool, [i32; 2], [i32; 2]) {
(
false,
false, true,
true, false,
false, [-1, -1],
[-1, -1], )
}
#[test]
fn write_inter_block_mode_info_single_newmv_round_trip() {
let stack = mk_mv_stack(3); let pred = assign_mv_pred_mv(&stack, MODE_NEWMV, 0, 1).unwrap();
let target_mv = [pred[0] + 7, pred[1] - 5];
let mv = [target_mv, [0, 0]];
let ref_frame = [4i32, -1]; let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_block_mode_info(
&mut writer,
&mut enc_cdfs,
ref_frame,
MODE_NEWMV,
mv,
1,
&stack,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&InterBlockModeInfoTail::bit_silent(),
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let (rf, ym, rmi, dmv) = mirror_inter_block_mode_info(
&bytes,
&mut dec_cdfs,
&stack,
0,
[0, 0],
false,
false,
false,
true,
);
assert_eq!(rf, ref_frame, "RefFrame round-trips");
assert_eq!(ym, MODE_NEWMV, "YMode round-trips");
assert_eq!(rmi, 1, "RefMvIdx round-trips");
assert_eq!(dmv[0], target_mv, "list-0 NEWMV Mv round-trips");
assert_eq!(enc_cdfs.new_mv_cdf(0), dec_cdfs.new_mv_cdf(0));
assert_eq!(enc_cdfs.mv_joint_cdf(0), dec_cdfs.mv_joint_cdf(0));
}
#[test]
fn write_inter_block_mode_info_single_nearmv_round_trip() {
let stack = mk_mv_stack(4); let pred = assign_mv_pred_mv(&stack, MODE_NEARMV, 0, 2).unwrap();
let mv = [pred, [0, 0]]; let ref_frame = [2i32, -1]; let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_block_mode_info(
&mut writer,
&mut enc_cdfs,
ref_frame,
MODE_NEARMV,
mv,
2,
&stack,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&InterBlockModeInfoTail::bit_silent(),
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let (rf, ym, rmi, dmv) = mirror_inter_block_mode_info(
&bytes,
&mut dec_cdfs,
&stack,
0,
[0, 0],
false,
false,
false,
true,
);
assert_eq!(rf, ref_frame);
assert_eq!(ym, MODE_NEARMV);
assert_eq!(rmi, 2, "RefMvIdx from has_nearmv drl arm");
assert_eq!(dmv[0], pred, "NEARMV Mv = PredMv (no diff bits)");
}
#[test]
fn write_inter_block_mode_info_single_nearestmv_round_trip() {
let stack = mk_mv_stack(4);
let pred = assign_mv_pred_mv(&stack, MODE_NEARESTMV, 0, 0).unwrap();
let mv = [pred, [0, 0]];
let ref_frame = [1i32, -1]; let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_block_mode_info(
&mut writer,
&mut enc_cdfs,
ref_frame,
MODE_NEARESTMV,
mv,
0,
&stack,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&InterBlockModeInfoTail::bit_silent(),
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let (rf, ym, rmi, dmv) = mirror_inter_block_mode_info(
&bytes,
&mut dec_cdfs,
&stack,
0,
[0, 0],
false,
false,
false,
true,
);
assert_eq!(rf, ref_frame);
assert_eq!(ym, MODE_NEARESTMV);
assert_eq!(rmi, 0);
assert_eq!(dmv[0], pred);
}
#[test]
fn write_inter_block_mode_info_skip_mode_emits_no_bits() {
let stack = mk_mv_stack(2);
let skip_frame = [1i8, 5i8]; let pred0 = assign_mv_pred_mv(&stack, MODE_NEAREST_NEARESTMV, 0, 0).unwrap();
let pred1 = assign_mv_pred_mv(&stack, MODE_NEAREST_NEARESTMV, 1, 0).unwrap();
let mv = [pred0, pred1];
let ref_frame = [1i32, 5];
let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_block_mode_info(
&mut writer,
&mut enc_cdfs,
ref_frame,
MODE_NEAREST_NEARESTMV,
mv,
0,
&stack,
BLOCK_8X8,
1,
skip_frame,
false,
0,
false,
false,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&InterBlockModeInfoTail::bit_silent(),
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let (rf, ym, rmi, dmv) = mirror_inter_block_mode_info(
&bytes,
&mut dec_cdfs,
&stack,
1,
[1, 5],
false,
false,
false,
true,
);
assert_eq!(rf, ref_frame);
assert_eq!(ym, MODE_NEAREST_NEARESTMV);
assert_eq!(rmi, 0);
assert_eq!(dmv[0], pred0);
assert_eq!(dmv[1], pred1);
}
#[test]
fn write_inter_block_mode_info_seg_globalmv_emits_no_bits() {
let stack = mk_mv_stack(2);
let mv = [stack.global_mvs[0], [0, 0]];
let ref_frame = [1i32, -1];
let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_inter_block_mode_info(
&mut writer,
&mut enc_cdfs,
ref_frame,
MODE_GLOBALMV,
mv,
0,
&stack,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
true,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&InterBlockModeInfoTail::bit_silent(),
)
.unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let (rf, ym, _rmi, dmv) = mirror_inter_block_mode_info(
&bytes,
&mut dec_cdfs,
&stack,
0,
[0, 0],
false,
true,
false,
true,
);
assert_eq!(rf, ref_frame);
assert_eq!(ym, MODE_GLOBALMV);
assert_eq!(dmv[0], stack.global_mvs[0]);
}
#[test]
fn write_inter_block_mode_info_rejects_arm_inconsistent_y_mode() {
let stack = mk_mv_stack(2);
let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_inter_block_mode_info(
&mut writer,
&mut enc_cdfs,
[1, -1],
MODE_NEWMV, [[0, 0], [0, 0]],
0,
&stack,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
true,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&InterBlockModeInfoTail::bit_silent(),
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn ii_zero() -> InterIntraReadout {
InterIntraReadout {
interintra: 0,
interintra_mode: None,
wedge_interintra: None,
wedge_index: None,
}
}
fn roundtrip_interintra(
readout: InterIntraReadout,
mi_size: usize,
skip_mode: u8,
is_compound: bool,
enable: bool,
) -> InterIntraReadout {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_interintra_mode(
&mut writer,
&mut enc_cdfs,
&readout,
mi_size,
skip_mode,
is_compound,
enable,
)
.unwrap();
let bytes = writer.finish();
let (walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), false).unwrap();
let got = walker
.read_interintra_mode(
&mut dec,
&mut dec_cdfs,
mi_size,
skip_mode,
is_compound,
enable,
)
.unwrap();
if let Some(ctx) = crate::cdf::interintra_ctx(mi_size) {
assert_eq!(enc_cdfs.inter_intra.get(ctx), dec_cdfs.inter_intra.get(ctx));
assert_eq!(
enc_cdfs.inter_intra_mode.get(ctx),
dec_cdfs.inter_intra_mode.get(ctx)
);
}
if mi_size < BLOCK_SIZES {
assert_eq!(
enc_cdfs.wedge_inter_intra[mi_size],
dec_cdfs.wedge_inter_intra[mi_size]
);
assert_eq!(enc_cdfs.wedge_index[mi_size], dec_cdfs.wedge_index[mi_size]);
}
got
}
#[test]
fn write_interintra_mode_gate_closed_writes_no_bits() {
let configs = [
(BLOCK_8X8, 1u8, false, true),
(BLOCK_8X8, 0u8, false, false),
(BLOCK_8X8, 0u8, true, true),
(BLOCK_4X4, 0u8, false, true),
(BLOCK_64X64, 0u8, false, true),
];
for (mi_size, skip_mode, is_compound, enable) in configs {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_interintra_mode(
&mut writer,
&mut cdfs,
&ii_zero(),
mi_size,
skip_mode,
is_compound,
enable,
)
.unwrap();
let got = writer.finish();
let want = SymbolWriter::new(false).finish();
assert_eq!(got, want, "gate-closed config writes no symbols");
}
}
#[test]
fn write_interintra_mode_gate_closed_rejects_nonzero_readout() {
let bad = InterIntraReadout {
interintra: 1,
interintra_mode: Some(0),
wedge_interintra: Some(0),
wedge_index: None,
};
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_interintra_mode(&mut writer, &mut cdfs, &bad, BLOCK_8X8, 1, false, true)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_interintra_mode_round_trips_inner_arm_no_wedge() {
for mi_size in [BLOCK_8X8, BLOCK_16X16, BLOCK_32X32] {
let got = roundtrip_interintra(ii_zero(), mi_size, 0, false, true);
assert_eq!(got, ii_zero(), "interintra = 0 at mi_size {mi_size}");
for ii_mode in 0..INTERINTRA_MODES as u8 {
let readout = InterIntraReadout {
interintra: 1,
interintra_mode: Some(ii_mode),
wedge_interintra: Some(0),
wedge_index: None,
};
let got = roundtrip_interintra(readout, mi_size, 0, false, true);
assert_eq!(got, readout, "mode {ii_mode} at mi_size {mi_size}");
}
}
}
#[test]
fn write_interintra_mode_round_trips_wedge_indices() {
for wi in [0u8, 7, 15] {
let readout = InterIntraReadout {
interintra: 1,
interintra_mode: Some(1),
wedge_interintra: Some(1),
wedge_index: Some(wi),
};
let got = roundtrip_interintra(readout, BLOCK_8X8, 0, false, true);
assert_eq!(got, readout, "wedge_index {wi}");
}
}
#[test]
fn write_interintra_mode_rejects_inconsistent_readouts() {
let cases = [
InterIntraReadout {
interintra: 2,
interintra_mode: None,
wedge_interintra: None,
wedge_index: None,
},
InterIntraReadout {
interintra: 0,
interintra_mode: Some(0),
wedge_interintra: None,
wedge_index: None,
},
InterIntraReadout {
interintra: 1,
interintra_mode: None,
wedge_interintra: Some(0),
wedge_index: None,
},
InterIntraReadout {
interintra: 1,
interintra_mode: Some(INTERINTRA_MODES as u8),
wedge_interintra: Some(0),
wedge_index: None,
},
InterIntraReadout {
interintra: 1,
interintra_mode: Some(0),
wedge_interintra: Some(1),
wedge_index: None,
},
InterIntraReadout {
interintra: 1,
interintra_mode: Some(0),
wedge_interintra: Some(0),
wedge_index: Some(0),
},
InterIntraReadout {
interintra: 1,
interintra_mode: Some(0),
wedge_interintra: Some(1),
wedge_index: Some(WEDGE_TYPES as u8),
},
];
for bad in cases {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err =
write_interintra_mode(&mut writer, &mut cdfs, &bad, BLOCK_8X8, 0, false, true)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"rejected {bad:?}"
);
}
}
#[test]
fn write_interintra_mode_interintra_zero_emits_one_symbol() {
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_interintra_mode(&mut wa, &mut a, &ii_zero(), BLOCK_16X16, 0, false, true).unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let ctx = crate::cdf::interintra_ctx(BLOCK_16X16).unwrap();
let row = b.inter_intra_cdf(ctx).unwrap();
wb.write_symbol(0, row).unwrap();
let want = wb.finish();
assert_eq!(got, want, "open gate + interintra = 0 is one S()");
}
fn walker_with_overlappable_above() -> PartitionWalker {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(1, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
walker
}
#[test]
fn write_motion_mode_short_circuit_arms_write_no_bits_and_reject_non_simple() {
struct Arm {
mi_size: usize,
skip_mode: u8,
is_compound: bool,
ref_frame: [i32; 2],
y_mode: u8,
switchable: bool,
force_integer_mv: bool,
gm_type_ref0: i32,
has_overlappable: bool,
}
let base = Arm {
mi_size: BLOCK_16X16,
skip_mode: 0,
is_compound: false,
ref_frame: [1, -1],
y_mode: MODE_NEARESTMV,
switchable: true,
force_integer_mv: false,
gm_type_ref0: 0,
has_overlappable: true,
};
let arms = [
Arm {
skip_mode: 1,
..base
},
Arm {
switchable: false,
..base
},
Arm {
mi_size: BLOCK_4X8,
..base
},
Arm {
y_mode: MODE_GLOBALMV,
gm_type_ref0: GM_TYPE_TRANSLATION + 1,
..base
},
Arm {
is_compound: true,
ref_frame: [1, 4],
..base
},
Arm {
ref_frame: [1, 0],
..base
},
Arm {
has_overlappable: false,
..base
},
];
for (i, arm) in arms.iter().enumerate() {
let mut gm_type = [0i32; 8];
gm_type[arm.ref_frame[0] as usize] = arm.gm_type_ref0;
let run = |motion_mode: u8| {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let r = write_motion_mode(
&mut writer,
&mut cdfs,
motion_mode,
arm.mi_size,
arm.skip_mode,
arm.is_compound,
arm.ref_frame,
arm.y_mode,
0,
arm.switchable,
true,
arm.force_integer_mv,
gm_type,
[false; 7],
arm.has_overlappable,
);
(r, writer.finish())
};
let (ok, bytes) = run(MOTION_MODE_SIMPLE);
ok.unwrap();
assert_eq!(
bytes,
SymbolWriter::new(false).finish(),
"short-circuit arm {i} writes no symbols"
);
let (err, _) = run(MOTION_MODE_OBMC);
assert!(
matches!(err.unwrap_err(), Error::PartitionWalkOutOfRange),
"short-circuit arm {i} rejects OBMC"
);
}
}
#[test]
fn write_motion_mode_arm_a_round_trips() {
for target in [MOTION_MODE_SIMPLE, MOTION_MODE_OBMC] {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_motion_mode(
&mut writer,
&mut enc_cdfs,
target,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
0,
true,
true,
true,
[0; 8],
[false; 7],
true,
)
.unwrap();
let bytes = writer.finish();
let walker = walker_with_overlappable_above();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = walker
.read_motion_mode(
&mut dec,
&mut dec_cdfs,
2,
0,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
0,
true,
true,
true,
[0; 8],
[false; 7],
)
.unwrap();
assert_eq!(got, target, "arm A target {target} round-trips");
assert_eq!(enc_cdfs.use_obmc[BLOCK_8X8], dec_cdfs.use_obmc[BLOCK_8X8]);
}
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_motion_mode(
&mut writer,
&mut cdfs,
crate::cdf::MOTION_MODE_WARPED_CAUSAL,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
0,
true,
true,
true,
[0; 8],
[false; 7],
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_motion_mode_arm_b_round_trips() {
for target in 0..MOTION_MODES as u8 {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_motion_mode(
&mut writer,
&mut enc_cdfs,
target,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
2,
true,
true,
false,
[0; 8],
[false; 7],
true,
)
.unwrap();
let bytes = writer.finish();
let walker = walker_with_overlappable_above();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let got = walker
.read_motion_mode(
&mut dec,
&mut dec_cdfs,
2,
0,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
2,
true,
true,
false,
[0; 8],
[false; 7],
)
.unwrap();
assert_eq!(got, target, "arm B target {target} round-trips");
assert_eq!(
enc_cdfs.motion_mode[BLOCK_8X8],
dec_cdfs.motion_mode[BLOCK_8X8]
);
}
}
#[test]
fn write_motion_mode_scaled_ref_routes_to_arm_a() {
let mut scaled = [false; 7];
scaled[0] = true; let mut cdfs = TileCdfContext::new_from_defaults();
let use_obmc_before = cdfs.use_obmc[BLOCK_8X8];
let motion_mode_before = cdfs.motion_mode[BLOCK_8X8];
let mut writer = SymbolWriter::new(false);
write_motion_mode(
&mut writer,
&mut cdfs,
MOTION_MODE_OBMC,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
2,
true,
true,
false,
[0; 8],
scaled,
true,
)
.unwrap();
assert_ne!(cdfs.use_obmc[BLOCK_8X8], use_obmc_before);
assert_eq!(cdfs.motion_mode[BLOCK_8X8], motion_mode_before);
let mut writer = SymbolWriter::new(false);
let err = write_motion_mode(
&mut writer,
&mut cdfs,
crate::cdf::MOTION_MODE_WARPED_CAUSAL,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
2,
true,
true,
false,
[0; 8],
scaled,
true,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn ct_preset(ty: u8) -> crate::cdf::CompoundTypeReadout {
crate::cdf::CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: ty,
wedge_index: None,
wedge_sign: None,
mask_type: None,
}
}
#[allow(clippy::too_many_arguments)]
fn roundtrip_compound_type(
readout: crate::cdf::CompoundTypeReadout,
mi_size: usize,
skip_mode: u8,
is_compound: bool,
interintra: u8,
wedge_interintra: u8,
enable_masked_compound: bool,
enable_jnt_comp: bool,
dist_equal: bool,
) -> crate::cdf::CompoundTypeReadout {
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_type(
&mut writer,
&mut enc_cdfs,
&readout,
mi_size,
skip_mode,
is_compound,
interintra,
wedge_interintra,
enable_masked_compound,
enable_jnt_comp,
dist_equal,
true,
true,
false,
false,
false,
false,
0,
0,
1,
1,
)
.unwrap();
let bytes = writer.finish();
let (walker, mut dec_cdfs) = fresh_walker_and_cdfs();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), false).unwrap();
walker
.read_compound_type(
&mut dec,
&mut dec_cdfs,
2,
2,
mi_size,
skip_mode,
is_compound,
interintra,
wedge_interintra,
enable_masked_compound,
enable_jnt_comp,
dist_equal,
true,
true,
false,
false,
false,
false,
)
.unwrap()
}
#[test]
fn write_compound_type_skip_mode_writes_no_bits() {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_type(
&mut writer,
&mut cdfs,
&ct_preset(COMPOUND_AVERAGE),
BLOCK_8X8,
1,
true,
0,
0,
true,
true,
false,
false,
false,
true,
true,
false,
false,
0,
0,
1,
1,
)
.unwrap();
assert_eq!(writer.finish(), SymbolWriter::new(false).finish());
let mut writer = SymbolWriter::new(false);
let err = write_compound_type(
&mut writer,
&mut cdfs,
&ct_preset(COMPOUND_DISTANCE),
BLOCK_8X8,
1,
true,
0,
0,
true,
true,
false,
false,
false,
true,
true,
false,
false,
0,
0,
1,
1,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_compound_type_single_pred_arms_write_no_bits() {
let shapes = [
(0u8, 0u8, COMPOUND_AVERAGE),
(1, 0, COMPOUND_INTRA),
(1, 1, COMPOUND_WEDGE),
];
for (ii, wii, expected) in shapes {
let got = roundtrip_compound_type(
ct_preset(expected),
BLOCK_8X8,
0,
false,
ii,
wii,
true,
true,
false,
);
assert_eq!(got, ct_preset(expected), "shape ({ii}, {wii})");
let bad = if expected == COMPOUND_AVERAGE {
COMPOUND_INTRA
} else {
COMPOUND_AVERAGE
};
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_type(
&mut writer,
&mut cdfs,
&ct_preset(bad),
BLOCK_8X8,
0,
false,
ii,
wii,
true,
true,
false,
true,
true,
false,
false,
false,
false,
0,
0,
1,
1,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
}
#[test]
fn write_compound_type_group1_wedge_round_trips() {
for (wi, ws) in [(0u8, 0u8), (5, 1), (15, 0)] {
let readout = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_WEDGE,
wedge_index: Some(wi),
wedge_sign: Some(ws),
mask_type: None,
};
let got = roundtrip_compound_type(readout, BLOCK_8X8, 0, true, 0, 0, true, true, false);
assert_eq!(got, readout, "wedge ({wi}, {ws})");
}
}
#[test]
fn write_compound_type_group1_diffwtd_round_trips() {
for mt in [0u8, 1] {
let readout = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_DIFFWTD,
wedge_index: None,
wedge_sign: None,
mask_type: Some(mt),
};
let got = roundtrip_compound_type(readout, BLOCK_8X8, 0, true, 0, 0, true, true, false);
assert_eq!(got, readout, "diffwtd mask_type {mt}");
}
}
#[test]
fn write_compound_type_group1_n_zero_forces_diffwtd() {
let readout = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_DIFFWTD,
wedge_index: None,
wedge_sign: None,
mask_type: Some(1),
};
let got = roundtrip_compound_type(readout, BLOCK_64X64, 0, true, 0, 0, true, true, false);
assert_eq!(got, readout);
let bad = crate::cdf::CompoundTypeReadout {
compound_type: COMPOUND_WEDGE,
wedge_index: Some(0),
wedge_sign: Some(0),
mask_type: None,
..readout
};
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_compound_type(
&mut writer,
&mut cdfs,
&bad,
BLOCK_64X64,
0,
true,
0,
0,
true,
true,
false,
true,
true,
false,
false,
false,
false,
0,
0,
1,
1,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_compound_type_group0_jnt_round_trips() {
for dist_equal in [false, true] {
for (ci, ty) in [(1u8, COMPOUND_AVERAGE), (0, COMPOUND_DISTANCE)] {
let readout = crate::cdf::CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: ci,
compound_type: ty,
wedge_index: None,
wedge_sign: None,
mask_type: None,
};
let got = roundtrip_compound_type(
readout, BLOCK_8X8, 0, true, 0, 0, true, true, dist_equal,
);
assert_eq!(got, readout, "jnt ({ci}, dist_equal {dist_equal})");
}
}
}
#[test]
fn write_compound_type_group0_no_jnt_emits_one_symbol() {
let mut a = TileCdfContext::new_from_defaults();
let mut wa = SymbolWriter::new(false);
write_compound_type(
&mut wa,
&mut a,
&ct_preset(COMPOUND_AVERAGE),
BLOCK_8X8,
0,
true,
0,
0,
true,
false,
false,
true,
true,
false,
false,
false,
false,
0,
0,
1,
1,
)
.unwrap();
let got = wa.finish();
let mut b = TileCdfContext::new_from_defaults();
let mut wb = SymbolWriter::new(false);
let ctx = crate::cdf::comp_group_idx_ctx(true, false, 0, false, true, false, 0, false);
let row = b.comp_group_idx_cdf(ctx);
wb.write_symbol(0, row).unwrap();
let want = wb.finish();
assert_eq!(
got, want,
"group-0 no-jnt is exactly one comp_group_idx S()"
);
}
#[test]
fn write_compound_type_rejects_inconsistent_readouts() {
let run =
|readout: &crate::cdf::CompoundTypeReadout, enable_masked: bool, enable_jnt: bool| {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_compound_type(
&mut writer,
&mut cdfs,
readout,
BLOCK_8X8,
0,
true,
0,
0,
enable_masked,
enable_jnt,
false,
true,
true,
false,
false,
false,
false,
0,
0,
1,
1,
)
};
let bad = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_DIFFWTD,
wedge_index: None,
wedge_sign: None,
mask_type: Some(0),
};
assert!(run(&bad, false, true).is_err());
let bad = crate::cdf::CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: COMPOUND_DISTANCE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
};
assert!(run(&bad, true, true).is_err());
let bad = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_WEDGE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
};
assert!(run(&bad, true, true).is_err());
let bad = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_DIFFWTD,
wedge_index: Some(0),
wedge_sign: None,
mask_type: Some(0),
};
assert!(run(&bad, true, true).is_err());
let bad = crate::cdf::CompoundTypeReadout {
comp_group_idx: 1,
compound_idx: 1,
compound_type: COMPOUND_WEDGE,
wedge_index: Some(WEDGE_TYPES as u8),
wedge_sign: Some(0),
mask_type: None,
};
assert!(run(&bad, true, true).is_err());
let bad = crate::cdf::CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 0,
compound_type: COMPOUND_AVERAGE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
};
assert!(run(&bad, true, false).is_err());
}
fn identity_gm_params() -> [[i32; 6]; 8] {
let mut params = [[0i32; 6]; 8];
for p in &mut params {
p[2] = 1 << 16;
p[5] = 1 << 16;
}
params
}
#[allow(clippy::too_many_arguments)]
fn roundtrip_interp_filter(
readout: InterpolationFilterReadout,
mi_size: usize,
skip_mode: u8,
y_mode: u8,
motion_mode: u8,
interpolation_filter: u8,
enable_dual_filter: bool,
gm_type: [i32; 8],
) -> InterpolationFilterReadout {
let ref_frame = [1i32, -1];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_interpolation_filter(
&mut writer,
&mut enc_cdfs,
&readout,
mi_size,
skip_mode,
false,
ref_frame,
y_mode,
motion_mode,
interpolation_filter,
enable_dual_filter,
gm_type,
false,
false,
[-1, -1],
[-1, -1],
[EIGHTTAP, EIGHTTAP],
[EIGHTTAP, EIGHTTAP],
)
.unwrap();
let bytes = writer.finish();
let pad = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec = SymbolDecoder::init_symbol(&pad, pad.len(), false).unwrap();
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let got = walker
.read_interpolation_filter(
&mut dec,
&mut dec_cdfs,
0,
0,
mi_size,
skip_mode,
false,
ref_frame,
y_mode,
motion_mode,
interpolation_filter,
enable_dual_filter,
gm_type,
)
.unwrap();
assert_eq!(enc_cdfs.interp_filter, dec_cdfs.interp_filter);
got
}
#[test]
fn write_interpolation_filter_switchable_dual_round_trips() {
let target = InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SMOOTH],
read_from_bitstream: [true, true],
};
let got = roundtrip_interp_filter(
target,
BLOCK_16X16,
0,
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
true,
[0; 8],
);
assert_eq!(got, target);
}
#[test]
fn write_interpolation_filter_switchable_single_dir_round_trips() {
let target = InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SMOOTH, crate::cdf::EIGHTTAP_SMOOTH],
read_from_bitstream: [true, false],
};
let got = roundtrip_interp_filter(
target,
BLOCK_8X8,
0,
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
false,
[0; 8],
);
assert_eq!(got, target);
}
#[test]
fn write_interpolation_filter_non_switchable_writes_no_bits() {
let target = InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP],
read_from_bitstream: [false, false],
};
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_interpolation_filter(
&mut writer,
&mut cdfs,
&target,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
crate::cdf::EIGHTTAP_SHARP,
true,
[0; 8],
false,
false,
[-1, -1],
[-1, -1],
[EIGHTTAP, EIGHTTAP],
[EIGHTTAP, EIGHTTAP],
)
.unwrap();
assert_eq!(writer.finish(), SymbolWriter::new(false).finish());
let got = roundtrip_interp_filter(
target,
BLOCK_8X8,
0,
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
crate::cdf::EIGHTTAP_SHARP,
true,
[0; 8],
);
assert_eq!(got, target);
}
#[test]
fn write_interpolation_filter_needs_gate_arms() {
let forced = InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [false, false],
};
let got = roundtrip_interp_filter(
forced,
BLOCK_8X8,
1,
MODE_NEAREST_NEARESTMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
false,
[0; 8],
);
assert_eq!(got, forced);
let got = roundtrip_interp_filter(
forced,
BLOCK_16X16,
0,
MODE_NEARESTMV,
MOTION_MODE_WARPED_CAUSAL,
SWITCHABLE,
true,
[0; 8],
);
assert_eq!(got, forced);
let mut gm = [0i32; 8];
gm[1] = GM_TYPE_TRANSLATION + 1; let got = roundtrip_interp_filter(
forced,
BLOCK_16X16,
0,
MODE_GLOBALMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
true,
gm,
);
assert_eq!(got, forced);
let mut gm = [0i32; 8];
gm[1] = GM_TYPE_TRANSLATION;
let coded = InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP],
read_from_bitstream: [true, false],
};
let got = roundtrip_interp_filter(
coded,
BLOCK_16X16,
0,
MODE_GLOBALMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
false,
gm,
);
assert_eq!(got, coded);
}
#[test]
fn write_interpolation_filter_neighbour_ctx_selection() {
let target = InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SMOOTH, crate::cdf::EIGHTTAP_SMOOTH],
read_from_bitstream: [true, false],
};
let ref_frame = [1i32, -1];
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_interpolation_filter(
&mut writer,
&mut enc_cdfs,
&target,
BLOCK_8X8,
0,
false,
ref_frame,
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
false,
[0; 8],
true,
false,
[1, -1], [-1, -1],
[crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP],
[EIGHTTAP, EIGHTTAP],
)
.unwrap();
let bytes = writer.finish();
let expected_ctx = interp_filter_ctx(
crate::cdf::EIGHTTAP_SHARP as usize,
INTERP_FILTER_NONE,
0,
false,
)
.unwrap();
assert_eq!(expected_ctx, 2);
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let sym = dec
.read_symbol(dec_cdfs.interp_filter_cdf(expected_ctx).unwrap())
.unwrap();
assert_eq!(sym as u8, crate::cdf::EIGHTTAP_SMOOTH);
assert_eq!(enc_cdfs.interp_filter, dec_cdfs.interp_filter);
let mut enc_cdfs2 = TileCdfContext::new_from_defaults();
let mut writer2 = SymbolWriter::new(false);
write_interpolation_filter(
&mut writer2,
&mut enc_cdfs2,
&target,
BLOCK_8X8,
0,
false,
ref_frame,
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
SWITCHABLE,
false,
[0; 8],
true,
false,
[4, -1], [-1, -1],
[crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP],
[EIGHTTAP, EIGHTTAP],
)
.unwrap();
let _ = writer2.finish();
assert_ne!(
enc_cdfs2.interp_filter[2], enc_cdfs.interp_filter[2],
"ctx-2 row adapted only on the matching-neighbour encode"
);
let sentinel_ctx =
interp_filter_ctx(INTERP_FILTER_NONE, INTERP_FILTER_NONE, 0, false).unwrap();
assert_eq!(sentinel_ctx, 3);
assert_ne!(
enc_cdfs2.interp_filter[3],
TileCdfContext::new_from_defaults().interp_filter[3],
"ctx-3 row adapted on the mismatched-neighbour encode"
);
}
#[test]
fn write_interpolation_filter_rejects_inconsistent_readouts() {
let run = |readout: InterpolationFilterReadout,
skip_mode: u8,
interpolation_filter: u8,
enable_dual_filter: bool|
-> Result<(), Error> {
let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
write_interpolation_filter(
&mut writer,
&mut cdfs,
&readout,
BLOCK_8X8,
skip_mode,
false,
[1, -1],
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
interpolation_filter,
enable_dual_filter,
[0; 8],
false,
false,
[-1, -1],
[-1, -1],
[EIGHTTAP, EIGHTTAP],
[EIGHTTAP, EIGHTTAP],
)
};
let forced = |f: u8| InterpolationFilterReadout {
interp_filter: [f, f],
read_from_bitstream: [false, false],
};
assert!(run(forced(EIGHTTAP), 0, crate::cdf::EIGHTTAP_SHARP, false).is_err());
assert!(run(
InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [true, false],
},
0,
EIGHTTAP,
false,
)
.is_err());
assert!(run(forced(crate::cdf::EIGHTTAP_SMOOTH), 1, SWITCHABLE, false).is_err());
assert!(run(
InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [true, false],
},
1,
SWITCHABLE,
false,
)
.is_err());
assert!(run(forced(crate::cdf::EIGHTTAP_SMOOTH), 0, SWITCHABLE, false).is_err());
assert!(run(
InterpolationFilterReadout {
interp_filter: [BILINEAR, BILINEAR],
read_from_bitstream: [true, false],
},
0,
SWITCHABLE,
false,
)
.is_err());
assert!(run(
InterpolationFilterReadout {
interp_filter: [EIGHTTAP, crate::cdf::EIGHTTAP_SMOOTH],
read_from_bitstream: [true, false],
},
0,
SWITCHABLE,
false,
)
.is_err());
assert!(run(
InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [true, true],
},
0,
SWITCHABLE,
false,
)
.is_err());
assert!(run(forced(EIGHTTAP), 0, SWITCHABLE + 1, false).is_err());
}
const RT_REF: [i32; 2] = [1, -1];
fn rt_tail_a() -> InterBlockModeInfoTail {
InterBlockModeInfoTail {
interpolation_filter: SWITCHABLE,
interp_filter: InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [false, false],
},
..InterBlockModeInfoTail::bit_silent()
}
}
fn rt_tail_b(has_overlappable: bool) -> InterBlockModeInfoTail {
InterBlockModeInfoTail {
interintra: InterIntraReadout {
interintra: 1,
interintra_mode: Some(crate::cdf::II_SMOOTH_PRED),
wedge_interintra: Some(1),
wedge_index: Some(5),
},
enable_interintra_compound: true,
is_motion_mode_switchable: true,
allow_warped_motion: true,
has_overlappable,
compound_type: crate::cdf::CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: COMPOUND_WEDGE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
},
interpolation_filter: SWITCHABLE,
interp_filter: InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP],
read_from_bitstream: [true, false],
},
above_interp_filters: [EIGHTTAP, EIGHTTAP],
..InterBlockModeInfoTail::bit_silent()
}
}
fn rt_tail_c(has_overlappable: bool, num_samples: u32) -> InterBlockModeInfoTail {
InterBlockModeInfoTail {
enable_interintra_compound: true,
motion_mode: MOTION_MODE_OBMC,
num_samples,
is_motion_mode_switchable: true,
allow_warped_motion: false,
has_overlappable,
interpolation_filter: SWITCHABLE,
interp_filter: InterpolationFilterReadout {
interp_filter: [crate::cdf::EIGHTTAP_SMOOTH, crate::cdf::EIGHTTAP_SMOOTH],
read_from_bitstream: [true, false],
},
above_interp_filters: [crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP],
..InterBlockModeInfoTail::bit_silent()
}
}
fn rt_decode_blocks(
bytes: &[u8],
n_blocks: usize,
) -> (PartitionWalker, Vec<crate::cdf::DecodedInterBlockModeInfo>) {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(bytes, bytes.len(), false).unwrap();
let mfmvs = crate::cdf::MotionFieldMvs::new_invalid(8, 8);
let mut infos = Vec::new();
for blk in 0..n_blocks {
let mi_row = (blk as u32) * 2;
let avail_u = blk > 0;
let above_ref_frame = if blk == 2 { [1, 0] } else { RT_REF };
let (eic, imms, awm) = match blk {
0 => (false, false, false),
1 => (true, true, true),
_ => (true, true, false),
};
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
mi_row,
0,
BLOCK_8X8,
0,
avail_u,
false,
above_ref_frame,
[-1, -1],
false,
false,
true,
true,
[1, -1],
false,
0,
false,
false,
false,
[0; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
imms,
awm,
[false; 7],
eic,
false,
false,
crate::cdf::FrameInterOrderHints::IDENTITY,
SWITCHABLE,
false,
&mfmvs,
)
.unwrap();
infos.push(info);
}
(walker, infos)
}
fn rt_encode_blocks(n_blocks: usize) -> Vec<u8> {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mfmvs = crate::cdf::MotionFieldMvs::new_invalid(8, 8);
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
for blk in 0..n_blocks {
let walker = if blk == 0 {
PartitionWalker::new(8, 8, geom).unwrap()
} else {
rt_decode_blocks(&rt_encode_blocks(blk), blk).0
};
let mi_row = (blk as u32) * 2;
let stack = walker
.find_mv_stack(
mi_row,
0,
BLOCK_8X8,
RT_REF,
false,
false,
[0; 8],
identity_gm_params(),
[0; 8],
false,
false,
&mfmvs,
)
.unwrap();
let y_mode = if blk == 0 {
MODE_GLOBALMV
} else {
MODE_NEARESTMV
};
let mv0 = if blk == 0 {
stack.global_mvs[0]
} else {
assert!(stack.num_mv_found > 0, "above stamps must feed the stack");
assign_mv_pred_mv(&stack, MODE_NEARESTMV, 0, 0).unwrap()
};
let has_ov = walker.has_overlappable_candidates(mi_row, 0, BLOCK_8X8);
let tail = match blk {
0 => rt_tail_a(),
1 => {
assert!(has_ov, "block A above must be overlappable");
rt_tail_b(has_ov)
}
_ => {
assert!(has_ov, "block B above must be overlappable");
let ns =
walker.find_warp_samples(mi_row, 0, BLOCK_8X8, RT_REF[0], [mv0, [0, 0]]);
rt_tail_c(has_ov, ns)
}
};
let above_ref_frame = if blk == 2 { [1, 0] } else { RT_REF };
write_inter_block_mode_info(
&mut writer,
&mut cdfs,
RT_REF,
y_mode,
[mv0, [0, 0]],
0,
&stack,
BLOCK_8X8,
0,
[1, -1],
false,
0,
false,
false,
false,
blk > 0,
false,
true,
true,
false,
false,
above_ref_frame,
[-1, -1],
false,
false,
&tail,
)
.unwrap();
}
writer.finish()
}
#[test]
fn write_inter_block_mode_info_tail_round_trips_through_dispatcher() {
let bytes = rt_encode_blocks(3);
let (walker, infos) = rt_decode_blocks(&bytes, 3);
assert_eq!(infos.len(), 3);
assert_eq!(infos[0].ref_frame, RT_REF);
assert_eq!(infos[0].y_mode, MODE_GLOBALMV);
assert_eq!(infos[0].interintra.interintra, 0);
assert_eq!(infos[0].motion_mode, MOTION_MODE_SIMPLE);
assert_eq!(infos[0].compound_type.compound_type, COMPOUND_AVERAGE);
assert_eq!(infos[0].interp_filter.interp_filter, [EIGHTTAP, EIGHTTAP]);
assert_eq!(infos[0].interp_filter.read_from_bitstream, [false, false]);
assert_eq!(
infos[1].ref_frame,
[1, 0],
"§5.11.28 RefFrame[1] = INTRA_FRAME override surfaces"
);
assert_eq!(infos[1].interintra.interintra, 1);
assert_eq!(
infos[1].interintra.interintra_mode,
Some(crate::cdf::II_SMOOTH_PRED)
);
assert_eq!(infos[1].interintra.wedge_interintra, Some(1));
assert_eq!(infos[1].interintra.wedge_index, Some(5));
assert_eq!(
infos[1].motion_mode, MOTION_MODE_SIMPLE,
"slot-1 INTRA_FRAME silences §5.11.27 despite overlappable neighbours"
);
assert_eq!(infos[1].compound_type.compound_type, COMPOUND_WEDGE);
assert_eq!(
infos[1].interp_filter.interp_filter,
[crate::cdf::EIGHTTAP_SHARP, crate::cdf::EIGHTTAP_SHARP]
);
assert_eq!(infos[2].ref_frame, RT_REF);
assert_eq!(infos[2].interintra.interintra, 0);
assert_eq!(infos[2].motion_mode, MOTION_MODE_OBMC);
assert_eq!(infos[2].compound_type.compound_type, COMPOUND_AVERAGE);
assert_eq!(
infos[2].interp_filter.interp_filter,
[crate::cdf::EIGHTTAP_SMOOTH, crate::cdf::EIGHTTAP_SMOOTH]
);
let cell = (2 * 8) as usize; assert_eq!(walker.ref_frames()[cell * 2 + 1], 0);
}
#[test]
fn write_inter_block_mode_info_tail_rejects_motion_on_interintra() {
let stack = mk_mv_stack(2);
let pred = assign_mv_pred_mv(&stack, MODE_NEARESTMV, 0, 0).unwrap();
let (au, al, asg, lsg, ai, li, arf, lrf) = empty_neighbour_inputs();
let mut tail = rt_tail_b(true);
tail.motion_mode = MOTION_MODE_OBMC; let mut cdfs = TileCdfContext::new_from_defaults();
let mut writer = SymbolWriter::new(false);
let err = write_inter_block_mode_info(
&mut writer,
&mut cdfs,
[1, -1],
MODE_NEARESTMV,
[pred, [0, 0]],
0,
&stack,
BLOCK_8X8,
0,
[0, 0],
false,
0,
false,
false,
false,
au,
al,
asg,
lsg,
ai,
li,
arf,
lrf,
false,
true,
&tail,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn r282_write_palette_tokens_plane_round_trip_with_offscreen_border() {
use crate::cdf::palette_tokens_plane;
use crate::symbol_decoder::SymbolDecoder;
let (block_w, block_h) = (8usize, 8usize);
let (onscreen_w, onscreen_h) = (6usize, 5usize);
let palette_size = 3usize;
let mut target = vec![0u8; block_w * block_h];
for r in 0..onscreen_h {
for c in 0..onscreen_w {
target[r * block_w + c] = ((r + c) % palette_size) as u8;
}
}
for plane in [PalettePlane::Y, PalettePlane::Uv] {
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
write_palette_tokens_plane(
&mut writer,
&mut enc_cdfs,
plane,
palette_size,
block_w,
block_h,
onscreen_w,
onscreen_h,
&target,
block_w,
)
.unwrap();
writer.write_literal(8, 0x3C).unwrap();
let bytes = writer.finish();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let color_index_map = dec.read_ns(palette_size as u32).unwrap() as u8;
assert_eq!(color_index_map, target[0], "NS top-left sample");
let mut color_map = vec![0u8; block_w * block_h];
palette_tokens_plane(
&mut dec,
&mut dec_cdfs,
plane,
palette_size,
block_w,
block_h,
onscreen_w,
onscreen_h,
color_index_map,
&mut color_map,
block_w,
)
.unwrap();
for r in 0..onscreen_h {
for c in 0..onscreen_w {
assert_eq!(
color_map[r * block_w + c],
target[r * block_w + c],
"on-screen ColorMap sample ({r}, {c})"
);
}
}
for r in 0..onscreen_h {
for c in onscreen_w..block_w {
assert_eq!(
color_map[r * block_w + c],
color_map[r * block_w + onscreen_w - 1],
"horizontal border-fill ({r}, {c})"
);
}
}
for r in onscreen_h..block_h {
for c in 0..block_w {
assert_eq!(
color_map[r * block_w + c],
color_map[(onscreen_h - 1) * block_w + c],
"vertical border-fill ({r}, {c})"
);
}
}
assert_eq!(enc_cdfs, dec_cdfs, "CDF adaptation lockstep");
assert_eq!(dec.read_literal(8).unwrap(), 0x3C, "sync sentinel");
}
}
#[test]
fn r282_write_palette_tokens_plane_rejects_caller_bugs() {
let mut writer = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let map = vec![0u8; 64];
for bad_size in [0usize, 1, PALETTE_COLORS + 1] {
let err = write_palette_tokens_plane(
&mut writer,
&mut cdfs,
PalettePlane::Y,
bad_size,
8,
8,
8,
8,
&map,
8,
)
.unwrap_err();
assert!(matches!(err, Error::InvalidPaletteWalkArgs));
}
let mut bad_map = map.clone();
bad_map[10] = 5;
let err = write_palette_tokens_plane(
&mut writer,
&mut cdfs,
PalettePlane::Y,
2,
8,
8,
8,
8,
&bad_map,
8,
)
.unwrap_err();
assert!(matches!(err, Error::InvalidPaletteWalkArgs));
let err = write_palette_tokens_plane(
&mut writer,
&mut cdfs,
PalettePlane::Y,
2,
8,
8,
8,
8,
&map[..32],
8,
)
.unwrap_err();
assert!(matches!(err, Error::InvalidPaletteWalkArgs));
for (ow, oh) in [(0usize, 8usize), (8, 0), (9, 8), (8, 9)] {
let err = write_palette_tokens_plane(
&mut writer,
&mut cdfs,
PalettePlane::Y,
2,
8,
8,
ow,
oh,
&map,
8,
)
.unwrap_err();
assert!(matches!(err, Error::InvalidPaletteWalkArgs));
}
}
}