///|
/// Parameters for `to_sdr_rgba16`.
///
/// `peak_luminance_nits` is the nominal display peak used for HLG's reference
/// display OOTF and as the extended-Reinhard shoulder.  `reference_white_nits`
/// is the linear-light normalization anchor.  Both values are absolute cd/m2
/// (nits), and `reference_white_nits` must not exceed the peak.  `exposure_stops`
/// is a base-2 gain applied before tone mapping.  The default is a 1000-nit
/// HLG reference-display peak and tone-map shoulder, with a 100-nit reference
/// white and zero exposure. The output remains normalized SDR.
pub(all) struct HdrToneMapOptions {
  peak_luminance_nits : Double
  reference_white_nits : Double
  exposure_stops : Double
}

///|
pub fn HdrToneMapOptions::default() -> HdrToneMapOptions {
  {
    peak_luminance_nits: 1000.0,
    reference_white_nits: 100.0,
    exposure_stops: 0.0,
  }
}

///|
priv struct HdrRgbMatrix {
  m00 : Double
  m01 : Double
  m02 : Double
  m10 : Double
  m11 : Double
  m12 : Double
  m20 : Double
  m21 : Double
  m22 : Double
}

///|
fn hdr_identity_matrix() -> HdrRgbMatrix {
  {
    m00: 1.0,
    m01: 0.0,
    m02: 0.0,
    m10: 0.0,
    m11: 1.0,
    m12: 0.0,
    m20: 0.0,
    m21: 0.0,
    m22: 1.0,
  }
}

///|
fn hdr_matrix_apply(
  m : HdrRgbMatrix,
  r : Double,
  g : Double,
  b : Double,
) -> (Double, Double, Double) {
  (
    m.m00 * r + m.m01 * g + m.m02 * b,
    m.m10 * r + m.m11 * g + m.m12 * b,
    m.m20 * r + m.m21 * g + m.m22 * b,
  )
}

///|
fn hdr_matrix_mul(a : HdrRgbMatrix, b : HdrRgbMatrix) -> HdrRgbMatrix {
  {
    m00: a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20,
    m01: a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21,
    m02: a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22,
    m10: a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20,
    m11: a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21,
    m12: a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22,
    m20: a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20,
    m21: a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21,
    m22: a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22,
  }
}

///|
fn hdr_matrix_inverse(m : HdrRgbMatrix) -> HdrRgbMatrix? {
  let determinant = m.m00 * (m.m11 * m.m22 - m.m12 * m.m21) -
    m.m01 * (m.m10 * m.m22 - m.m12 * m.m20) +
    m.m02 * (m.m10 * m.m21 - m.m11 * m.m20)
  if determinant.is_nan() || determinant.is_inf() || determinant.abs() < 1.0e-12 {
    return None
  }
  Some({
    m00: (m.m11 * m.m22 - m.m12 * m.m21) / determinant,
    m01: (m.m02 * m.m21 - m.m01 * m.m22) / determinant,
    m02: (m.m01 * m.m12 - m.m02 * m.m11) / determinant,
    m10: (m.m12 * m.m20 - m.m10 * m.m22) / determinant,
    m11: (m.m00 * m.m22 - m.m02 * m.m20) / determinant,
    m12: (m.m02 * m.m10 - m.m00 * m.m12) / determinant,
    m20: (m.m10 * m.m21 - m.m11 * m.m20) / determinant,
    m21: (m.m01 * m.m20 - m.m00 * m.m21) / determinant,
    m22: (m.m00 * m.m11 - m.m01 * m.m10) / determinant,
  })
}

