/// `find_warp_samples` (AV1 §7.10.4.1) and its `add_sample` helper
/// (§7.10.4.2), transcribed from `av1spec08.md:1836-1989`.
///
/// The process reads **no entropy-coded symbols**: it only inspects the motion
/// field of already-decoded neighbour blocks. Its output `NumSamples` is what
/// decides, inside `read_motion_mode`, whether a block codes the binary
/// `use_obmc` symbol or the three-symbol `motion_mode` symbol, so a wrong
/// `NumSamples` desynchronises the symbol stream rather than merely picking a
/// worse predictor.
///
/// What is *not* implemented here, and why:
///
/// * The warp **estimation** and warped **prediction** (§7.11.3.5, §7.11.3.8)
/// are absent, so a block that selects LOCALWARP is refused by the caller.
/// * `LEAST_SQUARES_SAMPLES_MAX`, the bound that makes `add_sample` stop
/// scanning, is not stated anywhere in the local specification references.
/// It is a parameter so the scan structure can be tested independently of it;
/// see `av1_warp_samples_scan_limit` for the derived regime in which the
/// bound is inert.
///|
/// The largest block the 64x64-superblock profile can code is 64x64, i.e. 16
/// units of 4x4 per axis. `find_warp_samples` scans at most `w4 / miStep`
/// positions above and `h4 / miStep` to the left, and `miStep` is
/// `Min(w4, srcW)` with the smallest codable block being 8x8 = 2 units, so each
/// axis contributes at most 8. The top-left and top-right corners add two more.
/// A limit of 18 therefore never truncates that profile's scan, which makes the
/// result independent of the unknown `LEAST_SQUARES_SAMPLES_MAX` there.
pub let av1_warp_samples_scan_limit : Int = 18
///|
/// `find_warp_samples`: the number of valid warp candidates around one block.
///
/// `block_row`/`block_col` are the block's MI position, `block_w4`/`block_h4`
/// its 4x4 extent, `avail_u`/`avail_l` the tile availability flags,
/// `mv_row`/`mv_col` the block's own motion vector, and `ref_frame` the
/// reference name every candidate must share.
fn av1_warp_samples(
field : Av1MotionField,
block_row : Int,
block_col : Int,
block_w4 : Int,
block_h4 : Int,
avail_u : Bool,
avail_l : Bool,
mv_row : Int,
mv_col : Int,
ref_frame : Int,
) -> Int {
let mut num_samples = 0
let mut num_scanned = 0
let mut do_top_left = true
let mut do_top_right = true
// `add_sample` (AV1 §7.10.4.2).
let add = fn(delta_row : Int, delta_col : Int) -> Unit {
if num_scanned >= av1_warp_samples_scan_limit {
return
}
let mv_row_at = block_row + delta_row
let mv_col_at = block_col + delta_col
if mv_row_at < 0 ||
mv_col_at < 0 ||
mv_row_at >= field.rows ||
mv_col_at >= field.cols {
return
}
let unit = av1_field_unit(field, mv_row_at, mv_col_at) * 2
if !field.written[unit / 2] ||
field.refs[unit] != ref_frame ||
field.refs[unit + 1] != av1_none_frame {
return
}
let cand_w4 = field.width4[unit / 2]
let cand_h4 = field.height4[unit / 2]
let cand_row = mv_row_at - mv_row_at % cand_h4
let cand_col = mv_col_at - mv_col_at % cand_w4
let cand_unit = av1_field_unit(field, cand_row, cand_col) * 2
// The candidate's `cand[0..3]` source/destination positions the warp fit
// consumes are derived from the block centre below; they are not needed to
// decide validity, so only the threshold comparison is computed.
let threshold = if 16 > 112 {
16
} else if 112 < block_w4 * 4 && 112 < block_h4 * 4 {
112
} else if block_w4 * 4 < block_h4 * 4 {
block_w4 * 4
} else {
block_h4 * 4
}
let diff_row = if field.mv_rows[cand_unit] - mv_row >= 0 {
field.mv_rows[cand_unit] - mv_row
} else {
mv_row - field.mv_rows[cand_unit]
}
let diff_col = if field.mv_cols[cand_unit] - mv_col >= 0 {
field.mv_cols[cand_unit] - mv_col
} else {
mv_col - field.mv_cols[cand_unit]
}
let valid = diff_row + diff_col <= threshold
num_scanned = num_scanned + 1
if !valid && num_scanned > 1 {
return
}
if valid {
num_samples = num_samples + 1
}
}
if avail_u {
let src_unit = av1_field_unit(field, block_row - 1, block_col)
let src_w4 = field.width4[src_unit]
if block_w4 <= src_w4 {
let col_offset = -(block_col & (src_w4 - 1))
if col_offset < 0 {
do_top_left = false
}
if col_offset + src_w4 > block_w4 {
do_top_right = false
}
add(-1, 0)
} else {
let mut i = 0
while i < block_w4 && i < field.cols - block_col {
let step = if block_w4 < src_w4 { block_w4 } else { src_w4 }
add(-1, i)
i = i + (if step < 1 { 1 } else { step })
}
}
}
if avail_l {
let src_unit = av1_field_unit(field, block_row, block_col - 1)
let src_h4 = field.height4[src_unit]
if block_h4 <= src_h4 {
let row_offset = -(block_row & (src_h4 - 1))
if row_offset < 0 {
do_top_left = false
}
add(0, -1)
} else {
let mut i = 0
while i < block_h4 && i < field.rows - block_row {
let step = if block_h4 < src_h4 { block_h4 } else { src_h4 }
add(i, -1)
i = i + (if step < 1 { 1 } else { step })
}
}
}
if do_top_left {
add(-1, -1)
}
if do_top_right &&
(if block_w4 > block_h4 { block_w4 } else { block_h4 }) <= 16 {
add(-1, block_w4)
}
if num_samples == 0 && num_scanned > 0 {
num_samples = 1
}
num_samples
}