use crate::cdf::{
split_or_horz_cdf, split_or_vert_cdf, TileCdfContext, BLOCK_8X8, BLOCK_SIZES, MI_WIDTH_LOG2,
PARTITION_HORZ, PARTITION_NONE, PARTITION_SPLIT, PARTITION_VERT,
};
use crate::encoder::symbol_writer::SymbolWriter;
use crate::Error;
#[inline]
#[must_use]
pub const fn partition_none_only(b_size: usize) -> bool {
b_size < BLOCK_8X8
}
#[inline]
#[must_use]
pub const fn partition_split_only(b_size: usize, has_rows: bool, has_cols: bool) -> bool {
!partition_none_only(b_size) && !has_rows && !has_cols
}
pub fn write_partition(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
partition: usize,
b_size: usize,
has_rows: bool,
has_cols: bool,
ctx: usize,
) -> Result<(), Error> {
if b_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if partition_none_only(b_size) {
if partition != PARTITION_NONE {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
if partition_split_only(b_size, has_rows, has_cols) {
if partition != PARTITION_SPLIT {
return Err(Error::PartitionWalkOutOfRange);
}
return Ok(());
}
let bsl = MI_WIDTH_LOG2[b_size] as u32;
if has_rows && has_cols {
let cdf = cdfs
.partition_cdf(bsl, ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
let n = (cdf.len() as u32).saturating_sub(1);
if partition as u32 >= n {
return Err(Error::PartitionWalkOutOfRange);
}
writer.write_symbol(partition as u32, cdf)
} else if has_cols {
let cdf_row = cdfs
.partition_cdf(bsl, ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
let mut bin = split_or_horz_cdf(cdf_row, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
let sym: u32 = match partition {
PARTITION_HORZ => 0,
PARTITION_SPLIT => 1,
_ => return Err(Error::PartitionWalkOutOfRange),
};
writer.write_symbol(sym, &mut bin)
} else {
let cdf_row = cdfs
.partition_cdf(bsl, ctx)
.ok_or(Error::PartitionWalkOutOfRange)?;
let mut bin = split_or_vert_cdf(cdf_row, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
let sym: u32 = match partition {
PARTITION_VERT => 0,
PARTITION_SPLIT => 1,
_ => return Err(Error::PartitionWalkOutOfRange),
};
writer.write_symbol(sym, &mut bin)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cdf::{
partition_ctx, BLOCK_128X128, BLOCK_16X16, BLOCK_4X4, BLOCK_64X64, PARTITION_CONTEXTS,
PARTITION_HORZ_4, PARTITION_HORZ_A, PARTITION_HORZ_B, PARTITION_VERT_4, PARTITION_VERT_A,
PARTITION_VERT_B,
};
use crate::symbol_decoder::SymbolDecoder;
fn roundtrip(
partition: usize,
b_size: usize,
has_rows: bool,
has_cols: bool,
ctx: usize,
) -> usize {
let mut w = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
write_partition(
&mut w,
&mut enc_cdfs,
partition,
b_size,
has_rows,
has_cols,
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();
decode_partition_local(&mut d, &mut dec_cdfs, b_size, has_rows, has_cols, ctx)
}
fn decode_partition_local(
d: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
b_size: usize,
has_rows: bool,
has_cols: bool,
ctx: usize,
) -> usize {
if b_size < BLOCK_8X8 {
return PARTITION_NONE;
}
let bsl = MI_WIDTH_LOG2[b_size] as u32;
if has_rows && has_cols {
let cdf = cdfs.partition_cdf(bsl, ctx).unwrap();
d.read_symbol(cdf).unwrap() as usize
} else if has_cols {
let cdf_row = cdfs.partition_cdf(bsl, ctx).unwrap();
let mut bin = split_or_horz_cdf(cdf_row, b_size).unwrap();
let s = d.read_symbol(&mut bin).unwrap();
if s == 0 {
PARTITION_HORZ
} else {
PARTITION_SPLIT
}
} else if has_rows {
let cdf_row = cdfs.partition_cdf(bsl, ctx).unwrap();
let mut bin = split_or_vert_cdf(cdf_row, b_size).unwrap();
let s = d.read_symbol(&mut bin).unwrap();
if s == 0 {
PARTITION_VERT
} else {
PARTITION_SPLIT
}
} else {
PARTITION_SPLIT
}
}
#[test]
fn partition_none_only_matches_spec() {
assert!(partition_none_only(BLOCK_4X4));
assert!(!partition_none_only(BLOCK_8X8));
assert!(!partition_none_only(BLOCK_16X16));
assert!(!partition_none_only(BLOCK_64X64));
}
#[test]
fn partition_split_only_matches_spec() {
assert!(!partition_split_only(BLOCK_4X4, false, false));
assert!(partition_split_only(BLOCK_16X16, false, false));
assert!(!partition_split_only(BLOCK_16X16, true, false));
assert!(!partition_split_only(BLOCK_16X16, false, true));
assert!(!partition_split_only(BLOCK_16X16, true, true));
}
#[test]
fn write_partition_none_short_circuit_no_symbol() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
write_partition(&mut w, &mut cdfs, PARTITION_NONE, BLOCK_4X4, true, true, 0).unwrap();
let p = roundtrip(PARTITION_NONE, BLOCK_4X4, true, true, 0);
assert_eq!(p, PARTITION_NONE);
}
#[test]
fn write_partition_none_short_circuit_rejects_other_partition() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_partition(&mut w, &mut cdfs, PARTITION_SPLIT, BLOCK_4X4, true, true, 0)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_partition_corner_forced_split_no_symbol() {
let p = roundtrip(PARTITION_SPLIT, BLOCK_16X16, false, false, 0);
assert_eq!(p, PARTITION_SPLIT);
}
#[test]
fn write_partition_corner_forced_split_rejects_other_partition() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_partition(
&mut w,
&mut cdfs,
PARTITION_HORZ,
BLOCK_16X16,
false,
false,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_partition_full_alphabet_round_trip_w16() {
for partition in [
PARTITION_NONE,
PARTITION_HORZ,
PARTITION_VERT,
PARTITION_SPLIT,
PARTITION_HORZ_A,
PARTITION_HORZ_B,
PARTITION_VERT_A,
PARTITION_VERT_B,
PARTITION_HORZ_4,
PARTITION_VERT_4,
] {
for ctx in 0..PARTITION_CONTEXTS {
let p = roundtrip(partition, BLOCK_16X16, true, true, ctx);
assert_eq!(
p, partition,
"partition {partition} ctx {ctx} did not round-trip"
);
}
}
}
#[test]
fn write_partition_w128_rejects_horz4_vert4() {
for partition in [PARTITION_HORZ_4, PARTITION_VERT_4] {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_partition(&mut w, &mut cdfs, partition, BLOCK_128X128, true, true, 0)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"partition {partition} on W128 must reject"
);
}
}
#[test]
fn write_partition_w128_alphabet_round_trip() {
for partition in [
PARTITION_NONE,
PARTITION_HORZ,
PARTITION_VERT,
PARTITION_SPLIT,
PARTITION_HORZ_A,
PARTITION_HORZ_B,
PARTITION_VERT_A,
PARTITION_VERT_B,
] {
let p = roundtrip(
partition,
BLOCK_128X128,
true,
true,
partition_ctx(true, false),
);
assert_eq!(p, partition);
}
}
#[test]
fn write_partition_split_or_horz_round_trip() {
for partition in [PARTITION_HORZ, PARTITION_SPLIT] {
for ctx in 0..PARTITION_CONTEXTS {
let p = roundtrip(partition, BLOCK_16X16, false, true, ctx);
assert_eq!(
p, partition,
"split_or_horz partition {partition} ctx {ctx} did not round-trip"
);
}
}
}
#[test]
fn write_partition_split_or_horz_rejects_other_partitions() {
for partition in [
PARTITION_NONE,
PARTITION_VERT,
PARTITION_HORZ_A,
PARTITION_HORZ_B,
PARTITION_VERT_A,
PARTITION_VERT_B,
PARTITION_HORZ_4,
PARTITION_VERT_4,
] {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_partition(&mut w, &mut cdfs, partition, BLOCK_16X16, false, true, 0)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"split_or_horz must reject partition {partition}"
);
}
}
#[test]
fn write_partition_split_or_vert_round_trip() {
for partition in [PARTITION_VERT, PARTITION_SPLIT] {
for ctx in 0..PARTITION_CONTEXTS {
let p = roundtrip(partition, BLOCK_16X16, true, false, ctx);
assert_eq!(
p, partition,
"split_or_vert partition {partition} ctx {ctx} did not round-trip"
);
}
}
}
#[test]
fn write_partition_split_or_vert_rejects_other_partitions() {
for partition in [
PARTITION_NONE,
PARTITION_HORZ,
PARTITION_HORZ_A,
PARTITION_HORZ_B,
PARTITION_VERT_A,
PARTITION_VERT_B,
PARTITION_HORZ_4,
PARTITION_VERT_4,
] {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_partition(&mut w, &mut cdfs, partition, BLOCK_16X16, true, false, 0)
.unwrap_err();
assert!(
matches!(err, Error::PartitionWalkOutOfRange),
"split_or_vert must reject partition {partition}"
);
}
}
#[test]
fn write_partition_rejects_out_of_range_b_size() {
let mut w = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let err = write_partition(
&mut w,
&mut cdfs,
PARTITION_NONE,
BLOCK_SIZES,
true,
true,
0,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn write_partition_sequence_round_trip_with_cdf_adaptation() {
let plan: &[(usize, usize, bool, bool, usize)] = &[
(PARTITION_NONE, BLOCK_16X16, true, true, 0),
(PARTITION_HORZ, BLOCK_16X16, true, true, 1),
(PARTITION_VERT, BLOCK_16X16, true, true, 2),
(PARTITION_SPLIT, BLOCK_16X16, true, true, 3),
(PARTITION_HORZ_A, BLOCK_16X16, true, true, 0),
(PARTITION_HORZ_B, BLOCK_16X16, true, true, 1),
(PARTITION_VERT_A, BLOCK_16X16, true, true, 2),
(PARTITION_VERT_B, BLOCK_16X16, true, true, 3),
(PARTITION_HORZ_4, BLOCK_16X16, true, true, 0),
(PARTITION_VERT_4, BLOCK_16X16, true, true, 1),
(PARTITION_HORZ, BLOCK_16X16, false, true, 2), (PARTITION_VERT, BLOCK_16X16, true, false, 3), (PARTITION_SPLIT, BLOCK_16X16, false, false, 0), (PARTITION_NONE, BLOCK_4X4, true, true, 0), ];
let mut w = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
for &(partition, b_size, has_rows, has_cols, ctx) in plan {
write_partition(
&mut w,
&mut enc_cdfs,
partition,
b_size,
has_rows,
has_cols,
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();
for (i, &(partition, b_size, has_rows, has_cols, ctx)) in plan.iter().enumerate() {
let got =
decode_partition_local(&mut d, &mut dec_cdfs, b_size, has_rows, has_cols, ctx);
assert_eq!(
got, partition,
"sequence position {i}: expected partition {partition}, decoded {got}"
);
}
assert_eq!(enc_cdfs.partition_w16, dec_cdfs.partition_w16);
}
}