///|
/// One visible native-depth plane, tightly packed in row-major order.
/// `data[y * width + x]` is an unshifted sample, not a serialized byte pair.
/// On return, data.length() == width * height and every sample is in
/// 0..<(1 << frame.bit_depth). The caller owns this mutable buffer.
pub struct Av1PixelPlane {
width : Int
height : Int
data : FixedArray[Int]
}
///|
/// A displayed AV1 picture after reconstruction, filters, super-resolution
/// and signaled film grain, before color conversion or range expansion.
/// `planes` contains Y alone for monochrome, otherwise Y, U, V. Matrix 0
/// identifies G, B, R in those same slots. Chroma dimensions round up on
/// subsampled axes. There is no coded padding in any output plane.
///
/// Color identifiers retain their bitstream values (2 means unspecified).
/// AVIF output also applies the existing nclx metadata rules. Samples retain
/// the original 8/10/12-bit precision and full/limited range. All buffers are
/// independent of decoder references, other returned pictures and input data.
pub struct Av1NativeFrame {
width : Int
height : Int
bit_depth : Int
subsampling_x : Bool
subsampling_y : Bool
full_range : Bool
matrix_coefficients : Int
color_primaries : Int
transfer_characteristics : Int
planes : FixedArray[Av1PixelPlane]
}
///|
/// An AVIF image: a primary image/grid or an animation sample, with validated alpha.
/// Alpha, when present, is a single native-depth plane with the same size
/// and bit depth as color. Its own full_range flag describes its raw samples;
/// limited-range alpha has not been expanded. None means opaque.
/// `premultiplied` reports the color-to-alpha prem association. Color samples
/// have not been unpremultiplied. Use avif_decode_rgba for straight RGBA8.
pub struct AvifNativeImage {
color : Av1NativeFrame
alpha : Av1NativeFrame?
premultiplied : Bool
}
///|
fn av1_frame_to_native(frame : Av1FramePlanes) -> Av1NativeFrame {
let planes = FixedArray::makei(frame.planes.length(), fn(plane) {
let (dx, dy) = av1_frame_plane_divisor(frame, plane)
let width = (frame.width + dx - 1) / dx
let height = (frame.height + dy - 1) / dy
let stride = frame.coded_width / dx
let source = frame.planes[plane]
let data = if width == stride {
// A new buffer is still required even when only bottom padding is cut.
FixedArray::from_array(source[:width * height])
} else {
let cropped = FixedArray::make(width * height, 0)
for y in 0.. Av1NativeFrame? {
if !decode_input_allowed(data.length(), limits) {
return None
}
av1_decode_frame_planes(
data,
allow_monochrome=true,
budget=decode_budget(limits),
).map(av1_frame_to_native)
}
///|
/// Decode AVIF primary color and optional alpha at native precision.
/// Grid assembly, layer selection and metadata follow avif_decode_rgba.
/// Invalid or unsupported color/alpha input returns None, never partial color.
/// This is the primary-item API; it does not return an animation timeline.
fn avif_decode_native_limited(
data : Array[Byte],
limits? : DecodeLimits? = None,
) -> AvifNativeImage? {
let (color, alpha, premultiplied) = match
avif_decode_image_planes(data, limits~) {
Some(value) => value
None => return None
}
Some({
color: av1_frame_to_native(color),
alpha: alpha.map(av1_frame_to_native),
premultiplied,
})
}
///|
/// Decode temporal units using the same state and presentation policy as
/// av1_video_decode_temporal_unit, returning owned native-depth pictures.
/// Some([]) means valid input with no presentation; hidden frames still
/// update references. None means rejection. State is not rolled back on
/// failure; discard the decoder after None. Monochrome is opt-in, matching
/// the RGBA video entry point. The two APIs can share one decoder in order.
fn av1_video_decode_temporal_unit_native_limited(
decoder : Av1VideoDecoder,
data : Array[Byte],
allow_monochrome? : Bool = false,
limits? : DecodeLimits? = None,
) -> Array[Av1NativeFrame]? {
if !decode_input_allowed(data.length(), limits) {
return None
}
av1_video_decode_temporal_unit_planes(
decoder,
data,
allow_monochrome~,
budget=decode_budget(limits),
).map(frames => frames.map(av1_frame_to_native))
}