1use super::cdf;
17use super::coder::{Site, TileCoder};
18use super::symbol::SymbolDecoder;
19use super::transform::TxSize;
20use otf_pixels_core::{PixelsError, Result};
21
22pub const BLOCK_4X4: usize = 0;
24
25const MAX_TX_SIZE_RECT: [TxSize; 22] = [
28 TxSize::Tx4x4,
29 TxSize::Tx4x8,
30 TxSize::Tx8x4,
31 TxSize::Tx8x8,
32 TxSize::Tx8x16,
33 TxSize::Tx16x8,
34 TxSize::Tx16x16,
35 TxSize::Tx16x32,
36 TxSize::Tx32x16,
37 TxSize::Tx32x32,
38 TxSize::Tx32x64,
39 TxSize::Tx64x32,
40 TxSize::Tx64x64,
41 TxSize::Tx64x64,
42 TxSize::Tx64x64,
43 TxSize::Tx64x64,
44 TxSize::Tx4x16,
45 TxSize::Tx16x4,
46 TxSize::Tx8x32,
47 TxSize::Tx32x8,
48 TxSize::Tx16x64,
49 TxSize::Tx64x16,
50];
51
52const MAX_TX_DEPTH: [usize; 22] = [
56 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 2, 2, 3, 3, 4, 4,
57];
58
59const SPLIT_TX_SIZE: [TxSize; 19] = [
61 TxSize::Tx4x4,
62 TxSize::Tx4x4,
63 TxSize::Tx8x8,
64 TxSize::Tx16x16,
65 TxSize::Tx32x32,
66 TxSize::Tx4x4,
67 TxSize::Tx4x4,
68 TxSize::Tx8x8,
69 TxSize::Tx8x8,
70 TxSize::Tx16x16,
71 TxSize::Tx16x16,
72 TxSize::Tx32x32,
73 TxSize::Tx32x32,
74 TxSize::Tx4x8,
75 TxSize::Tx8x4,
76 TxSize::Tx8x16,
77 TxSize::Tx16x8,
78 TxSize::Tx16x32,
79 TxSize::Tx32x16,
80];
81
82#[must_use]
84pub fn max_tx_size_rect(block: usize) -> TxSize {
85 MAX_TX_SIZE_RECT
86 .get(block)
87 .copied()
88 .unwrap_or(TxSize::Tx4x4)
89}
90
91#[must_use]
93pub fn max_tx_depth(block: usize) -> usize {
94 MAX_TX_DEPTH.get(block).copied().unwrap_or(0)
95}
96
97#[must_use]
99pub fn split_tx_size(tx: TxSize) -> TxSize {
100 SPLIT_TX_SIZE
101 .get(tx as usize)
102 .copied()
103 .unwrap_or(TxSize::Tx4x4)
104}
105
106#[must_use]
109pub fn block_size_from_4x4(w4: usize, h4: usize) -> Option<usize> {
110 Some(match (w4, h4) {
111 (1, 1) => 0,
112 (1, 2) => 1,
113 (2, 1) => 2,
114 (2, 2) => 3,
115 (2, 4) => 4,
116 (4, 2) => 5,
117 (4, 4) => 6,
118 (4, 8) => 7,
119 (8, 4) => 8,
120 (8, 8) => 9,
121 (8, 16) => 10,
122 (16, 8) => 11,
123 (16, 16) => 12,
124 (16, 32) => 13,
125 (32, 16) => 14,
126 (32, 32) => 15,
127 (1, 4) => 16,
128 (4, 1) => 17,
129 (2, 8) => 18,
130 (8, 2) => 19,
131 (4, 16) => 20,
132 (16, 4) => 21,
133 _ => return None,
134 })
135}
136
137#[must_use]
143pub fn tx_depth_ctx(above_w: usize, left_h: usize, max_rect: TxSize) -> usize {
144 usize::from(above_w >= max_rect.width()) + usize::from(left_h >= max_rect.height())
145}
146
147pub struct TxDepthCdfs {
149 tx8x8: [[u16; 3]; 3],
150 tx16x16: [[u16; 4]; 3],
151 tx32x32: [[u16; 4]; 3],
152 tx64x64: [[u16; 4]; 3],
153}
154
155impl TxDepthCdfs {
156 #[must_use]
158 pub fn new() -> Self {
159 Self {
160 tx8x8: cdf::DEFAULT_TX_8X8_CDF,
161 tx16x16: cdf::DEFAULT_TX_16X16_CDF,
162 tx32x32: cdf::DEFAULT_TX_32X32_CDF,
163 tx64x64: cdf::DEFAULT_TX_64X64_CDF,
164 }
165 }
166}
167
168impl Default for TxDepthCdfs {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174fn row_mut<T>(slice: &mut [T], index: usize) -> Result<&mut T> {
175 slice
176 .get_mut(index)
177 .ok_or_else(|| PixelsError::malformed("avif", "an AV1 tx-size CDF index ran out of range"))
178}
179
180pub struct TxSizeParams {
182 pub block: usize,
184 pub tx_mode_select: bool,
186 pub lossless: bool,
188 pub allow_select: bool,
190 pub above_w: usize,
192 pub left_h: usize,
194}
195
196pub fn read_tx_size(
206 dec: &mut SymbolDecoder<'_>,
207 cdfs: &mut TxDepthCdfs,
208 params: &TxSizeParams,
209) -> Result<TxSize> {
210 code_tx_size(dec, cdfs, params)
211}
212
213pub(crate) fn code_tx_size(
216 dec: &mut impl TileCoder,
217 cdfs: &mut TxDepthCdfs,
218 params: &TxSizeParams,
219) -> Result<TxSize> {
220 if params.lossless {
221 return Ok(TxSize::Tx4x4);
222 }
223 let max_rect = max_tx_size_rect(params.block);
224 let max_depth = max_tx_depth(params.block);
225 let mut tx = max_rect;
226 if params.block > BLOCK_4X4 && params.allow_select && params.tx_mode_select {
227 let ctx = tx_depth_ctx(params.above_w, params.left_h, max_rect);
228 let depth = match max_depth {
229 4 => dec.symbol(row_mut(&mut cdfs.tx64x64, ctx)?, Site::TxDepth)?,
230 3 => dec.symbol(row_mut(&mut cdfs.tx32x32, ctx)?, Site::TxDepth)?,
231 2 => dec.symbol(row_mut(&mut cdfs.tx16x16, ctx)?, Site::TxDepth)?,
232 _ => dec.symbol(row_mut(&mut cdfs.tx8x8, ctx)?, Site::TxDepth)?,
233 };
234 for _ in 0..depth {
235 tx = split_tx_size(tx);
236 }
237 }
238 Ok(tx)
239}
240
241#[cfg(test)]
242#[allow(
243 clippy::unwrap_used,
244 clippy::indexing_slicing,
245 clippy::panic,
246 reason = "tests operate on known-good values and assert shapes directly"
247)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn tables_agree_with_the_spec() {
253 assert_eq!(max_tx_size_rect(6), TxSize::Tx16x16);
255 assert_eq!(max_tx_depth(6), 2);
256 assert_eq!(split_tx_size(TxSize::Tx16x16), TxSize::Tx8x8);
257 assert_eq!(split_tx_size(TxSize::Tx8x8), TxSize::Tx4x4);
258 assert_eq!(split_tx_size(TxSize::Tx4x4), TxSize::Tx4x4);
259 assert_eq!(max_tx_size_rect(12), TxSize::Tx64x64);
261 assert_eq!(max_tx_depth(12), 4);
262 assert_eq!(split_tx_size(TxSize::Tx64x64), TxSize::Tx32x32);
263 assert_eq!(split_tx_size(TxSize::Tx4x16), TxSize::Tx4x8);
265 }
266
267 #[test]
268 fn block_size_lookup_round_trips_the_defined_shapes() {
269 assert_eq!(block_size_from_4x4(1, 1), Some(0));
270 assert_eq!(block_size_from_4x4(4, 4), Some(6));
271 assert_eq!(block_size_from_4x4(16, 4), Some(21));
272 assert_eq!(block_size_from_4x4(1, 8), None);
274 }
275
276 fn params(block: usize, tx_mode_select: bool, lossless: bool) -> TxSizeParams {
277 TxSizeParams {
278 block,
279 tx_mode_select,
280 lossless,
281 allow_select: true,
282 above_w: 0,
283 left_h: 0,
284 }
285 }
286
287 #[test]
288 fn lossless_is_always_4x4_and_reads_nothing() {
289 let data = [0xFF; 4];
290 let mut dec = SymbolDecoder::new(&data, true).unwrap();
291 let mut cdfs = TxDepthCdfs::new();
292 let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(12, true, true)).unwrap();
294 assert_eq!(tx, TxSize::Tx4x4);
295 }
296
297 #[test]
298 fn without_selection_the_size_is_the_max_rect() {
299 let data = [0xFF; 4];
300 let mut dec = SymbolDecoder::new(&data, true).unwrap();
301 let mut cdfs = TxDepthCdfs::new();
302 let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(6, false, false)).unwrap();
304 assert_eq!(tx, TxSize::Tx16x16);
305 let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(BLOCK_4X4, true, false)).unwrap();
307 assert_eq!(tx, TxSize::Tx4x4);
308 }
309
310 #[test]
311 fn a_selected_size_is_the_max_rect_or_a_split_of_it() {
312 let data = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00];
313 let mut dec = SymbolDecoder::new(&data, true).unwrap();
314 let mut cdfs = TxDepthCdfs::new();
315 let tx = read_tx_size(&mut dec, &mut cdfs, ¶ms(6, true, false)).unwrap();
318 assert!(matches!(
319 tx,
320 TxSize::Tx16x16 | TxSize::Tx8x8 | TxSize::Tx4x4
321 ));
322 }
323
324 #[test]
325 fn depth_context_counts_the_larger_neighbours() {
326 let m = TxSize::Tx16x16;
328 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); }
332}