///|
/// H.273 Table 2 chromaticities for every RGB primary set already recognized
/// by the decoder.  Value 10 is CIE XYZ, handled as an identity XYZ source.
fn hdr_primary_chromaticities(
  primaries : Int,
) -> (Double, Double, Double, Double, Double, Double, Double, Double)? {
  match primaries {
    // BT.709 / sRGB, D65.
    1 => Some((0.640, 0.330, 0.300, 0.600, 0.150, 0.060, 0.3127, 0.3290))
    // BT.470 System M, C white.
    4 => Some((0.670, 0.330, 0.210, 0.710, 0.140, 0.080, 0.310, 0.316))
    // BT.470 System B, G, D65.
    5 => Some((0.640, 0.330, 0.290, 0.600, 0.150, 0.060, 0.3127, 0.3290))
    // SMPTE 170M / BT.601, D65.
    6 => Some((0.630, 0.340, 0.310, 0.595, 0.155, 0.070, 0.3127, 0.3290))
    // SMPTE 240M, D65.
    7 => Some((0.630, 0.340, 0.310, 0.595, 0.155, 0.070, 0.3127, 0.3290))
    // Generic film, C white.
    8 => Some((0.681, 0.319, 0.243, 0.692, 0.145, 0.049, 0.310, 0.316))
    // BT.2020, D65.
    9 => Some((0.708, 0.292, 0.170, 0.797, 0.131, 0.046, 0.3127, 0.3290))
    // CIE XYZ has no RGB chromaticities.
    10 => None
    // SMPTE RP 431-2 / DCI-P3, DCI white.
    11 => Some((0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.314, 0.351))
    // SMPTE EG 432-1 / Display-P3, D65.
    12 => Some((0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290))
    // EBU Tech. 3213-E, D65.
    22 => Some((0.630, 0.340, 0.295, 0.605, 0.155, 0.077, 0.3127, 0.3290))
    _ => None
  }
}

///|
/// Build the RGB-to-XYZ matrix from the primary and reference-white
/// chromaticities in H.273 Table 2.
fn hdr_rgb_to_xyz(primaries : Int) -> HdrRgbMatrix? {
  let (rx, ry, gx, gy, bx, by, wx, wy) = match
    hdr_primary_chromaticities(primaries) {
    Some(value) => value
    None => return None
  }
  let p = {
    m00: rx / ry,
    m01: gx / gy,
    m02: bx / by,
    m10: 1.0,
    m11: 1.0,
    m12: 1.0,
    m20: (1.0 - rx - ry) / ry,
    m21: (1.0 - gx - gy) / gy,
    m22: (1.0 - bx - by) / by,
  }
  let inverse = match hdr_matrix_inverse(p) {
    Some(value) => value
    None => return None
  }
  let white = (wx / wy, 1.0, (1.0 - wx - wy) / wy)
  let (sr, sg, sb) = hdr_matrix_apply(inverse, white.0, white.1, white.2)
  Some({
    m00: p.m00 * sr,
    m01: p.m01 * sg,
    m02: p.m02 * sb,
    m10: p.m10 * sr,
    m11: p.m11 * sg,
    m12: p.m12 * sb,
    m20: p.m20 * sr,
    m21: p.m21 * sg,
    m22: p.m22 * sb,
  })
}

///|
fn hdr_adapt_source_white_to_d65(primaries : Int) -> HdrRgbMatrix? {
  let (_, _, _, _, _, _, wx, wy) = match hdr_primary_chromaticities(primaries) {
    Some(value) => value
    None => return None
  }
  let source_white = (wx / wy, 1.0, (1.0 - wx - wy) / wy)
  let target_white = (0.3127 / 0.3290, 1.0, (1.0 - 0.3127 - 0.3290) / 0.3290)
  // Bradford chromatic adaptation.  It is identity (within rounding) for
  // the D65 primary sets, while keeping C/DCI-P3 sources neutral in BT.709.
  let bradford = {
    m00: 0.8951,
    m01: 0.2664,
    m02: -0.1614,
    m10: -0.7502,
    m11: 1.7135,
    m12: 0.0367,
    m20: 0.0389,
    m21: -0.0685,
    m22: 1.0296,
  }
  let inverse = match hdr_matrix_inverse(bradford) {
    Some(value) => value
    None => return None
  }
  let source_lms = hdr_matrix_apply(
    bradford,
    source_white.0,
    source_white.1,
    source_white.2,
  )
  let target_lms = hdr_matrix_apply(
    bradford,
    target_white.0,
    target_white.1,
    target_white.2,
  )
  if source_lms.0 == 0.0 ||
    source_lms.1 == 0.0 ||
    source_lms.2 == 0.0 ||
    !hdr_finite(source_lms.0) ||
    !hdr_finite(source_lms.1) ||
    !hdr_finite(source_lms.2) ||
    !hdr_finite(target_lms.0) ||
    !hdr_finite(target_lms.1) ||
    !hdr_finite(target_lms.2) {
    return None
  }
  let scale = {
    m00: target_lms.0 / source_lms.0,
    m01: 0.0,
    m02: 0.0,
    m10: 0.0,
    m11: target_lms.1 / source_lms.1,
    m12: 0.0,
    m20: 0.0,
    m21: 0.0,
    m22: target_lms.2 / source_lms.2,
  }
  Some(hdr_matrix_mul(inverse, hdr_matrix_mul(scale, bradford)))
}

