///|
/// Straight RGBA UNORM16, row-major R/G/B/A, four UInt16 samples per pixel.
/// Each channel spans 0..65535; alpha 65535 is opaque. This is an integer
/// buffer, not half-float or serialized bytes. The caller owns its data.
pub struct Image16 {
width : Int
height : Int
data : FixedArray[UInt16]
}
///|
/// Native buffers are caller-mutable: validate their shape and sample range
/// before conversion instead of reading a mismatched plane or wrapping a value.
fn av1_native_pixels_valid(frame : Av1NativeFrame) -> Bool {
if frame.width <= 0 ||
frame.height <= 0 ||
frame.width > MAX_IMAGE_PIXELS / frame.height ||
(frame.bit_depth != 8 && frame.bit_depth != 10 && frame.bit_depth != 12) ||
(frame.planes.length() != 1 && frame.planes.length() != 3) {
return false
}
if frame.planes.length() == 3 && frame.subsampling_y && !frame.subsampling_x {
return false
}
let maximum = (1 << frame.bit_depth) - 1
for p in 0.. 0 && frame.subsampling_x {
(frame.width + 1) / 2
} else {
frame.width
}
let h = if p > 0 && frame.subsampling_y {
(frame.height + 1) / 2
} else {
frame.height
}
if plane.width != w || plane.height != h || plane.data.length() != w * h {
return false
}
for sample in plane.data {
if sample < 0 || sample > maximum {
return false
}
}
}
true
}
///|
fn av1_rgba16_quantize(value : Double) -> UInt16 {
let clipped = if value <= 0.0 {
0.0
} else if value >= 1.0 {
1.0
} else {
value
}
(clipped * 65535.0 + 0.5).to_int().to_uint16()
}
///|
fn av1_rgba16_straight(
value : Double,
maximum : Double,
coverage : Double,
) -> Double {
if coverage == 0.0 || value <= 0.0 {
0.0
} else {
let clipped = if value >= 1.0 { 1.0 } else { value }
let result = clipped * maximum / coverage
if result > 1.0 {
1.0
} else {
result
}
}
}
///|
fn av1_rgba16_inputs_valid(
color : Av1NativeFrame,
alpha : Av1NativeFrame?,
premultiplied : Bool,
) -> Bool {
if !av1_native_pixels_valid(color) || (premultiplied && alpha is None) {
return false
}
if alpha is Some(plane) {
if !av1_native_pixels_valid(plane) ||
plane.width != color.width ||
plane.height != color.height ||
plane.bit_depth != color.bit_depth ||
plane.planes.length() != 1 {
return false
}
}
let mono = color.planes.length() == 1
if !mono &&
(
!av1_color_supported(
color.matrix_coefficients,
color.color_primaries,
color.transfer_characteristics,
) ||
(
color.matrix_coefficients == 0 &&
(color.subsampling_x || color.subsampling_y)
)
) {
return false
}
true
}
///|
fn av1_write_rgba16(
color : Av1NativeFrame,
alpha : Av1NativeFrame?,
premultiplied : Bool,
out : FixedArray[UInt16],
row_stride : Int,
sample_offset : Int,
) -> Bool {
let mono = color.planes.length() == 1
let maximum = (1 << color.bit_depth) - 1
let scale = 1 << (color.bit_depth - 8)
let black = if color.full_range { 0.0 } else { (16 * scale).to_double() }
let luma_span = if color.full_range {
maximum.to_double()
} else {
(219 * scale).to_double()
}
let chroma_span = if color.full_range {
maximum.to_double()
} else if color.matrix_coefficients == 8 {
(219 * scale).to_double()
} else {
(224 * scale).to_double()
}
let midpoint = (128 * scale).to_double()
let maximum_f = maximum.to_double()
let luma = color.planes[0].data
// Monochrome never reads chroma. Reusing its plane here avoids optional
// plane lookups in the pixel loop without allocating dummy planes.
let u_plane = color.planes[if mono { 0 } else { 1 }]
let v_plane = color.planes[if mono { 0 } else { 2 }]
let u_data = u_plane.data
let v_data = v_plane.data
for row in 0.. {
r = red
g = green
b = blue
}
None => return false
}
}
}
let coverage = match alpha {
None => maximum
Some(frame) => {
let sample = frame.planes[0].data[i]
if frame.full_range {
sample
} else {
av1_alpha_expand_limited_sample(sample, frame.bit_depth)
}
}
}
// Undo premultiplication with native alpha before RGB quantization.
if premultiplied {
let coverage_f = coverage.to_double()
r = av1_rgba16_straight(r, maximum_f, coverage_f)
g = av1_rgba16_straight(g, maximum_f, coverage_f)
b = av1_rgba16_straight(b, maximum_f, coverage_f)
}
if !mono && color.color_primaries == 10 {
match av1_color_xyz_to_srgb(r, g, b, color.transfer_characteristics) {
Some((red, green, blue)) => {
r = red
g = green
b = blue
}
None => return false
}
}
if r.is_nan() ||
g.is_nan() ||
b.is_nan() ||
r.is_inf() ||
g.is_inf() ||
b.is_inf() {
return false
}
let destination = sample_offset + row * row_stride + x * 4
out[destination] = av1_rgba16_quantize(r)
out[destination + 1] = av1_rgba16_quantize(g)
out[destination + 2] = av1_rgba16_quantize(b)
out[destination + 3] = ((coverage * 65535 + maximum / 2) / maximum).to_uint16()
}
}
true
}
///|
fn av1_native_to_rgba16(
color : Av1NativeFrame,
alpha : Av1NativeFrame?,
premultiplied : Bool,
) -> Image16? {
if !av1_rgba16_inputs_valid(color, alpha, premultiplied) {
return None
}
let out = FixedArray::make(color.width * color.height * 4, (0 : UInt16))
if !av1_write_rgba16(color, alpha, premultiplied, out, color.width * 4, 0) {
return None
}
Some({ width: color.width, height: color.height, data: out, })
}
///|
/// Convert an owned native frame to opaque RGBA16 without decoding again.
/// Nearest chroma and source color/transfer conventions match RGBA8; XYZ is
/// converted to BT.709/sRGB. No HDR tone mapping is performed. Malformed
/// caller-edited planes or unsupported color metadata return None.
pub fn Av1NativeFrame::to_rgba16(self : Av1NativeFrame) -> Image16? {
av1_native_to_rgba16(self, None, false)
}
///|
/// Convert a static image or animation sample to straight RGBA16. Limited
/// alpha is expanded at its original depth; prem is undone with native alpha
/// before final RGB16 rounding. This does not quantize through RGBA8.
/// The returned buffer shares no mutable data with this image or prior outputs.
pub fn AvifNativeImage::to_rgba16(self : AvifNativeImage) -> Image16? {
av1_native_to_rgba16(self.color, self.alpha, self.premultiplied)
}
///|
/// Decode raw AV1 and convert its first selected presentation to RGBA16.
fn av1_decode_rgba16_limited(
data : Array[Byte],
limits? : DecodeLimits? = None,
) -> Image16? {
match av1_decode_native_limited(data, limits~) {
Some(frame) => frame.to_rgba16()
None => None
}
}
///|
/// Decode an AVIF primary image/grid with optional alpha to straight RGBA16.
fn avif_decode_rgba16_limited(
data : Array[Byte],
limits? : DecodeLimits? = None,
) -> Image16? {
match avif_decode_native_limited(data, limits~) {
Some(image) => image.to_rgba16()
None => None
}
}