use crate::{Float, is};
#[doc = crate::_tags!(color)]
#[doc = crate::_doc_location!("media/visual/color")]
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Gamma<T> {
pub exp: T,
}
impl<T> Gamma<T> {
pub const fn new(gamma: T) -> Self {
Self { exp: gamma }
}
}
macro_rules! impl_gamma {
() => { impl_gamma![gamma f32, f64]; };
( gamma $($T:ty),+) => { $( impl_gamma![@gamma $T]; )+ };
(@gamma $T:ty) => {
impl Gamma<$T> {
#[cfg(feature = "std")]
#[doc = _DOC_GAMMA_ENCODE!()]
pub fn encode(self, v: $T) -> $T { v.powf(self.exp.recip()) }
#[doc = _DOC_GAMMA_ENCODE!()]
pub const fn const_encode(self, v: $T) -> $T {
let terms = Float(v).exp_series_terms();
Float(v).powf_series(self.exp.recip(), terms).0
}
#[cfg(feature = "std")]
#[doc = _DOC_GAMMA_ENCODE!()]
pub fn decode(self, v: $T) -> $T { v.powf(self.exp) }
#[doc = _DOC_GAMMA_ENCODE!()]
pub const fn const_decode(self, v: $T) -> $T {
let terms = Float(v).exp_series_terms();
Float(v).powf_series(self.exp, terms).0
}
#[cfg(feature = "std")]
#[doc = _DOC_GAMMA_ENCODE_SRGB!()]
pub fn encode_srgb(self, v: $T) -> $T {
is![v <= 0.003_130_8, 12.92 * v, 1.055 * v.powf(self.exp.recip()) - 0.055]
}
#[doc = _DOC_GAMMA_ENCODE_SRGB!()]
pub const fn const_encode_srgb(self, v: $T) -> $T {
if v <= 0.003_130_8 { 12.92 * v }
else {
let terms = Float(v).exp_series_terms();
1.055 * Float(v).powf_series(self.exp.recip(), terms).0 - 0.055
}
}
#[cfg(feature = "std")]
#[doc = _DOC_GAMMA_DECODE_SRGB!()]
pub fn decode_srgb(self, v: $T) -> $T {
is![v <= 0.040_45, v / 12.92, ((v + 0.055) / (1.055)).powf(self.exp)]
}
#[doc = _DOC_GAMMA_DECODE_SRGB!()]
pub const fn const_decode_srgb(self, v: $T) -> $T {
if v <= 0.040_45 { v / 12.92 } else {
let terms = Float(v).exp_series_terms();
Float((v + 0.055) / (1.055)).powf_series(self.exp, terms).0
}
}
}
impl Gamma<$T> {
pub const SRGB: [$T; 3] = Self::REC_709;
pub const SRGB_GAMMA: $T = 2.2;
pub const SRGB_LINEAR_THRESHOLD: $T = 0.003_130_8;
pub const REC_709: [$T; 3] = [0.212_639, 0.715_169, 0.072_192];
pub const REC_601: [$T; 3] = [0.299, 0.587, 0.114];
pub const REC_2020: [$T; 3] = [0.2627, 0.6780, 0.0593];
pub const REC_1886_GAMMA: $T = 2.4;
pub const CIE_E: $T = 216.0 / 24_389.0;
pub const CIE_K: $T = 24_389.0 / 27.0;
pub const fn compute_luma(rgb: [$T; 3], weights: [$T; 3]) -> $T {
let [r, g, b] = rgb;
let [kr, kg, kb] = weights;
kr * r + kg * g + kb * b
}
pub const fn luma_srgb(rgb: [$T; 3]) -> $T {
Self::compute_luma(rgb, Self::SRGB)
}
pub const fn luma_rec_709(rgb: [$T; 3]) -> $T {
Self::compute_luma(rgb, Self::REC_709)
}
pub const fn luma_rec_601(rgb: [$T; 3]) -> $T {
Self::compute_luma(rgb, Self::REC_601)
}
pub const fn luma_rec_2020(rgb: [$T; 3]) -> $T {
Self::compute_luma(rgb, Self::REC_2020)
}
pub const fn const_luminance_to_lightness(y: $T) -> $T {
is![y <= Self::CIE_E, Self::CIE_K * y, 116.0 * Float(y).cbrt_nr().0 - 16.0]
}
pub const fn const_lightness_to_luminance(l_star: $T) -> $T {
if l_star <= 8.0 { l_star / Self::CIE_K }
else { Float((l_star + 16.0) / 116.0).const_powi(3).0 }
}
}
};
}
impl_gamma!();
crate::CONST! {
_DOC_GAMMA_ENCODE = "Encodes the given linear `v`alue using this gamma: $v^{(1/γ)}$.\n\n
Performs basic gamma encoding (power-law).";
_DOC_GAMMA_DECODE = "Decodes the given gamma-encoded `v`alue using this gamma: $v^γ$.\n\n
Performs basic gamma decoding (power-law inverse).";
_DOC_GAMMA_ENCODE_SRGB = r#"Encodes the given `v`alue using the sRGB transfer function.
Applies a piecewise curve based on this gamma (typically 2.4).
# Algorithm
$$
f_\text{encode}(c) = \begin{cases}
12.92c, & \text{if } c <= 0.0031308 \cr
1.055c^{1/\gamma} - 0.055, & \text{if } c > 0.0031308
\end{cases}
$$"#;
_DOC_GAMMA_DECODE_SRGB = r#"Decodes the given `v`alue using the sRGB inverse transfer function.
Applies the inverse piecewise curve based on this gamma (typically 2.4).
# Algorithm
$$
\notag f_\text{decode}(c) = \begin{cases}
c / 12.92, & \normalsize\text{if } c <= 0.04045 \cr
\left(\Large\frac{c + 0.055}{1.055}\right)^\gamma
& \normalsize \text{if } c > 0.04045
\end{cases}
$$"#;
}