/// Loop-restoration stripe boundary storage and source view.
///
/// Loop restoration filters each global stripe of a plane against an
/// immutable post-CDEF/upscaled source, except at internal stripe
/// boundaries where the filter taps would read CDEF-modified rows
/// belonging to the neighbouring stripe. The deblocked rows at those
/// boundaries are saved before CDEF and, when superres is active,
/// horizontally upscaled with the same normative scaler and full-frame
/// widths as the frame itself, so the saved rows share the frame's
/// sampling phase (AV1 audit ยง6; AOM restoration.c:1355-1508).
///
/// Stripe geometry is global per plane and independent of tiles,
/// superblocks and restoration unit sizes: stripe k spans
/// `[max(0, k*S - O), min((k+1)*S - O, plane_height))` with
/// `S = 64 >> ss_y` and `O = 8 >> ss_y`. A 256-row luma plane yields
/// `[0,56),[56,120),[120,184),...`; a 4:2:0 chroma plane of 128 rows
/// yields `[0,28),[28,60),[60,92),...`. The last stripe is clipped to
/// the frame height.
///|
/// Global stripe rectangles for one plane, as (start, end) row pairs.
fn av1_restoration_stripe_bounds(
plane_height : Int,
subsampling_y : Bool,
) -> Array[(Int, Int)]? {
if plane_height <= 0 || plane_height > 65536 {
return None
}
let s = if subsampling_y { 32 } else { 64 }
let o = if subsampling_y { 4 } else { 8 }
let count = (plane_height + o + s - 1) / s
let bounds : Array[(Int, Int)] = []
for k in 0.. o { k * s - o } else { 0 }
let end = {
let raw = (k + 1) * s - o
if raw < plane_height {
raw
} else {
plane_height
}
}
bounds.push((start, end))
}
Some(bounds)
}
///|
/// Saved deblocked rows at every internal stripe boundary of one
/// plane. `top[k]` holds the two rows above stripe k's start (k >= 1)
/// and `bottom[k]` the two rows at or after stripe k's end (non-last
/// stripe), each as `2 * width` native samples: the first saved row
/// occupies `[0, width)`, the second `[width, 2*width)`. Outer
/// boundaries are empty; the source view repeats the frame edge rows
/// there. All rows are stored at the upscaled plane width.
pub(all) struct Av1RestorationPlaneStripes {
count : Int
width : Int
height : Int
starts : Array[Int]
ends : Array[Int]
top : Array[Array[Int]]
bottom : Array[Array[Int]]
}
///|
/// Per-plane stripe stores for one frame.
pub(all) struct Av1RestorationStripeStore {
planes : Array[Av1RestorationPlaneStripes]
}
///|
/// One stored boundary pair (two rows at upscaled width), or an empty
/// array when the boundary is absent.
fn av1_restoration_stripe_pair(
store : Av1RestorationStripeStore,
plane : Int,
stripe : Int,
above : Bool,
) -> Array[Int] {
if plane < 0 || plane >= store.planes.length() {
return []
}
let info = store.planes[plane]
if stripe < 0 || stripe >= info.count {
return []
}
if above {
info.top[stripe]
} else {
info.bottom[stripe]
}
}
///|
/// Save the deblocked rows at every internal stripe boundary before
/// CDEF runs. For each internal stripe top (k >= 1) save LF rows
/// `y0-2, y0-1`; for each internal stripe bottom (y1 < plane height)
/// save LF rows `y1, y1+1`, duplicating `y1` when only one row
/// remains. When superres is active (`output_width != width`) the
/// saved rows are upscaled with the normative scaler at the same
/// full-frame plane widths, preserving the frame's sampling phase.
/// Returns None on invalid geometry or an out-of-range plane.
fn av1_restoration_save_stripes(
planes : Array[Array[Int]],
coded_width : Int,
width : Int,
output_width : Int,
height : Int,
bit_depth : Int,
monochrome : Bool,
sub_x? : Int = 1,
sub_y? : Int = 1,
) -> Av1RestorationStripeStore? {
let num_planes = if monochrome { 1 } else { 3 }
if planes.length() != num_planes ||
width <= 0 ||
height <= 0 ||
width > 65536 ||
height > 65536 ||
coded_width < width ||
output_width < width ||
output_width > 2 * width ||
(bit_depth != 8 && bit_depth != 10 && bit_depth != 12) {
return None
}
let superres = output_width != width
let result : Array[Av1RestorationPlaneStripes] = []
for plane in 0.. value
None => return None
}
let count = bounds.length()
let starts : Array[Int] = Array::make(count, 0)
let ends : Array[Int] = Array::make(count, 0)
let top : Array[Array[Int]] = Array::makei(count, fn(_) { [] })
let bottom : Array[Array[Int]] = Array::makei(count, fn(_) { [] })
// One entry per internal boundary pair, in stripe order.
let entries : Array[(Int, Bool, Int, Int)] = []
for k in 0..= 1 {
entries.push((k, true, start - 2, start - 1))
}
if end < plane_height {
let second = if end + 1 < plane_height { end + 1 } else { end }
entries.push((k, false, end, second))
}
}
if !superres {
for entry in entries {
let (k, above, row_a, row_b) = entry
let pair : Array[Int] = Array::make(2 * plane_width_coded, 0)
for i in 0..<2 {
let row = if i == 0 { row_a } else { row_b }
for col in 0.. 0 {
// Pack the coded rows and upscale them in one normative pass so
// every saved row shares the full-frame sampling phase.
let row_count = entries.length() * 2
let temp : Array[Int] = Array::make(row_count * stride, 0)
let mut index = 0
for entry in entries {
let (_, _, row_a, row_b) = entry
for i in 0..<2 {
let row = if i == 0 { row_a } else { row_b }
for col in 0.. value
None => return None
}
index = 0
for entry in entries {
let (k, above, _, _) = entry
let pair : Array[Int] = Array::make(2 * plane_width_up, 0)
for i in 0..<2 {
for col in 0.. Bool {
if store.planes.length() == 0 {
return false
}
for info in store.planes {
if info.count <= 0 || info.width <= 0 || info.height <= 0 {
return false
}
if info.starts.length() != info.count ||
info.ends.length() != info.count ||
info.top.length() != info.count ||
info.bottom.length() != info.count {
return false
}
for k in 0.. info.height {
return false
}
for pair in [info.top[k], info.bottom[k]] {
if pair.length() != 0 && pair.length() != 2 * info.width {
return false
}
}
}
if info.top[0].length() != 0 || info.bottom[info.count - 1].length() != 0 {
return false
}
for k in 1.. Int {
let store = view.store
if plane < 0 ||
plane >= store.planes.length() ||
plane >= view.frame.length() ||
plane >= view.frame_strides.length() {
return 0
}
let info = store.planes[plane]
if stripe < 0 || stripe >= info.count || info.width <= 0 {
return 0
}
let px = if x < 0 { 0 } else if x >= info.width { info.width - 1 } else { x }
let stride = view.frame_strides[plane]
let start = info.starts[stripe]
let end = info.ends[stripe]
if y < start {
// Above the current stripe's start. Stripe 0 starts at row 0, so
// this is only reached by out-of-frame taps: the frame top repeats
// the post-CDEF/upscaled edge row. Internal starts read the saved
// top pair as [top0, top0, top1] at offsets -3, -2, -1.
if stripe == 0 {
return view.frame[plane][px]
}
let pair = info.top[stripe]
let row = if start - y >= 2 { 0 } else { 1 }
return pair[row * info.width + px]
}
if y >= end {
// Below the current stripe's end. Only the last stripe ends at the
// frame bottom; its out-of-frame taps repeat the post-CDEF edge
// row. Non-last stripes read the saved bottom pair as
// [bottom0, bottom1, bottom1] at offsets 0, 1, 2, whose duplicated
// second row also covers taps past the frame bottom.
if stripe == info.count - 1 {
return view.frame[plane][(info.height - 1) * stride + px]
}
let pair = info.bottom[stripe]
let row = if y - end <= 0 { 0 } else { 1 }
return pair[row * info.width + px]
}
view.frame[plane][y * stride + px]
}