use crate::cdf::{
TileCdfContext, BLOCK_4X4, BLOCK_SIZES, MAX_TX_DEPTH, MAX_TX_DEPTH_TABLE, MAX_TX_SIZE_RECT,
MAX_VARTX_DEPTH, MI_SIZE, SPLIT_TX_SIZE, TXFM_PARTITION_CONTEXTS, TX_4X4, TX_HEIGHT,
TX_SIZES_ALL, TX_SIZE_CONTEXTS, TX_WIDTH,
};
use crate::encoder::symbol_writer::SymbolWriter;
use crate::Error;
#[allow(clippy::too_many_arguments)]
pub fn write_block_tx_size(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
tx_size: usize,
sub_size: usize,
lossless: bool,
allow_select: bool,
tx_mode_select: bool,
ctx: usize,
) -> Result<(), Error> {
if sub_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if tx_size >= TX_SIZES_ALL {
return Err(Error::PartitionWalkOutOfRange);
}
if ctx >= TX_SIZE_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
if lossless {
if tx_size != TX_4X4 {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let max_rect_tx_size = MAX_TX_SIZE_RECT[sub_size];
let max_tx_depth = MAX_TX_DEPTH_TABLE[sub_size];
if !(sub_size > BLOCK_4X4 && allow_select && tx_mode_select) {
if tx_size != max_rect_tx_size {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let mut depth: u32 = 0;
let mut walker = max_rect_tx_size;
while walker != tx_size {
if depth as usize >= MAX_TX_DEPTH {
return Err(Error::PartitionWalkOutOfRange);
}
let next = SPLIT_TX_SIZE[walker];
if next == walker {
return Err(Error::PartitionWalkOutOfRange);
}
walker = next;
depth += 1;
}
let cdf = cdfs
.tx_depth_cdf(max_tx_depth, ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(depth, cdf)
}
pub fn write_var_tx_size(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
root: &VarTxNode,
) -> Result<(), Error> {
write_var_tx_size_recursive(writer, cdfs, root, 0)
}
fn write_var_tx_size_recursive(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
node: &VarTxNode,
depth: u32,
) -> Result<(), Error> {
if node.tx_size >= TX_SIZES_ALL {
return Err(Error::PartitionWalkOutOfRange);
}
if depth > MAX_VARTX_DEPTH {
return Err(Error::PartitionWalkOutOfRange);
}
let terminal = node.tx_size == TX_4X4 || depth == MAX_VARTX_DEPTH;
match &node.kind {
VarTxNodeKind::Leaf => {
if !terminal {
if node.ctx >= TXFM_PARTITION_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.txfm_split_cdf(node.ctx);
writer.write_symbol(0, cdf)?;
}
Ok(())
}
VarTxNodeKind::Split(children) => {
if terminal {
return Err(Error::PartitionWalkOutOfRange);
}
if node.ctx >= TXFM_PARTITION_CONTEXTS {
return Err(Error::PartitionWalkOutOfRange);
}
let cdf = cdfs.txfm_split_cdf(node.ctx);
writer.write_symbol(1, cdf)?;
let sub_tx_sz = SPLIT_TX_SIZE[node.tx_size];
let w4 = TX_WIDTH[node.tx_size] / MI_SIZE;
let h4 = TX_HEIGHT[node.tx_size] / MI_SIZE;
let step_w = TX_WIDTH[sub_tx_sz] / MI_SIZE;
let step_h = TX_HEIGHT[sub_tx_sz] / MI_SIZE;
if step_w == 0 || step_h == 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let expected_children = (h4 / step_h) * (w4 / step_w);
if children.len() != expected_children {
return Err(Error::PartitionWalkOutOfRange);
}
for child in children {
if child.tx_size != sub_tx_sz {
return Err(Error::PartitionWalkOutOfRange);
}
write_var_tx_size_recursive(writer, cdfs, child, depth + 1)?;
}
Ok(())
}
}
}
#[derive(Debug, Clone)]
pub struct VarTxNode {
pub tx_size: usize,
pub ctx: usize,
pub kind: VarTxNodeKind,
}
#[derive(Debug, Clone)]
pub enum VarTxNodeKind {
Leaf,
Split(Vec<VarTxNode>),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cdf::{BLOCK_32X32, BLOCK_64X64, BLOCK_8X8, TX_16X16, TX_32X32, TX_64X64, TX_8X8};
use crate::symbol_decoder::SymbolDecoder;
fn roundtrip_tx_depth(
tx_size: usize,
sub_size: usize,
lossless: bool,
allow_select: bool,
tx_mode_select: bool,
ctx: usize,
) -> u32 {
let mut w = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
write_block_tx_size(
&mut w,
&mut enc_cdfs,
tx_size,
sub_size,
lossless,
allow_select,
tx_mode_select,
ctx,
)
.unwrap();
let bytes = w.finish();
let bytes = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut d = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
if !lossless && sub_size > BLOCK_4X4 && allow_select && tx_mode_select {
let max_tx_depth = MAX_TX_DEPTH_TABLE[sub_size];
let cdf = dec_cdfs.tx_depth_cdf(max_tx_depth, ctx).unwrap();
d.read_symbol(cdf).unwrap()
} else {
0
}
}
#[test]
fn write_block_tx_size_emits_zero_for_max_rect() {
let depth = roundtrip_tx_depth(TX_8X8, BLOCK_8X8, false, true, true, 0);
assert_eq!(depth, 0);
}
#[test]
fn write_block_tx_size_emits_one_for_one_split() {
let depth = roundtrip_tx_depth(TX_4X4, BLOCK_8X8, false, true, true, 1);
assert_eq!(depth, 1);
}
#[test]
fn write_block_tx_size_emits_two_for_two_splits() {
let depth = roundtrip_tx_depth(TX_8X8, BLOCK_32X32, false, true, true, 2);
assert_eq!(depth, 2);
}
#[test]
fn write_block_tx_size_lossless_emits_no_symbol() {
let depth = roundtrip_tx_depth(TX_4X4, BLOCK_32X32, true, true, true, 0);
assert_eq!(depth, 0);
}
#[test]
fn write_block_tx_size_no_select_emits_no_symbol() {
let depth = roundtrip_tx_depth(TX_64X64, BLOCK_64X64, false, true, false, 0);
assert_eq!(depth, 0);
}
#[test]
fn write_block_tx_size_lossless_rejects_non_4x4() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_block_tx_size(&mut w, &mut cdfs, TX_8X8, BLOCK_32X32, true, true, true, 0)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_block_tx_size_no_select_rejects_non_max() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_block_tx_size(
&mut w,
&mut cdfs,
TX_8X8,
BLOCK_32X32,
false,
true,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_block_tx_size_rejects_unreachable() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_block_tx_size(&mut w, &mut cdfs, TX_64X64, BLOCK_8X8, false, true, true, 0)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_block_tx_size_rejects_out_of_range_sub_size() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_block_tx_size(&mut w, &mut cdfs, TX_4X4, BLOCK_SIZES, false, true, true, 0)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_block_tx_size_rejects_out_of_range_tx_size() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_block_tx_size(
&mut w,
&mut cdfs,
TX_SIZES_ALL,
BLOCK_8X8,
false,
true,
true,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_block_tx_size_rejects_out_of_range_ctx() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_block_tx_size(
&mut w,
&mut cdfs,
TX_8X8,
BLOCK_8X8,
false,
true,
true,
TX_SIZE_CONTEXTS,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn roundtrip_var_tx(root: &VarTxNode) -> Vec<u32> {
let mut w = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
write_var_tx_size(&mut w, &mut enc_cdfs, root).unwrap();
let bytes = w.finish();
let bytes = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut d = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut splits = Vec::new();
decode_var_tx_replay(&mut d, &mut dec_cdfs, root, 0, &mut splits);
splits
}
fn decode_var_tx_replay(
d: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
node: &VarTxNode,
depth: u32,
out: &mut Vec<u32>,
) {
let terminal = node.tx_size == TX_4X4 || depth == MAX_VARTX_DEPTH;
match &node.kind {
VarTxNodeKind::Leaf => {
if !terminal {
let cdf = cdfs.txfm_split_cdf(node.ctx);
let s = d.read_symbol(cdf).unwrap();
out.push(s);
assert_eq!(s, 0, "leaf must round-trip txfm_split = 0");
}
}
VarTxNodeKind::Split(children) => {
let cdf = cdfs.txfm_split_cdf(node.ctx);
let s = d.read_symbol(cdf).unwrap();
out.push(s);
assert_eq!(s, 1, "split must round-trip txfm_split = 1");
for child in children {
decode_var_tx_replay(d, cdfs, child, depth + 1, out);
}
}
}
}
#[test]
fn var_tx_size_leaf_emits_zero() {
let root = VarTxNode {
tx_size: TX_16X16,
ctx: 3,
kind: VarTxNodeKind::Leaf,
};
let splits = roundtrip_var_tx(&root);
assert_eq!(splits, vec![0]);
}
#[test]
fn var_tx_size_terminal_4x4_emits_no_symbol() {
let root = VarTxNode {
tx_size: TX_4X4,
ctx: 0,
kind: VarTxNodeKind::Leaf,
};
let splits = roundtrip_var_tx(&root);
assert!(splits.is_empty());
}
#[test]
fn var_tx_size_split_then_leaves() {
let leaf_child = |ctx| VarTxNode {
tx_size: TX_8X8,
ctx,
kind: VarTxNodeKind::Leaf,
};
let root = VarTxNode {
tx_size: TX_16X16,
ctx: 5,
kind: VarTxNodeKind::Split(vec![
leaf_child(0),
leaf_child(1),
leaf_child(2),
leaf_child(3),
]),
};
let splits = roundtrip_var_tx(&root);
assert_eq!(splits, vec![1, 0, 0, 0, 0]);
}
#[test]
fn var_tx_size_two_level_split() {
let leaf_8x8 = |ctx| VarTxNode {
tx_size: TX_8X8,
ctx,
kind: VarTxNodeKind::Leaf,
};
let inner_16x16 = |ctx| VarTxNode {
tx_size: TX_16X16,
ctx,
kind: VarTxNodeKind::Split(vec![leaf_8x8(0), leaf_8x8(0), leaf_8x8(0), leaf_8x8(0)]),
};
let root = VarTxNode {
tx_size: TX_32X32,
ctx: 7,
kind: VarTxNodeKind::Split(vec![
inner_16x16(1),
inner_16x16(2),
inner_16x16(3),
inner_16x16(4),
]),
};
let splits = roundtrip_var_tx(&root);
assert_eq!(splits, vec![1, 1, 1, 1, 1]);
}
#[test]
fn var_tx_size_split_at_4x4_terminal_rejected() {
let root = VarTxNode {
tx_size: TX_4X4,
ctx: 0,
kind: VarTxNodeKind::Split(vec![VarTxNode {
tx_size: TX_4X4,
ctx: 0,
kind: VarTxNodeKind::Leaf,
}]),
};
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_var_tx_size(&mut w, &mut cdfs, &root).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn var_tx_size_split_at_max_depth_rejected() {
let illegal_inner = VarTxNode {
tx_size: TX_16X16,
ctx: 0,
kind: VarTxNodeKind::Split(vec![
VarTxNode {
tx_size: TX_8X8,
ctx: 0,
kind: VarTxNodeKind::Leaf,
};
4
]),
};
let mid_32 = VarTxNode {
tx_size: TX_32X32,
ctx: 0,
kind: VarTxNodeKind::Split(vec![illegal_inner; 4]),
};
let root = VarTxNode {
tx_size: TX_64X64,
ctx: 0,
kind: VarTxNodeKind::Split(vec![mid_32; 4]),
};
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_var_tx_size(&mut w, &mut cdfs, &root).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn var_tx_size_wrong_child_count_rejected() {
let root = VarTxNode {
tx_size: TX_16X16,
ctx: 0,
kind: VarTxNodeKind::Split(vec![
VarTxNode {
tx_size: TX_8X8,
ctx: 0,
kind: VarTxNodeKind::Leaf,
};
3
]),
};
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_var_tx_size(&mut w, &mut cdfs, &root).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn var_tx_size_wrong_child_size_rejected() {
let root = VarTxNode {
tx_size: TX_16X16,
ctx: 0,
kind: VarTxNodeKind::Split(vec![
VarTxNode {
tx_size: TX_4X4,
ctx: 0,
kind: VarTxNodeKind::Leaf,
};
4
]),
};
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_var_tx_size(&mut w, &mut cdfs, &root).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn var_tx_size_rejects_out_of_range_tx_size() {
let root = VarTxNode {
tx_size: TX_SIZES_ALL,
ctx: 0,
kind: VarTxNodeKind::Leaf,
};
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_var_tx_size(&mut w, &mut cdfs, &root).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn var_tx_size_rejects_out_of_range_ctx() {
let root = VarTxNode {
tx_size: TX_16X16,
ctx: TXFM_PARTITION_CONTEXTS,
kind: VarTxNodeKind::Leaf,
};
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_var_tx_size(&mut w, &mut cdfs, &root).unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
}