Skip to main content

ph_color/
color.rs

1//! Typed color triple.
2
3use core::marker::PhantomData;
4
5use crate::encoding::{Encoded, Encoding, Linear};
6use crate::fixed::Q0_16;
7use crate::space::{ColorSpace, Perceptual};
8
9#[cfg(feature = "f32")]
10pub use crate::color_f32::ColorF32;
11
12/// Three [`Q0_16`] channels tagged with space and encoding.
13pub struct Color<S: ColorSpace, E: Encoding> {
14    /// Channel values.
15    pub ch: [Q0_16; 3],
16    _pd: PhantomData<fn() -> (S, E)>,
17}
18
19impl<S: ColorSpace, E: Encoding> Copy for Color<S, E> {}
20
21impl<S: ColorSpace, E: Encoding> Clone for Color<S, E> {
22    fn clone(&self) -> Self {
23        *self
24    }
25}
26
27impl<S: ColorSpace, E: Encoding> PartialEq for Color<S, E> {
28    fn eq(&self, other: &Self) -> bool {
29        self.ch == other.ch
30    }
31}
32
33impl<S: ColorSpace, E: Encoding> Eq for Color<S, E> {}
34
35impl<S: ColorSpace, E: Encoding> core::fmt::Debug for Color<S, E> {
36    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37        f.debug_struct("Color").field("ch", &self.ch).finish()
38    }
39}
40
41impl<S: ColorSpace, E: Encoding> Color<S, E> {
42    /// Construct from three [`Q0_16`] channels.
43    #[must_use]
44    pub const fn new(ch: [Q0_16; 3]) -> Self {
45        Self {
46            ch,
47            _pd: PhantomData,
48        }
49    }
50
51    /// Convert channels to unit `f32` (`feature = "f32"`).
52    #[cfg(feature = "f32")]
53    #[must_use]
54    pub const fn to_f32(self) -> crate::ColorF32<S, E> {
55        let [c0, c1, c2] = self.ch;
56        crate::ColorF32::new([c0.to_f32(), c1.to_f32(), c2.to_f32()])
57    }
58}
59
60impl<S: ColorSpace> Color<S, Linear> {
61    /// Encode with a same-space transfer. Numeric LUT apply is a later issue.
62    #[must_use]
63    pub const fn encode(self) -> Color<S, Encoded> {
64        Color::new(self.ch)
65    }
66
67    /// Linear interpolation between two linear colors of the same space.
68    ///
69    /// `t` is [`Q0_16`]: `0` returns `self`, `65535` returns `other`.
70    #[must_use]
71    pub const fn lerp(self, other: Self, t: Q0_16) -> Self {
72        let [a0, a1, a2] = self.ch;
73        let [b0, b1, b2] = other.ch;
74        Color::new([
75            crate::arith::lerp_q0_16(a0, b0, t),
76            crate::arith::lerp_q0_16(a1, b1, t),
77            crate::arith::lerp_q0_16(a2, b2, t),
78        ])
79    }
80}
81
82impl<S: ColorSpace> Color<S, Encoded> {
83    /// Decode with a same-space transfer. Numeric LUT apply is a later issue.
84    #[must_use]
85    pub const fn decode(self) -> Color<S, Linear> {
86        Color::new(self.ch)
87    }
88}
89
90impl<S: ColorSpace + Perceptual> Color<S, Encoded> {
91    /// Interpolate encoded values only when the space is [`Perceptual`].
92    #[must_use]
93    pub const fn lerp(self, other: Self, t: Q0_16) -> Self {
94        let [a0, a1, a2] = self.ch;
95        let [b0, b1, b2] = other.ch;
96        Color::new([
97            crate::arith::lerp_q0_16(a0, b0, t),
98            crate::arith::lerp_q0_16(a1, b1, t),
99            crate::arith::lerp_q0_16(a2, b2, t),
100        ])
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::space::Srgb;
108
109    #[test]
110    fn constructs_linear_and_encoded_srgb() {
111        let lin = Color::<Srgb, Linear>::new(Q0_16::array_from_raw([1, 2, 3]));
112        let enc = Color::<Srgb, Encoded>::new(Q0_16::array_from_raw([4, 5, 6]));
113        assert_eq!(lin.ch, Q0_16::array_from_raw([1, 2, 3]));
114        assert_eq!(enc.ch, Q0_16::array_from_raw([4, 5, 6]));
115        let _ = lin.encode();
116        let _ = enc.decode();
117    }
118
119    fn assert_send_sync<T: Send + Sync>() {}
120
121    #[test]
122    fn color_matrix_gain_are_send_sync() {
123        assert_send_sync::<Color<Srgb, Linear>>();
124        assert_send_sync::<Color<Srgb, Encoded>>();
125        assert_send_sync::<crate::matrix::Matrix3<Srgb, Srgb>>();
126        assert_send_sync::<crate::gain::Gain<Srgb>>();
127    }
128}