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!();