///|
fn hdr_source_to_bt709(primaries : Int) -> HdrRgbMatrix? {
  let target = match hdr_rgb_to_xyz(1) {
    Some(value) => value
    None => return None
  }
  let target_inverse = match hdr_matrix_inverse(target) {
    Some(value) => value
    None => return None
  }
  if primaries == 10 {
    // CICP 10 is CIE XYZ, so no source RGB-to-XYZ matrix is required.
    return Some(target_inverse)
  }
  let source = match hdr_rgb_to_xyz(primaries) {
    Some(value) => value
    None => return None
  }
  let adaptation = match hdr_adapt_source_white_to_d65(primaries) {
    Some(value) => value
    None => return None
  }
  Some(hdr_matrix_mul(target_inverse, hdr_matrix_mul(adaptation, source)))
}

///|
fn hdr_finite(value : Double) -> Bool {
  !value.is_nan() && !value.is_inf()
}

///|
fn hdr_options_valid(options : HdrToneMapOptions) -> Bool {
  hdr_finite(options.peak_luminance_nits) &&
  hdr_finite(options.reference_white_nits) &&
  hdr_finite(options.exposure_stops) &&
  options.peak_luminance_nits >= 1.0 &&
  options.peak_luminance_nits <= 10000.0 &&
  options.reference_white_nits >= 1.0 &&
  options.reference_white_nits <= options.peak_luminance_nits &&
  options.exposure_stops >= -16.0 &&
  options.exposure_stops <= 16.0
}

///|
fn hdr_matrix_metadata_valid(frame : Av1NativeFrame) -> Bool {
  if frame.planes.length() == 1 {
    return av1_color_transfer_supported(frame.transfer_characteristics)
  }
  if frame.matrix_coefficients == 0 &&
    (frame.subsampling_x || frame.subsampling_y) {
    return false
  }
  av1_color_supported(
    frame.matrix_coefficients,
    frame.color_primaries,
    frame.transfer_characteristics,
  ) &&
  av1_color_transfer_supported(frame.transfer_characteristics) &&
  (if frame.color_primaries == 10 {
    frame.matrix_coefficients == 11
  } else {
    hdr_primary_chromaticities(frame.color_primaries) is Some(_)
  }) &&
  !((frame.matrix_coefficients == 12 || frame.matrix_coefficients == 13) &&
  av1_color_derived_coefficients(frame.color_primaries) is None)
}

///|
fn hdr_decode_transfer(value : Double, transfer : Int) -> Double? {
  match av1_color_linear(value, transfer) {
    Some(linear) => if hdr_finite(linear) { Some(linear) } else { None }
    None => None
  }
}

///|
fn hdr_unpremultiply(
  value : Double,
  maximum : Double,
  coverage : Double,
) -> Double {
  if coverage <= 0.0 || value <= 0.0 {
    0.0
  } else {
    value * maximum / coverage
  }
}

