devela/media/color/
luminance.rs

1// devela::media::color::luminance
2//
3//! Defines the [`Lum`] type and aliases:
4//! [`Luminance`], [`Lightness`], [`Luma`], `LinearLightness`.
5//
6
7use super::*; // IMPROVE: specify
8use crate::NumConst;
9#[allow(unused_imports)]
10use crate::{ExtFloat, Float};
11
12/// A generic luminance-like component.
13///
14/// Represents either physical luminance, gamma-encoded luma, or perceptual lightness,
15/// depending on the `LINEAR` and `LIGHTNESS` flags.
16///
17/// Variants (in order of typical usage):
18/// - [`Luminance<T>`]:        linear, physical
19/// - [`Lightness<T>`]:        non-linear, perceptual
20/// - [`Luma<T>`]:             non-linear, technical
21/// - [`LinearLightness<T>`]:  linear, perceptual (experimental hybrid)
22#[must_use]
23#[repr(C)]
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
25pub struct Lum<T, const LINEAR: bool = true, const LIGHTNESS: bool = false> {
26    /// The luminance-like channel value.
27    pub c: [T; 1],
28}
29
30/* aliases*/
31
32/// Physical [luminance].
33///
34/// Linear light intensity, measured in cd/m² or normalized to [0.0, 1.0].
35///
36/// [luminance]: https://en.wikipedia.org/wiki/Luminance
37pub type Luminance<T> = Lum<T, true, false>;
38
39/// Perceptual [lightness] (L*).
40///
41/// Non-linear encoding of luminance,
42/// normalized to [0.0, 1.0] for floats or 0..=MAX for integers.
43///
44/// [lightness]: https://en.wikipedia.org/wiki/Lightness
45pub type Lightness<T> = Lum<T, false, true>;
46
47/// Gamma-encoded [luma] (Y′).
48///
49/// A non-linear approximation of luminance, typically used in video systems.
50///
51/// [luma]: https://en.wikipedia.org/wiki/Luma_(video)
52pub type Luma<T> = Lum<T, false, false>;
53
54/// Linearized perceptual lightness (L* in linear space).
55///
56/// Use cases include:
57/// - Combining linear luminance (for precise computations)
58///   and perceptual lightness (for display scaling).
59/// - Tone mapping in HDR imaging, where linear data is scaled to a perceptual range.
60/// - Representing raw radiometric data (e.g., watts/sr/m²) prior to photometric weighting (CIE Y).
61pub type LinearLightness<T> = Lum<T, true, true>;
62
63/// ## Args
64/// `$T`   : the type used to represent the main value. (u8, u16, f32, f64)
65/// `$f`   : associated floating-point type for operations. (f32|f64)
66/// `$BITS`: the number of bits of each inner component.
67/// `$INT` : a boolean indicating whether the components are integers.
68macro_rules! impl_lum {
69    () => {
70        impl_lum![common u8|f32, u16|f32];
71        impl_lum![common f32|f32];
72        impl_lum![common f64|f64];
73
74        impl_lum![lumina u8|f32:8+true, u16|f32:16+true];
75        impl_lum![lumina f32|f32:32+false];
76        impl_lum![lumina f64|f64:64+false];
77
78        impl_lum![light u8|f32:8+true, u16|f32:16+true];
79        impl_lum![light f32|f32:32+false];
80        impl_lum![light f64|f64:64+false];
81
82        impl_lum![luma u8|f32:8+true, u16|f32:16+true];
83        impl_lum![luma f32|f32:32+false];
84        impl_lum![luma f64|f64:64+false];
85
86        impl_lum![lumi_light u8|f32:8+true, u16|f32:16+true];
87        impl_lum![lumi_light f32|f32:32+false];
88        impl_lum![lumi_light f64|f64:64+false];
89    };
90    ( // Methods common to all types.
91      common  $( $T:ty | $f:ty ),+) => { $( impl_lum![@common $T|$f]; )+ };
92    (@common     $T:ty | $f:ty    ) => {
93        impl<const LINEAR: bool, const LIGHTNESS: bool> Lum<$T, LINEAR, LIGHTNESS> {
94            /// New `Luminance` with the given channel.
95            pub const fn new(c: $T) -> Self { Self { c: [c] } }
96
97            /// Returns the raw channel value, regardless of interpretation.
98            ///
99            /// Prefer type-specific methods like [`luminance()`](Luminance::luminance) or
100            /// [`lightness()`](Lightness::lightness) where possible.
101            pub const fn l(self) -> $T { self.c[0] }
102
103            /// Returns a mutable reference to the raw channel value.
104            pub const fn l_mut(&mut self) -> &mut $T { &mut self.c[0] }
105
106            /// Converts an `Rgb` into unweighted brightness by averaging the R'G'B' components.
107            ///
108            /// May be useful for quick approximations.
109            /// Not correct for perceptual brightness (luma) or physical light (luminance).
110            pub const fn brightness_from_rgb(rgb: Rgb<$T>) -> Self {
111                Self::new((rgb.r() + rgb.g() + rgb.b()) / <$T as NumConst>::NUM_THREE.unwrap())
112            }
113        }
114    };
115    ( // Methods for Luminance: (linear non-lightness)
116      lumina $( $T:ty | $f:ty : $BITS:literal + $INT:literal),+) => {
117        $( impl_lum![@lumina $T|$f : $BITS+$INT]; )+
118    };
119    (@lumina    $T:ty | $f:ty : $BITS:literal + $INT:literal   ) => {
120        impl_color![lum: $T, $BITS, $INT, true, false];
121
122        impl Luminance<$T> {
123            /// Returns the **linear luminance** (physical light intensity, Y).
124            ///
125            /// Measured in cd/m² (floats) or normalized (integers).
126            pub const fn luminance(self) -> $T { self.c[0] }
127            /// Returns a mutable reference to the **linear luminance**.
128            pub const fn luminance_mut(&mut self) -> &mut $T { &mut self.c[0] }
129
130            /* gamma conversion */
131
132            // WIP TODO RE-DESIGN
133
134            /// Converts to gamma-encoded luma using the specified gamma value.
135            ///
136            /// Uses standard power-law gamma encoding. For sRGB, prefer
137            /// [`to_luma_srgb`][Self::to_luma_srgb].
138            ///
139            /// Typical gamma values:
140            /// - 2.2 for sRGB/Rec.709
141            /// - 2.4 for Rec.1886
142            pub const fn to_luma_with_gamma(self, gamma: $f) -> Luma<$T> {
143                let linear = self.l() as $f;
144                let encoded = Gamma::<$f>::new(gamma).const_encode(linear);
145                Luma::<$T>::new(encoded as $T) // FIXME: normalize right.
146            }
147
148            // /// Converts to sRGB/Rec.709 luma using the standard sRGB transfer function.
149            // ///
150            // /// This matches how displays expect luma values, using the piecewise sRGB curve.
151            // pub const fn to_luma_srgb(self) -> Luma<$T> {
152            //     let linear = cast::$f::from(self.c[0]);
153            //     let encoded = Gamma::new(Gamma::<$f>::SRGB_GAMMA).const_encode_srgb(linear);
154            //     Luma::new(cast::$T::from(encoded))
155            // }
156
157            // /// Converts to perceptual lightness (CIE L*) normalized to [0, 1].
158            // ///
159            // /// The CIE lightness scale (L*) is designed to match human perception:
160            // /// - 0.0 is black
161            // /// - 0.5 is middle gray (≈18% reflectance)
162            // /// - 1.0 is white
163            // pub const fn to_lightness(self) -> Lightness<$T> {
164            //     let y = self.l() as $f;
165            //     let l_star = Gamma::<$f>::luminance_to_lightness(y);
166            //     Lightness::new((l_star / 100.0) as $T) // Normalize to [0,1]
167            // }
168        }
169    };
170    ( // Methods for Lightness: (non-linear, lightness)
171      light $( $T:ty | $f:ty : $BITS:literal + $INT:literal),+) => {
172        $( impl_lum![@light $T|$f : $BITS+$INT]; )+
173    };
174    (@light    $T:ty | $f:ty : $BITS:literal + $INT:literal   ) => {
175        impl_color![lum: $T, $BITS, $INT, false, true];
176
177        impl Lightness<$T> {
178            /// Returns the **perceptual lightness** (CIE L\*).
179            ///
180            /// Normalized to `0.0..=1.0` (floats) or `0..=MAX` (integers).
181            pub const fn lightness(self) -> $T { self.c[0] }
182            /// Returns a mutable reference to the **perceptual lightness**.
183            pub const fn lightness_mut(&mut self) -> &mut $T { &mut self.c[0] }
184
185            /* gamma conversion */
186
187            // /// TODO
188            // pub const fn to_luminance() -> Lightness<$T> {
189            //     todo![]
190            // }
191            // /// TODO
192            // pub const fn to_luma() -> Luma<$T> {
193            //     todo![]
194            // }
195        }
196    };
197    ( // Methods for Luma: (non-linear, non-lightness)
198      luma $( $T:ty | $f:ty : $BITS:literal + $INT:literal),+) => {
199        $( impl_lum![@luma $T|$f : $BITS+$INT]; )+
200    };
201    (@luma    $T:ty | $f:ty : $BITS:literal + $INT:literal    ) => {
202        impl_color![lum: $T, $BITS, $INT, false, false];
203
204        impl Luma<$T> {
205            /// Returns the **gamma-encoded luma** (non-linear Y′).
206            ///
207            /// Compatible with sRGB/Rec. 709 for display.
208            pub const fn lightness(self) -> $T { self.c[0] }
209            /// Returns a mutable reference to the **gamma-encoded luma**.
210            pub const fn lightness_mut(&mut self) -> &mut $T { &mut self.c[0] }
211
212            /* gamma conversion */
213
214            // /// TODO
215            // pub const fn to_luminance() -> Lightness<$T> {
216            //     todo![]
217            // }
218            // /// TODO
219            // pub const fn to_lightness() -> Luma<$T> {
220            //     todo![]
221            // }
222        }
223    };
224    ( // Methods for LinearLuminance: (linear, lightness)
225      lumi_light $( $T:ty | $f:ty : $BITS:literal + $INT:literal),+) => {
226          $( impl_lum![@lumi_light $T|$f : $BITS+$INT]; )+
227    };
228    (@lumi_light    $T:ty | $f:ty : $BITS:literal + $INT:literal   ) => {
229        impl_color![lum: $T, $BITS, $INT, true, true];
230
231        impl LinearLightness<$T> {
232            /// Returns the **linear-light perceptual** value (experimental).
233            ///
234            /// Used for hybrid workflows like HDR tonemapping.
235            pub const fn linear_lightness(self) -> $T { self.c[0] }
236            /// Returns a mutable reference to the **linear-light perceptual** value.
237            pub const fn linear_lightness_mut(&mut self) -> &mut $T { &mut self.c[0] }
238
239            /* gamma conversion */
240
241            // /// TODO
242            // pub const fn to_luminance() -> Lightness<$T> {
243            //     todo![]
244            // }
245            // /// TODO
246            // pub const fn to_luma() -> Luma<$T> {
247            //     todo![]
248            // }
249        }
250    };
251}
252use impl_lum;
253impl_lum!();