use super::cdf;
use super::coder::{Site, TileCoder};
use super::symbol::SymbolDecoder;
use super::transform::TxSize;
use otf_pixels_core::{PixelsError, Result};
pub const BLOCK_4X4: usize = 0;
const MAX_TX_SIZE_RECT: [TxSize; 22] = [
TxSize::Tx4x4,
TxSize::Tx4x8,
TxSize::Tx8x4,
TxSize::Tx8x8,
TxSize::Tx8x16,
TxSize::Tx16x8,
TxSize::Tx16x16,
TxSize::Tx16x32,
TxSize::Tx32x16,
TxSize::Tx32x32,
TxSize::Tx32x64,
TxSize::Tx64x32,
TxSize::Tx64x64,
TxSize::Tx64x64,
TxSize::Tx64x64,
TxSize::Tx64x64,
TxSize::Tx4x16,
TxSize::Tx16x4,
TxSize::Tx8x32,
TxSize::Tx32x8,
TxSize::Tx16x64,
TxSize::Tx64x16,
];
const MAX_TX_DEPTH: [usize; 22] = [
0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 2, 2, 3, 3, 4, 4,
];
const SPLIT_TX_SIZE: [TxSize; 19] = [
TxSize::Tx4x4,
TxSize::Tx4x4,
TxSize::Tx8x8,
TxSize::Tx16x16,
TxSize::Tx32x32,
TxSize::Tx4x4,
TxSize::Tx4x4,
TxSize::Tx8x8,
TxSize::Tx8x8,
TxSize::Tx16x16,
TxSize::Tx16x16,
TxSize::Tx32x32,
TxSize::Tx32x32,
TxSize::Tx4x8,
TxSize::Tx8x4,
TxSize::Tx8x16,
TxSize::Tx16x8,
TxSize::Tx16x32,
TxSize::Tx32x16,
];
#[must_use]
pub fn max_tx_size_rect(block: usize) -> TxSize {
MAX_TX_SIZE_RECT
.get(block)
.copied()
.unwrap_or(TxSize::Tx4x4)
}
#[must_use]
pub fn max_tx_depth(block: usize) -> usize {
MAX_TX_DEPTH.get(block).copied().unwrap_or(0)
}
#[must_use]
pub fn split_tx_size(tx: TxSize) -> TxSize {
SPLIT_TX_SIZE
.get(tx as usize)
.copied()
.unwrap_or(TxSize::Tx4x4)
}
#[must_use]
pub fn block_size_from_4x4(w4: usize, h4: usize) -> Option<usize> {
Some(match (w4, h4) {
(1, 1) => 0,
(1, 2) => 1,
(2, 1) => 2,
(2, 2) => 3,
(2, 4) => 4,
(4, 2) => 5,
(4, 4) => 6,
(4, 8) => 7,
(8, 4) => 8,
(8, 8) => 9,
(8, 16) => 10,
(16, 8) => 11,
(16, 16) => 12,
(16, 32) => 13,
(32, 16) => 14,
(32, 32) => 15,
(1, 4) => 16,
(4, 1) => 17,
(2, 8) => 18,
(8, 2) => 19,
(4, 16) => 20,
(16, 4) => 21,
_ => return None,
})
}
#[must_use]
pub fn tx_depth_ctx(above_w: usize, left_h: usize, max_rect: TxSize) -> usize {
usize::from(above_w >= max_rect.width()) + usize::from(left_h >= max_rect.height())
}
pub struct TxDepthCdfs {
tx8x8: [[u16; 3]; 3],
tx16x16: [[u16; 4]; 3],
tx32x32: [[u16; 4]; 3],
tx64x64: [[u16; 4]; 3],
}
impl TxDepthCdfs {
#[must_use]
pub fn new() -> Self {
Self {
tx8x8: cdf::DEFAULT_TX_8X8_CDF,
tx16x16: cdf::DEFAULT_TX_16X16_CDF,
tx32x32: cdf::DEFAULT_TX_32X32_CDF,
tx64x64: cdf::DEFAULT_TX_64X64_CDF,
}
}
}
impl Default for TxDepthCdfs {
fn default() -> Self {
Self::new()
}
}
fn row_mut<T>(slice: &mut [T], index: usize) -> Result<&mut T> {
slice
.get_mut(index)
.ok_or_else(|| PixelsError::malformed("avif", "an AV1 tx-size CDF index ran out of range"))
}
pub struct TxSizeParams {
pub block: usize,
pub tx_mode_select: bool,
pub lossless: bool,
pub allow_select: bool,
pub above_w: usize,
pub left_h: usize,
}
pub fn read_tx_size(
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TxDepthCdfs,
params: &TxSizeParams,
) -> Result<TxSize> {
code_tx_size(dec, cdfs, params)
}
pub(crate) fn code_tx_size(
dec: &mut impl TileCoder,
cdfs: &mut TxDepthCdfs,
params: &TxSizeParams,
) -> Result<TxSize> {
if params.lossless {
return Ok(TxSize::Tx4x4);
}
let max_rect = max_tx_size_rect(params.block);
let max_depth = max_tx_depth(params.block);
let mut tx = max_rect;
if params.block > BLOCK_4X4 && params.allow_select && params.tx_mode_select {
let ctx = tx_depth_ctx(params.above_w, params.left_h, max_rect);
let depth = match max_depth {
4 => dec.symbol(row_mut(&mut cdfs.tx64x64, ctx)?, Site::TxDepth)?,
3 => dec.symbol(row_mut(&mut cdfs.tx32x32, ctx)?, Site::TxDepth)?,
2 => dec.symbol(row_mut(&mut cdfs.tx16x16, ctx)?, Site::TxDepth)?,
_ => dec.symbol(row_mut(&mut cdfs.tx8x8, ctx)?, Site::TxDepth)?,
};
for _ in 0..depth {
tx = split_tx_size(tx);
}
}
Ok(tx)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::panic,
reason = "tests operate on known-good values and assert shapes directly"
)]
mod tests {
use super::*;
#[test]
fn tables_agree_with_the_spec() {
assert_eq!(max_tx_size_rect(6), TxSize::Tx16x16);
assert_eq!(max_tx_depth(6), 2);
assert_eq!(split_tx_size(TxSize::Tx16x16), TxSize::Tx8x8);
assert_eq!(split_tx_size(TxSize::Tx8x8), TxSize::Tx4x4);
assert_eq!(split_tx_size(TxSize::Tx4x4), TxSize::Tx4x4);
assert_eq!(max_tx_size_rect(12), TxSize::Tx64x64);
assert_eq!(max_tx_depth(12), 4);
assert_eq!(split_tx_size(TxSize::Tx64x64), TxSize::Tx32x32);
assert_eq!(split_tx_size(TxSize::Tx4x16), TxSize::Tx4x8);
}
#[test]
fn block_size_lookup_round_trips_the_defined_shapes() {
assert_eq!(block_size_from_4x4(1, 1), Some(0));
assert_eq!(block_size_from_4x4(4, 4), Some(6));
assert_eq!(block_size_from_4x4(16, 4), Some(21));
assert_eq!(block_size_from_4x4(1, 8), None);
}
fn params(block: usize, tx_mode_select: bool, lossless: bool) -> TxSizeParams {
TxSizeParams {
block,
tx_mode_select,
lossless,
allow_select: true,
above_w: 0,
left_h: 0,
}
}
#[test]
fn lossless_is_always_4x4_and_reads_nothing() {
let data = [0xFF; 4];
let mut dec = SymbolDecoder::new(&data, true).unwrap();
let mut cdfs = TxDepthCdfs::new();
let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(12, true, true)).unwrap();
assert_eq!(tx, TxSize::Tx4x4);
}
#[test]
fn without_selection_the_size_is_the_max_rect() {
let data = [0xFF; 4];
let mut dec = SymbolDecoder::new(&data, true).unwrap();
let mut cdfs = TxDepthCdfs::new();
let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(6, false, false)).unwrap();
assert_eq!(tx, TxSize::Tx16x16);
let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(BLOCK_4X4, true, false)).unwrap();
assert_eq!(tx, TxSize::Tx4x4);
}
#[test]
fn a_selected_size_is_the_max_rect_or_a_split_of_it() {
let data = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut dec = SymbolDecoder::new(&data, true).unwrap();
let mut cdfs = TxDepthCdfs::new();
let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(6, true, false)).unwrap();
assert!(matches!(
tx,
TxSize::Tx16x16 | TxSize::Tx8x8 | TxSize::Tx4x4
));
}
#[test]
fn depth_context_counts_the_larger_neighbours() {
let m = TxSize::Tx16x16;
assert_eq!(tx_depth_ctx(0, 0, m), 0); assert_eq!(tx_depth_ctx(16, 0, m), 1); assert_eq!(tx_depth_ctx(32, 16, m), 2); }
}