///|
fn hdr_decode_yuv(
  color : Av1NativeFrame,
  x : Int,
  y_index : Int,
) -> (Double, Double, Double)? {
  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 luma = color.planes[0].data[y_index * color.width + x].to_double()
  let y_value = (luma - black) / luma_span
  if mono {
    return Some((y_value, y_value, y_value))
  }
  let u_plane = color.planes[1]
  let v_plane = color.planes[2]
  let cx = if color.subsampling_x { x / 2 } else { x }
  let cy = if color.subsampling_y { y_index / 2 } else { y_index }
  let ci = cy * u_plane.width + cx
  let u = u_plane.data[ci].to_double()
  let v = v_plane.data[ci].to_double()
  if color.matrix_coefficients == 0 {
    Some(((v - black) / luma_span, y_value, (u - black) / luma_span))
  } else {
    av1_color_rgb(
      y_value,
      (u - midpoint) / chroma_span,
      (v - midpoint) / chroma_span,
      color.matrix_coefficients,
      color.color_primaries,
      color.transfer_characteristics,
    )
  }
}

///|
fn hdr_hlg_scene_weights(
  primaries : Int,
  mono : Bool,
) -> (Double, Double, Double)? {
  if mono {
    // Monochrome has no RGB primary set; its scalar channel is its scene
    // luminance by definition.
    Some((1.0, 0.0, 0.0))
  } else if primaries == 10 {
    // CICP 10 carries CIE XYZ, whose Y component is scene luminance.
    Some((0.0, 1.0, 0.0))
  } else if primaries == 9 {
    // BT.2100 explicitly defines the HLG OOTF with these BT.2020 weights.
    Some((0.2627, 0.6780, 0.0593))
  } else {
    // Other declared RGB primaries use their own XYZ Y row. This avoids
    // silently applying BT.2020 weights to BT.709, P3, or legacy RGB sets.
    match hdr_rgb_to_xyz(primaries) {
      Some(matrix) => Some((matrix.m10, matrix.m11, matrix.m12))
      None => None
    }
  }
}

///|
fn hdr_to_display_nits(
  r : Double,
  g : Double,
  b : Double,
  transfer : Int,
  peak_luminance_nits : Double,
  reference_white_nits : Double,
  scene_weights : (Double, Double, Double),
) -> (Double, Double, Double)? {
  let lr = match hdr_decode_transfer(r, transfer) {
    Some(value) => value
    None => return None
  }
  let lg = match hdr_decode_transfer(g, transfer) {
    Some(value) => value
    None => return None
  }
  let lb = match hdr_decode_transfer(b, transfer) {
    Some(value) => value
    None => return None
  }
  if transfer == 16 {
    Some((lr * 10000.0, lg * 10000.0, lb * 10000.0))
  } else if transfer == 18 {
    // BT.2100 Table 5: HLG's inverse OETF yields scene-linear E; the
    // reference display OOTF applies alpha * E * Ys^(gamma-1), with alpha
    // equal to the nominal peak luminance in cd/m2.
    let ys = scene_weights.0 * lr + scene_weights.1 * lg + scene_weights.2 * lb
    let gamma = 1.2 + 0.42 * @math.log10(peak_luminance_nits / 1000.0)
    let ootf = if ys <= 0.0 { 0.0 } else { @math.pow(ys, gamma - 1.0) }
    Some(
      (
        peak_luminance_nits * lr * ootf,
        peak_luminance_nits * lg * ootf,
        peak_luminance_nits * lb * ootf,
      ),
    )
  } else {
    // SDR and scene-linear CICP transfers use the declared reference white as
    // their absolute anchor.  PQ is the only absolute transfer in H.273.
    Some(
      (
        lr * reference_white_nits,
        lg * reference_white_nits,
        lb * reference_white_nits,
      ),
    )
  }
}

///|
fn hdr_tone_map_luminance(
  luminance : Double,
  options : HdrToneMapOptions,
) -> Double {
  if luminance <= 0.0 {
    return 0.0
  }
  let exposed = luminance * @math.pow(2.0, options.exposure_stops)
  let x = exposed / options.reference_white_nits
  let white = options.peak_luminance_nits / options.reference_white_nits
  // Extended Reinhard, with white equal to the configured peak. Values above
  // the shoulder are clipped only at the final normalized output.
  let mapped = x * (1.0 + x / (white * white)) / (1.0 + x)
  if mapped <= 0.0 {
    0.0
  } else if mapped >= 1.0 {
    1.0
  } else {
    mapped
  }
}

