/// Directional reference availability follows libaom reconintra.c at
/// 8e7b6a567df174d795479b92b4ac766d271add73, including its packed 128-superblock
/// tables and VERT_A/B exceptions. No sample values determine availability.
/// Copyright (c) 2016, Alliance for Open Media. All rights reserved.
/// BSD 2 Clause License and Alliance for Open Media Patent License 1.0:
/// https://www.aomedia.org/license/software and https://www.aomedia.org/license/patent
///|
priv struct Av1IntraEdgeCounts {
n_top : Int
n_left : Int
n_topright : Int
n_bottomleft : Int
}
///|
fn av1_intra_edge_table(
top_right : Bool,
index : Int,
partition : Av1SbPartition,
) -> Array[Int]? {
let normal = if top_right { av1_edge_tr_tables } else { av1_edge_bl_tables }
if partition is Av1SbPartitionVertA || partition is Av1SbPartitionVertB {
let special = if top_right {
av1_edge_tr_vert_tables
} else {
av1_edge_bl_vert_tables
}
match index {
3 => Some(special[0])
6 => Some(special[1])
9 => Some(special[2])
12 => Some(special[3])
1 | 4 | 7 | 10 | 13 | 15 => Some(normal[index])
_ => None
}
} else {
Some(normal[index])
}
}
///|
fn av1_intra_has_top_right(
block_width : Int,
block_height : Int,
mi_row : Int,
mi_col : Int,
row_offset : Int,
col_offset : Int,
tx_width : Int,
subsample : Int,
sb_size : Int,
top_available : Bool,
right_available : Bool,
table : Array[Int],
) -> Bool {
if !top_available || !right_available {
return false
}
let bw_mi = block_width / 4
let plane_bw = if bw_mi >> subsample > 0 { bw_mi >> subsample } else { 1 }
let tx_w_mi = tx_width / 4
if row_offset > 0 {
if block_width > 64 {
let chunk = 16 >> subsample
if row_offset == chunk && col_offset + tx_w_mi == chunk {
return true
}
return col_offset % chunk + tx_w_mi < chunk
}
return col_offset + tx_w_mi < plane_bw
}
if col_offset + tx_w_mi < plane_bw {
return true
}
let log_w = av1_msac_floor_log2(bw_mi)
let log_h = av1_msac_floor_log2(block_height / 4)
let sb_mi = sb_size / 4
let block_row = (mi_row & (sb_mi - 1)) >> log_h
let block_col = (mi_col & (sb_mi - 1)) >> log_w
if block_row == 0 {
return true
}
if (block_col + 1) << log_w >= sb_mi {
return false
}
// MAX_MIB_SIZE_LOG2 is five even when the sequence uses 64x64 SBs.
let index = (block_row << (5 - log_w)) + block_col
((table[index / 8] >> (index % 8)) & 1) != 0
}
///|
fn av1_intra_has_bottom_left(
block_width : Int,
block_height : Int,
mi_row : Int,
mi_col : Int,
row_offset : Int,
col_offset : Int,
tx_height : Int,
subsample : Int,
sb_size : Int,
bottom_available : Bool,
left_available : Bool,
table : Array[Int],
) -> Bool {
if !bottom_available || !left_available {
return false
}
let tx_h_mi = tx_height / 4
if block_width > 64 && col_offset > 0 {
let chunk = 16 >> subsample
if col_offset % chunk == 0 {
let block_height_plane = (block_height / 4) >> subsample
let unit_height = if block_height_plane < chunk {
block_height_plane
} else {
chunk
}
return row_offset % chunk + tx_h_mi < unit_height
}
}
if col_offset > 0 {
return false
}
let bh_mi = block_height / 4
let plane_bh = if bh_mi >> subsample > 0 { bh_mi >> subsample } else { 1 }
if row_offset + tx_h_mi < plane_bh {
return true
}
let log_w = av1_msac_floor_log2(block_width / 4)
let log_h = av1_msac_floor_log2(bh_mi)
let sb_mi = sb_size / 4
let block_row = (mi_row & (sb_mi - 1)) >> log_h
let block_col = (mi_col & (sb_mi - 1)) >> log_w
if block_col == 0 {
let row_in_sb = (block_row << log_h >> subsample) + row_offset
return row_in_sb + tx_h_mi < sb_mi >> subsample
}
if (block_row + 1) << log_h >= sb_mi {
return false
}
let index = (block_row << (5 - log_w)) + block_col
((table[index / 8] >> (index % 8)) & 1) != 0
}
///|
/// tile/block coordinates are luma pixels; tx coordinates are plane pixels.
/// coded dimensions are the MI-padded frame dimensions. Preserve the original
/// chroma owner's MI position when scaling a 4-axis block for table lookup.
/// Counts are nonnegative. The directional caller changes unused TR/BL counts
/// to -1 according to the final angle before invoking the prediction kernel.
fn av1_intra_edge_counts(
tile : Av1SbBlock,
coded_width : Int,
coded_height : Int,
block : Av1SbBlock,
partition : Av1SbPartition,
tx : Av1TxNode,
plane : Int,
sb_size : Int,
sub_x? : Int = 1,
sub_y? : Int = 1,
) -> Av1IntraEdgeCounts? {
if coded_width <= 0 ||
coded_height <= 0 ||
coded_width > MAX_IMAGE_PIXELS / coded_height ||
coded_width % 8 != 0 ||
coded_height % 8 != 0 ||
tile.row < 0 ||
tile.col < 0 ||
tile.width <= 0 ||
tile.height <= 0 ||
tile.row % 4 != 0 ||
tile.col % 4 != 0 ||
tile.width % 4 != 0 ||
tile.height % 4 != 0 ||
tile.width > coded_width ||
tile.height > coded_height ||
tile.col > coded_width - tile.width ||
tile.row > coded_height - tile.height ||
(sb_size != 64 && sb_size != 128) ||
plane < 0 ||
plane > 2 ||
block.row < tile.row ||
block.col < tile.col ||
block.row >= tile.row + tile.height ||
block.col >= tile.col + tile.width ||
block.row % 4 != 0 ||
block.col % 4 != 0 ||
block.width > sb_size ||
block.height > sb_size ||
av1_intra_block_index(block.width, block.height) is None ||
!av1_tx_block_geometry_valid(tx.width, tx.height) ||
tx.width > 64 ||
tx.height > 64 {
return None
}
let mi_row = block.row / 4
let mi_col = block.col / 4
// Each axis carries its own subsampling shift; a luma block narrower than
// eight pixels merges into its eight-pixel chroma group only on a subsampled
// axis (AV1 section 6.4.3 is_chroma_reference).
let plane_sub_x = if plane == 0 { 0 } else { sub_x }
let plane_sub_y = if plane == 0 { 0 } else { sub_y }
if plane != 0 &&
(
(plane_sub_x == 1 && block.width < 8 && (mi_col & 1) == 0) ||
(plane_sub_y == 1 && block.height < 8 && (mi_row & 1) == 0)
) {
return None
}
let origin_x = if plane_sub_x == 1 { block.col / 8 * 4 } else { block.col }
let origin_y = if plane_sub_y == 1 { block.row / 8 * 4 } else { block.row }
let plane_width = if plane != 0 {
let scaled = block.width >> plane_sub_x
if scaled < 4 {
4
} else {
scaled
}
} else {
block.width
}
let plane_height = if plane != 0 {
let scaled = block.height >> plane_sub_y
if scaled < 4 {
4
} else {
scaled
}
} else {
block.height
}
if tx.col < origin_x ||
tx.row < origin_y ||
tx.col % 4 != 0 ||
tx.row % 4 != 0 ||
tx.width > plane_width ||
tx.height > plane_height ||
tx.col - origin_x > plane_width - tx.width ||
tx.row - origin_y > plane_height - tx.height ||
tx.col >= (tile.col + tile.width) >> plane_sub_x ||
tx.row >= (tile.row + tile.height) >> plane_sub_y {
return None
}
let col_offset = (tx.col - origin_x) / 4
let row_offset = (tx.row - origin_y) / 4
let top_available = row_offset != 0 ||
(if plane_sub_y == 1 && block.height == 4 {
mi_row - 1 > tile.row / 4
} else {
mi_row > tile.row / 4
})
let left_available = col_offset != 0 ||
(if plane_sub_x == 1 && block.width == 4 {
mi_col - 1 > tile.col / 4
} else {
mi_col > tile.col / 4
})
let xr = (coded_width >> plane_sub_x) - tx.col - tx.width
let yd = (coded_height >> plane_sub_y) - tx.row - tx.height
let right_available = mi_col + ((col_offset + tx.width / 4) << plane_sub_x) <
(tile.col + tile.width) / 4
let bottom_available = yd > 0 &&
mi_row + ((row_offset + tx.height / 4) << plane_sub_y) <
(tile.row + tile.height) / 4
let scaled_width = plane_width << plane_sub_x
let scaled_height = plane_height << plane_sub_y
let index = av1_intra_block_index(scaled_width, scaled_height).unwrap()
let tr_table = match av1_intra_edge_table(true, index, partition) {
Some(value) => value
None => return None
}
let bl_table = match av1_intra_edge_table(false, index, partition) {
Some(value) => value
None => return None
}
let has_tr = av1_intra_has_top_right(
scaled_width,
scaled_height,
mi_row,
mi_col,
row_offset,
col_offset,
tx.width,
plane_sub_x,
sb_size,
top_available,
right_available,
tr_table,
)
let has_bl = av1_intra_has_bottom_left(
scaled_width,
scaled_height,
mi_row,
mi_col,
row_offset,
col_offset,
tx.height,
plane_sub_y,
sb_size,
bottom_available,
left_available,
bl_table,
)
Some({
n_top: if top_available {
if xr >= 0 {
tx.width
} else {
xr + tx.width
}
} else {
0
},
n_left: if left_available {
if yd >= 0 {
tx.height
} else {
yd + tx.height
}
} else {
0
},
n_topright: if has_tr && xr > 0 {
if xr < tx.width {
xr
} else {
tx.width
}
} else {
0
},
n_bottomleft: if has_bl && yd > 0 {
if yd < tx.height {
yd
} else {
tx.height
}
} else {
0
},
})
}
///|
/// Return tile-local MI indices of the neighbouring coding blocks used for
/// edge-filter type. UV selects the bottom-right owner in each aligned chroma
/// group; it must not inspect the adjacent non-owner luma block instead.
fn av1_intra_edge_neighbor_indices(
tile : Av1SbBlock,
block : Av1SbBlock,
plane : Int,
sub_x? : Int = 1,
sub_y? : Int = 1,
) -> (Int?, Int?)? {
if tile.row < 0 ||
tile.col < 0 ||
tile.row % 4 != 0 ||
tile.col % 4 != 0 ||
tile.width <= 0 ||
tile.height <= 0 ||
tile.width % 4 != 0 ||
tile.height % 4 != 0 ||
plane < 0 ||
plane > 2 ||
block.row < tile.row ||
block.col < tile.col ||
block.row >= tile.row + tile.height ||
block.col >= tile.col + tile.width ||
block.row % 4 != 0 ||
block.col % 4 != 0 ||
av1_intra_block_index(block.width, block.height) is None {
return None
}
if plane != 0 &&
(
(sub_x == 1 && block.width < 8 && ((block.col / 4) & 1) == 0) ||
(sub_y == 1 && block.height < 8 && ((block.row / 4) & 1) == 0)
) {
return None
}
let cols = tile.width / 4
let rows = tile.height / 4
let row = (block.row - tile.row) / 4
let col = (block.col - tile.col) / 4
let above_row = if plane == 0 || sub_y == 0 {
row - 1
} else {
row - ((block.row / 4) & 1) - 1
}
let above_col = if plane == 0 || sub_x == 0 {
col
} else {
col - ((block.col / 4) & 1) + 1
}
let left_row = if plane == 0 || sub_y == 0 {
row
} else {
row - ((block.row / 4) & 1) + 1
}
let left_col = if plane == 0 || sub_x == 0 {
col - 1
} else {
col - ((block.col / 4) & 1) - 1
}
let above = if above_row >= 0 &&
above_row < rows &&
above_col >= 0 &&
above_col < cols {
Some(above_row * cols + above_col)
} else {
None
}
let left = if left_row >= 0 &&
left_row < rows &&
left_col >= 0 &&
left_col < cols {
Some(left_row * cols + left_col)
} else {
None
}
Some((above, left))
}
///|
/// Smooth context belongs to neighbouring coding-block modes, not to a
/// preceding transform inside the current block. Inter UV modes may be stale
/// and are ignored, matching reconintra.c:is_smooth.
fn av1_intra_edge_neighbor_smooth(
tile : Av1SbBlock,
block : Av1SbBlock,
plane : Int,
y_modes : Array[Int],
uv_modes : Array[Int],
inter? : Array[Bool]? = None,
sub_x? : Int = 1,
sub_y? : Int = 1,
) -> Bool? {
let (above, left) = match
av1_intra_edge_neighbor_indices(tile, block, plane, sub_x~, sub_y~) {
Some(value) => value
None => return None
}
let modes = if plane == 0 { y_modes } else { uv_modes }
let count = tile.width / 4 * (tile.height / 4)
if modes.length() != count {
return None
}
if inter is Some(flags) && flags.length() != count {
return None
}
for neighbor in [above, left] {
if neighbor is Some(i) {
let skip = if plane == 0 {
false
} else {
match inter {
Some(flags) => flags[i]
None => false
}
}
if !skip && modes[i] >= AV1_INTRA_SMOOTH && modes[i] <= AV1_INTRA_SMOOTH_H {
return Some(true)
}
}
}
Some(false)
}
///|
// Packed flags copied from the original libaom 128x128 tables.
let av1_edge_tr_tables : Array[Array[Int]] = [
[
255, 255, 255, 255, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127,
127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 127,
255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127,
127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 255, 255, 127,
85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85,
85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85,
119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119,
119, 119, 119, 85, 85, 85, 85,
], // 4x4
[
255, 255, 255, 255, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119,
255, 127, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119,
255, 255, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119,
255, 127, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119,
], // 4x8
[
255, 255, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 127, 127, 0, 0, 85,
85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 255, 127, 0, 0, 85, 85, 0, 0, 119, 119,
0, 0, 85, 85, 0, 0, 127, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0,
0,
], // 8x4
[
255, 255, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85, 255,
127, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85,
], // 8x8
[
255, 255, 119, 119, 127, 127, 119, 119, 255, 127, 119, 119, 127, 127, 119, 119,
], // 8x16
[255, 0, 85, 0, 119, 0, 85, 0, 127, 0, 85, 0, 119, 0, 85, 0], // 16x8
[255, 85, 119, 85, 127, 85, 119, 85], // 16x16
[255, 119, 127, 119], // 16x32
[15, 5, 7, 5], // 32x16
[95, 87], // 32x32
[127], // 32x64
[19], // 64x32
[7], // 64x64
[3], // 64x128
[1], // 128x64
[1], // 128x128
[
255, 255, 255, 255, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127,
255, 255, 255, 127, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127,
], // 4x16
[
255, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0, 127, 0, 0, 0, 85, 0, 0,
0, 119, 0, 0, 0, 85, 0, 0, 0,
], // 16x4
[255, 255, 127, 127, 255, 127, 127, 127], // 8x32
[15, 0, 5, 0, 7, 0, 5, 0], // 32x8
[255, 127], // 16x64
[3, 1], // 64x16
]
///|
// Packed flags copied from the original libaom 128x128 tables.
let av1_edge_tr_vert_tables : Array[Array[Int]] = [
[
255, 255, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0, 255, 127, 0,
0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0,
], // 8x8
[255, 0, 119, 0, 127, 0, 119, 0], // 16x16
[15, 7], // 32x32
[3], // 64x64
]
///|
// Packed flags copied from the original libaom 128x128 tables.
let av1_edge_bl_tables : Array[Array[Int]] = [
[
84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16,
17, 17, 17, 84, 85, 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85,
85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0,
0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85,
16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84,
85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0,
0, 0,
], // 4x4
[
16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1,
1, 1, 16, 17, 17, 17, 0, 0, 0, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17,
0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0,
], // 4x8
[
254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 1, 254, 255,
84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 0, 254, 255, 84, 85,
254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 1, 254, 255, 84, 85, 254, 255,
16, 17, 254, 255, 84, 85, 254, 255, 0, 0,
], // 8x4
[
84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0, 84, 85, 16, 17, 84,
85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0,
], // 8x8
[16, 17, 0, 1, 16, 17, 0, 0, 16, 17, 0, 1, 16, 17, 0, 0], // 8x16
[254, 84, 254, 16, 254, 84, 254, 0, 254, 84, 254, 16, 254, 84, 254, 0], // 16x8
[84, 16, 84, 0, 84, 16, 84, 0], // 16x16
[16, 0, 16, 0], // 16x32
[78, 14, 78, 14], // 32x16
[4, 4], // 32x32
[0], // 32x64
[34], // 64x32
[0], // 64x64
[0], // 64x128
[0], // 128x64
[0], // 128x128
[
0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1,
1, 1, 0, 0, 0, 0,
], // 4x16
[
254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0, 254,
254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0,
], // 16x4
[0, 1, 0, 0, 0, 1, 0, 0], // 8x32
[238, 78, 238, 14, 238, 78, 238, 14], // 32x8
[0, 0], // 16x64
[42, 42], // 64x16
]
///|
// Packed flags copied from the original libaom 128x128 tables.
let av1_edge_bl_vert_tables : Array[Array[Int]] = [
[
254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0, 254, 255,
16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0,
], // 8x8
[254, 16, 254, 0, 254, 16, 254, 0], // 16x16
[14, 14], // 32x32
[2], // 64x64
]