use crate::math::MathState;
pub trait IsLinearEncoding {}
pub trait IsSceneReferred {}
pub trait IsDisplayReferred {}
pub trait TransferFunction: 'static {
const ENCODED_MIN: [f32; 3];
const ENCODED_MAX: [f32; 3];
fn decode<M: MathState>(encoded: [f32; 3]) -> [f32; 3];
fn encode<M: MathState>(linear: [f32; 3]) -> [f32; 3];
}
#[inline(always)]
fn map(v: [f32; 3], f: impl Fn(f32) -> f32) -> [f32; 3] {
[f(v[0]), f(v[1]), f(v[2])]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LinearTf;
impl IsLinearEncoding for LinearTf {}
impl IsSceneReferred for LinearTf {}
impl TransferFunction for LinearTf {
const ENCODED_MIN: [f32; 3] = [f32::NEG_INFINITY; 3];
const ENCODED_MAX: [f32; 3] = [f32::INFINITY; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
v
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
v
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SrgbTf;
const SRGB_ALPHA: f32 = 1.055;
const SRGB_GAMMA: f32 = 2.4;
const SRGB_LINEAR_SLOPE: f32 = 12.92;
const SRGB_LINEAR_THRESHOLD: f32 = 0.0031308;
const SRGB_ENCODED_THRESHOLD: f32 = 0.04045;
impl TransferFunction for SrgbTf {
const ENCODED_MIN: [f32; 3] = [0.0; 3];
const ENCODED_MAX: [f32; 3] = [1.0; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
if x <= SRGB_ENCODED_THRESHOLD {
x / SRGB_LINEAR_SLOPE
} else {
M::powf((x + (SRGB_ALPHA - 1.0)) / SRGB_ALPHA, SRGB_GAMMA)
}
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let abs = x.abs();
sign * if abs <= SRGB_LINEAR_THRESHOLD {
abs * SRGB_LINEAR_SLOPE
} else {
SRGB_ALPHA * M::powf(abs, 1.0 / SRGB_GAMMA) - (SRGB_ALPHA - 1.0)
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Rec709Tf;
#[allow(clippy::excessive_precision)]
const REC709_ALPHA: f32 = 1.09929682680944;
const REC709_POWER: f32 = 0.45;
const REC709_LINEAR_SLOPE: f32 = 4.5;
#[allow(clippy::excessive_precision)]
const REC709_LINEAR_THRESHOLD: f32 = 0.018053968510807;
#[allow(clippy::excessive_precision)]
const REC709_ENCODED_THRESHOLD: f32 = 0.081242858298635;
impl TransferFunction for Rec709Tf {
const ENCODED_MIN: [f32; 3] = [0.0; 3];
const ENCODED_MAX: [f32; 3] = [1.0; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
if x <= REC709_ENCODED_THRESHOLD {
x / REC709_LINEAR_SLOPE
} else {
M::powf((x + (REC709_ALPHA - 1.0)) / REC709_ALPHA, 1.0 / REC709_POWER)
}
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let abs = x.abs();
sign * if abs <= REC709_LINEAR_THRESHOLD {
abs * REC709_LINEAR_SLOPE
} else {
REC709_ALPHA * M::powf(abs, REC709_POWER) - (REC709_ALPHA - 1.0)
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PqTf;
#[allow(clippy::excessive_precision)]
const PQ_M1: f32 = 0.1593017578125; const PQ_M2: f32 = 78.84375; const PQ_C1: f32 = 0.8359375; #[allow(clippy::excessive_precision)]
const PQ_C2: f32 = 18.8515625; const PQ_C3: f32 = 18.6875;
impl TransferFunction for PqTf {
const ENCODED_MIN: [f32; 3] = [0.0; 3];
const ENCODED_MAX: [f32; 3] = [1.0; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let v_m2 = M::powf(x.max(0.0), 1.0 / PQ_M2);
let num = (v_m2 - PQ_C1).max(0.0);
let den = PQ_C2 - PQ_C3 * v_m2;
M::powf(num / den, 1.0 / PQ_M1)
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let y_m1 = M::powf(x.max(0.0), PQ_M1);
M::powf((PQ_C1 + PQ_C2 * y_m1) / (1.0 + PQ_C3 * y_m1), PQ_M2)
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct HlgTf;
const HLG_A: f32 = 0.17883277;
const HLG_B: f32 = 0.28466892;
const HLG_C: f32 = 0.559_910_7;
const HLG_LINEAR_THRESHOLD: f32 = 1.0 / 12.0;
const HLG_ENCODED_THRESHOLD: f32 = 0.5;
impl TransferFunction for HlgTf {
const ENCODED_MIN: [f32; 3] = [0.0; 3];
const ENCODED_MAX: [f32; 3] = [1.0; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
if x.abs() <= HLG_ENCODED_THRESHOLD {
x * x.abs() / 3.0
} else {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
sign * (M::exp((x.abs() - HLG_C) / HLG_A) + HLG_B) / 12.0
}
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let abs = x.abs();
let sign = if x < 0.0 { -1.0 } else { 1.0 };
if abs <= HLG_LINEAR_THRESHOLD {
sign * M::sqrt(3.0 * abs)
} else {
sign * (HLG_A * M::ln((12.0 * abs - HLG_B).max(f32::MIN_POSITIVE)) + HLG_C)
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ProPhotoTf;
const PRO_PHOTO_GAMMA: f32 = 1.8;
const PRO_PHOTO_LINEAR_SLOPE: f32 = 16.0;
const PRO_PHOTO_LINEAR_THRESHOLD: f32 = 1.0 / 512.0;
const PRO_PHOTO_ENCODED_THRESHOLD: f32 = 1.0 / 32.0;
impl TransferFunction for ProPhotoTf {
const ENCODED_MIN: [f32; 3] = [0.0; 3];
const ENCODED_MAX: [f32; 3] = [1.0; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let abs = x.abs();
sign * if abs <= PRO_PHOTO_ENCODED_THRESHOLD {
abs / PRO_PHOTO_LINEAR_SLOPE
} else {
M::powf(abs, PRO_PHOTO_GAMMA)
}
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let abs = x.abs();
sign * if abs <= PRO_PHOTO_LINEAR_THRESHOLD {
abs * PRO_PHOTO_LINEAR_SLOPE
} else {
M::powf(abs, 1.0 / PRO_PHOTO_GAMMA)
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AcesCcTf;
const ACESCC_CUT1: f32 = -0.30136986; const ACESCC_LOG_SCALE: f32 = 17.52;
const ACESCC_LOG_OFFSET: f32 = 9.72;
const LOG2_RECIP: f32 = 1.0 / core::f32::consts::LN_2;
impl TransferFunction for AcesCcTf {
const ENCODED_MIN: [f32; 3] = [-0.3584; 3]; const ENCODED_MAX: [f32; 3] = [1.4679; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
let linear = x * ACESCC_LOG_SCALE - ACESCC_LOG_OFFSET;
if x < ACESCC_CUT1 {
(M::exp(linear * core::f32::consts::LN_2) - 2.0_f32.powi(-16)) * 2.0
} else {
M::exp(linear * core::f32::consts::LN_2)
}
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
if x < 0.0 {
(M::ln(2.0_f32.powi(-16)) * LOG2_RECIP + ACESCC_LOG_OFFSET) / ACESCC_LOG_SCALE
} else if x < 2.0_f32.powi(-15) {
(M::ln(2.0_f32.powi(-16) + x * 0.5) * LOG2_RECIP + ACESCC_LOG_OFFSET) / ACESCC_LOG_SCALE
} else {
(M::ln(x) * LOG2_RECIP + ACESCC_LOG_OFFSET) / ACESCC_LOG_SCALE
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AcesCctTf;
const ACESCCT_X_BRK: f32 = 0.0078125; const ACESCCT_Y_BRK: f32 = 0.155_251_15; const ACESCCT_A: f32 = 10.540_237; const ACESCCT_B: f32 = 0.072_905_53; const ACESCCT_LOG_SCALE: f32 = 17.52;
const ACESCCT_LOG_OFFSET: f32 = 9.72;
impl TransferFunction for AcesCctTf {
const ENCODED_MIN: [f32; 3] = [ACESCCT_B; 3]; const ENCODED_MAX: [f32; 3] = [1.4679; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
if x <= ACESCCT_Y_BRK {
(x - ACESCCT_B) / ACESCCT_A
} else {
M::exp((x * ACESCCT_LOG_SCALE - ACESCCT_LOG_OFFSET) * core::f32::consts::LN_2)
}
})
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| {
if x <= ACESCCT_X_BRK {
ACESCCT_A * x.max(0.0) + ACESCCT_B
} else {
(M::ln(x) * LOG2_RECIP + ACESCCT_LOG_OFFSET) / ACESCCT_LOG_SCALE
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DciP3Tf;
const DCI_P3_GAMMA: f32 = 2.6;
impl TransferFunction for DciP3Tf {
const ENCODED_MIN: [f32; 3] = [0.0; 3];
const ENCODED_MAX: [f32; 3] = [1.0; 3];
#[inline(always)]
fn decode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| M::powf(x.max(0.0), DCI_P3_GAMMA))
}
#[inline(always)]
fn encode<M: MathState>(v: [f32; 3]) -> [f32; 3] {
map(v, |x| M::powf(x.max(0.0), 1.0 / DCI_P3_GAMMA))
}
}
impl IsSceneReferred for HlgTf {}
impl IsSceneReferred for AcesCcTf {}
impl IsSceneReferred for AcesCctTf {}
impl IsDisplayReferred for SrgbTf {}
impl IsDisplayReferred for Rec709Tf {}
impl IsDisplayReferred for PqTf {}
impl IsDisplayReferred for ProPhotoTf {}
impl IsDisplayReferred for DciP3Tf {}