///|
fn hdr_clamp01(value : Double) -> Double {
  if value <= 0.0 {
    0.0
  } else if value >= 1.0 {
    1.0
  } else {
    value
  }
}

///|
fn hdr_quantize(value : Double) -> UInt16 {
  (hdr_clamp01(value) * 65535.0 + 0.5).to_int().to_uint16()
}

///|
fn hdr_alpha16(coverage : Int, maximum : Int) -> UInt16 {
  ((coverage * 65535 + maximum / 2) / maximum).to_uint16()
}

///|
fn hdr_write_sdr_rgba16(
  color : Av1NativeFrame,
  alpha : Av1NativeFrame?,
  premultiplied : Bool,
  options : HdrToneMapOptions,
  source_to_bt709 : HdrRgbMatrix,
  scene_weights : (Double, Double, Double),
  out : FixedArray[UInt16],
) -> Result[Unit, DecodeError] {
  let maximum = (1 << color.bit_depth) - 1
  let maximum_f = maximum.to_double()
  let transfer = color.transfer_characteristics
  for y in 0.. value
        None =>
          return Err(
            decode_error(
              InvalidData,
              "hdr/color",
              "Color matrix reconstruction produced invalid values",
            ),
          )
      }
      let (encoded_r0, encoded_g0, encoded_b0) = encoded
      let mut r = encoded_r0
      let mut g = encoded_g0
      let mut b = encoded_b0
      let coverage = match alpha {
        None => maximum
        Some(frame) => {
          let sample = frame.planes[0].data[y * color.width + x]
          if frame.full_range {
            sample
          } else {
            av1_alpha_expand_limited_sample(sample, frame.bit_depth)
          }
        }
      }
      if premultiplied {
        let coverage_f = coverage.to_double()
        r = hdr_unpremultiply(r, maximum_f, coverage_f)
        g = hdr_unpremultiply(g, maximum_f, coverage_f)
        b = hdr_unpremultiply(b, maximum_f, coverage_f)
      }
      let (lr, lg, lb) = match
        hdr_to_display_nits(
          r,
          g,
          b,
          transfer,
          options.peak_luminance_nits,
          options.reference_white_nits,
          scene_weights,
        ) {
        Some(value) => value
        None =>
          return Err(
            decode_error(
              InvalidData,
              "hdr/transfer",
              "Transfer inverse produced a non-finite value",
            ),
          )
      }
      let (rr0, gg0, bb0) = hdr_matrix_apply(source_to_bt709, lr, lg, lb)
      let mut rr = rr0
      let mut gg = gg0
      let mut bb = bb0
      if !hdr_finite(rr) || !hdr_finite(gg) || !hdr_finite(bb) {
        return Err(
          decode_error(
            InvalidData,
            "hdr/color",
            "Gamut conversion produced a non-finite value",
          ),
        )
      }
      let luminance = 0.2126 * rr + 0.7152 * gg + 0.0722 * bb
      let mapped = hdr_tone_map_luminance(luminance, options)
      if luminance > 0.0 {
        let scale = mapped / luminance
        rr *= scale
        gg *= scale
        bb *= scale
      } else {
        rr = 0.0
        gg = 0.0
        bb = 0.0
      }
      // SDR output uses the IEC 61966-2-1 / H.273 transfer (CICP 13).
      let encoded_r = match av1_color_nonlinear(hdr_clamp01(rr), 13) {
        Some(value) => value
        None =>
          return Err(
            decode_error(InvalidData, "hdr/output", "sRGB encoding failed"),
          )
      }
      let encoded_g = match av1_color_nonlinear(hdr_clamp01(gg), 13) {
        Some(value) => value
        None =>
          return Err(
            decode_error(InvalidData, "hdr/output", "sRGB encoding failed"),
          )
      }
      let encoded_b = match av1_color_nonlinear(hdr_clamp01(bb), 13) {
        Some(value) => value
        None =>
          return Err(
            decode_error(InvalidData, "hdr/output", "sRGB encoding failed"),
          )
      }
      let destination = (y * color.width + x) * 4
      out[destination] = hdr_quantize(encoded_r)
      out[destination + 1] = hdr_quantize(encoded_g)
      out[destination + 2] = hdr_quantize(encoded_b)
      out[destination + 3] = hdr_alpha16(coverage, maximum)
    }
  }
  Ok(())
}

