use crate::DcpTargetGamut;
use super::xyz12_constants::{
INV_4095, OETF_POLY_COEFFS, OETF_POLY_DEGREE, OETF_POLY_SEG_BOUNDS, OETF_POLY_SEG_CENTERS,
OETF_POLY_SEGMENTS, SAMPLE_MASK, SMPTE428_INV_NORM, xyz_to_rgb_matrix,
};
#[cfg_attr(not(tarpaulin), inline(always))]
fn powf32(x: f32, y: f32) -> f32 {
#[cfg(feature = "std")]
{
f32::powf(x, y)
}
#[cfg(all(not(feature = "std"), feature = "alloc"))]
{
libm::powf(x, y)
}
}
#[cfg(all(test, feature = "std"))]
#[cfg_attr(not(tarpaulin), inline(always))]
fn powf64(x: f64, y: f64) -> f64 {
#[cfg(feature = "std")]
{
f64::powf(x, y)
}
#[cfg(all(not(feature = "std"), feature = "alloc"))]
{
libm::pow(x, y)
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn read_xyz12_sample<const BE: bool>(s: u16) -> u16 {
let raw = if BE { u16::from_be(s) } else { u16::from_le(s) };
(raw >> 4) & SAMPLE_MASK
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn smpte428_inverse_oetf(x_u12: u16) -> f32 {
let normalised = (x_u12 & SAMPLE_MASK) as f32 * INV_4095;
powf32(normalised, 2.6_f32) * SMPTE428_INV_NORM
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn matmul3_xyz_rgb(m: &[[f32; 3]; 3], xyz: [f32; 3]) -> [f32; 3] {
let [x, y, z] = xyz;
[
m[0][0] * x + m[0][1] * y + m[0][2] * z,
m[1][0] * x + m[1][1] * y + m[1][2] * z,
m[2][0] * x + m[2][1] * y + m[2][2] * z,
]
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn oetf_srgb(c: f32) -> f32 {
if c < 0.0031308_f32 {
return 12.92_f32 * c;
}
let mut seg_idx = 0_usize;
let mut i = OETF_POLY_SEGMENTS;
while i > 0 {
i -= 1;
if c >= OETF_POLY_SEG_BOUNDS[i] {
seg_idx = i;
break;
}
}
let center = OETF_POLY_SEG_CENTERS[seg_idx];
let dx = c - center;
let base = seg_idx * (OETF_POLY_DEGREE + 1);
let mut acc = 0.0_f32;
let mut k = OETF_POLY_DEGREE + 1;
while k > 0 {
k -= 1;
acc = acc * dx + OETF_POLY_COEFFS[base + k];
}
acc
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn narrow_unit_to_u8(c: f32) -> u8 {
let scaled = c.clamp(0.0_f32, 1.0_f32) * 255.0_f32 + 0.5_f32;
scaled.clamp(0.0_f32, 255.0_f32) as u8
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn narrow_unit_to_u16(c: f32) -> u16 {
let scaled = c.clamp(0.0_f32, 1.0_f32) * 65535.0_f32 + 0.5_f32;
scaled.clamp(0.0_f32, 65535.0_f32) as u16
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_pixel_to_rgb_linear<const BE: bool>(
m: &[[f32; 3]; 3],
triple: &[u16; 3],
) -> [f32; 3] {
let x = smpte428_inverse_oetf(read_xyz12_sample::<BE>(triple[0]));
let y = smpte428_inverse_oetf(read_xyz12_sample::<BE>(triple[1]));
let z = smpte428_inverse_oetf(read_xyz12_sample::<BE>(triple[2]));
matmul3_xyz_rgb(m, [x, y, z])
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_pixel_to_xyz_linear<const BE: bool>(triple: &[u16; 3]) -> [f32; 3] {
[
smpte428_inverse_oetf(read_xyz12_sample::<BE>(triple[0])),
smpte428_inverse_oetf(read_xyz12_sample::<BE>(triple[1])),
smpte428_inverse_oetf(read_xyz12_sample::<BE>(triple[2])),
]
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgb_row<const BE: bool>(
xyz: &[u16],
rgb_out: &mut [u8],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgb_out.len() >= width * 3, "rgb_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
for x in 0..width {
let i = x * 3;
let triple = [xyz[i], xyz[i + 1], xyz[i + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgb_out[i] = narrow_unit_to_u8(oetf_srgb(rgb_lin[0]));
rgb_out[i + 1] = narrow_unit_to_u8(oetf_srgb(rgb_lin[1]));
rgb_out[i + 2] = narrow_unit_to_u8(oetf_srgb(rgb_lin[2]));
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgba_row<const BE: bool>(
xyz: &[u16],
rgba_out: &mut [u8],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgba_out.len() >= width * 4, "rgba_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
for x in 0..width {
let xi = x * 3;
let oi = x * 4;
let triple = [xyz[xi], xyz[xi + 1], xyz[xi + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgba_out[oi] = narrow_unit_to_u8(oetf_srgb(rgb_lin[0]));
rgba_out[oi + 1] = narrow_unit_to_u8(oetf_srgb(rgb_lin[1]));
rgba_out[oi + 2] = narrow_unit_to_u8(oetf_srgb(rgb_lin[2]));
rgba_out[oi + 3] = 0xFF;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgb_u16_row<const BE: bool>(
xyz: &[u16],
rgb_out: &mut [u16],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgb_out.len() >= width * 3, "rgb_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
for x in 0..width {
let i = x * 3;
let triple = [xyz[i], xyz[i + 1], xyz[i + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgb_out[i] = narrow_unit_to_u16(oetf_srgb(rgb_lin[0]));
rgb_out[i + 1] = narrow_unit_to_u16(oetf_srgb(rgb_lin[1]));
rgb_out[i + 2] = narrow_unit_to_u16(oetf_srgb(rgb_lin[2]));
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgba_u16_row<const BE: bool>(
xyz: &[u16],
rgba_out: &mut [u16],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgba_out.len() >= width * 4, "rgba_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
for x in 0..width {
let xi = x * 3;
let oi = x * 4;
let triple = [xyz[xi], xyz[xi + 1], xyz[xi + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgba_out[oi] = narrow_unit_to_u16(oetf_srgb(rgb_lin[0]));
rgba_out[oi + 1] = narrow_unit_to_u16(oetf_srgb(rgb_lin[1]));
rgba_out[oi + 2] = narrow_unit_to_u16(oetf_srgb(rgb_lin[2]));
rgba_out[oi + 3] = 0xFFFF;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgb_f32_row<const BE: bool>(
xyz: &[u16],
rgb_out: &mut [f32],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgb_out.len() >= width * 3, "rgb_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
for x in 0..width {
let i = x * 3;
let triple = [xyz[i], xyz[i + 1], xyz[i + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgb_out[i] = rgb_lin[0];
rgb_out[i + 1] = rgb_lin[1];
rgb_out[i + 2] = rgb_lin[2];
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_xyz_f32_row<const BE: bool>(xyz: &[u16], xyz_out: &mut [f32], width: usize) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(xyz_out.len() >= width * 3, "xyz_out row too short");
for x in 0..width {
let i = x * 3;
let triple = [xyz[i], xyz[i + 1], xyz[i + 2]];
let xyz_lin = xyz12_pixel_to_xyz_linear::<BE>(&triple);
xyz_out[i] = xyz_lin[0];
xyz_out[i + 1] = xyz_lin[1];
xyz_out[i + 2] = xyz_lin[2];
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgb_f16_row<const BE: bool>(
xyz: &[u16],
rgb_out: &mut [half::f16],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgb_out.len() >= width * 3, "rgb_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
for x in 0..width {
let i = x * 3;
let triple = [xyz[i], xyz[i + 1], xyz[i + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgb_out[i] = half::f16::from_f32(oetf_srgb(rgb_lin[0]).clamp(0.0, 1.0));
rgb_out[i + 1] = half::f16::from_f32(oetf_srgb(rgb_lin[1]).clamp(0.0, 1.0));
rgb_out[i + 2] = half::f16::from_f32(oetf_srgb(rgb_lin[2]).clamp(0.0, 1.0));
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_to_rgba_f16_row<const BE: bool>(
xyz: &[u16],
rgba_out: &mut [half::f16],
width: usize,
target_gamut: DcpTargetGamut,
) {
debug_assert!(xyz.len() >= width * 3, "xyz row too short");
debug_assert!(rgba_out.len() >= width * 4, "rgba_out row too short");
let m = xyz_to_rgb_matrix(target_gamut);
let one_f16 = half::f16::from_f32(1.0);
for x in 0..width {
let xi = x * 3;
let oi = x * 4;
let triple = [xyz[xi], xyz[xi + 1], xyz[xi + 2]];
let rgb_lin = xyz12_pixel_to_rgb_linear::<BE>(&m, &triple);
rgba_out[oi] = half::f16::from_f32(oetf_srgb(rgb_lin[0]).clamp(0.0, 1.0));
rgba_out[oi + 1] = half::f16::from_f32(oetf_srgb(rgb_lin[1]).clamp(0.0, 1.0));
rgba_out[oi + 2] = half::f16::from_f32(oetf_srgb(rgb_lin[2]).clamp(0.0, 1.0));
rgba_out[oi + 3] = one_f16;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_rgb_to_luma_row(
rgb: &[u8],
luma_out: &mut [u8],
width: usize,
luma_q15: (i32, i32, i32),
) {
debug_assert!(rgb.len() >= width * 3, "rgb row too short");
debug_assert!(luma_out.len() >= width, "luma row too short");
let (k_r, k_g, k_b) = luma_q15;
const RND: i32 = 1 << 14;
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let y = (k_r * r + k_g * g + k_b * b + RND) >> 15;
luma_out[x] = y.clamp(0, 255) as u8;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xyz12_rgb_to_luma_u16_row(
rgb: &[u8],
luma_out: &mut [u16],
width: usize,
luma_q15: (i32, i32, i32),
) {
debug_assert!(rgb.len() >= width * 3, "rgb row too short");
debug_assert!(luma_out.len() >= width, "luma row too short");
let (k_r, k_g, k_b) = luma_q15;
const RND: i32 = 1 << 14;
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let y = (k_r * r + k_g * g + k_b * b + RND) >> 15;
luma_out[x] = y.clamp(0, 255) as u16;
}
}
#[cfg(all(test, feature = "std"))]
mod tests;