///|
fn av1_native_to_sdr_rgba16(
  color : Av1NativeFrame,
  alpha : Av1NativeFrame?,
  premultiplied : Bool,
  options : HdrToneMapOptions,
) -> Result[Image16, DecodeError] {
  if !hdr_options_valid(options) {
    return Err(
      decode_error(
        InvalidData,
        "hdr/options",
        "HDR options must be finite, peak/reference white must be 1..10000 nits with reference <= peak, and exposure must be within +/-16 stops",
      ),
    )
  }
  if !av1_native_pixels_valid(color) || (premultiplied && alpha is None) {
    return Err(
      decode_error(
        InvalidBuffer,
        "hdr/input",
        "Native color or alpha planes are malformed",
      ),
    )
  }
  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 Err(
        decode_error(
          InvalidBuffer,
          "hdr/alpha",
          "Alpha plane does not match the native color image",
        ),
      )
    }
  }
  if !hdr_matrix_metadata_valid(color) {
    return Err(
      decode_error(
        Unsupported,
        "hdr/color",
        "Color matrix, transfer, or primaries are unsupported for HDR conversion",
      ),
    )
  }
  if color.transfer_characteristics == 18 &&
    1.2 + 0.42 * @math.log10(options.peak_luminance_nits / 1000.0) <= 0.0 {
    return Err(
      decode_error(
        InvalidData,
        "hdr/options",
        "HLG peak must give a positive system gamma",
      ),
    )
  }
  let source_to_bt709 = if color.planes.length() == 1 {
    // Monochrome is scalar and does not depend on an RGB primary set.
    hdr_identity_matrix()
  } else {
    match hdr_source_to_bt709(color.color_primaries) {
      Some(value) => value
      None =>
        return Err(
          decode_error(
            Unsupported,
            "hdr/primaries",
            "No finite RGB-to-BT.709 gamut conversion is defined",
          ),
        )
    }
  }
  let scene_weights = match
    hdr_hlg_scene_weights(color.color_primaries, color.planes.length() == 1) {
    Some(value) => value
    None =>
      return Err(
        decode_error(
          Unsupported,
          "hdr/primaries",
          "No scene luminance weights for these primaries",
        ),
      )
  }
  let out = FixedArray::make(color.width * color.height * 4, (0 : UInt16))
  match
    hdr_write_sdr_rgba16(
      color, alpha, premultiplied, options, source_to_bt709, scene_weights, out,
    ) {
    Err(error) => Err(error)
    Ok(_) => Ok({ width: color.width, height: color.height, data: out, })
  }
}

///|
/// Convert an owned native AV1 frame to straight SDR RGBA16.  PQ samples are
/// interpreted as absolute luminance up to 10,000 nits; HLG samples use the
/// BT.2100 scene-to-display OOTF.  The conversion is performed directly from
/// native-depth planes and returns a newly owned buffer.
pub fn Av1NativeFrame::to_sdr_rgba16(
  self : Av1NativeFrame,
  options? : HdrToneMapOptions = HdrToneMapOptions::default(),
) -> Result[Image16, DecodeError] {
  av1_native_to_sdr_rgba16(self, None, false, options)
}

///|
/// Convert an AVIF native image to straight SDR RGBA16, preserving native
/// alpha precision and undoing color premultiplication before tone mapping.
pub fn AvifNativeImage::to_sdr_rgba16(
  self : AvifNativeImage,
  options? : HdrToneMapOptions = HdrToneMapOptions::default(),
) -> Result[Image16, DecodeError] {
  av1_native_to_sdr_rgba16(self.color, self.alpha, self.premultiplied, options)
}