Skip to main content

optic_color/
channels.rs

1use crate::{RGB, RGBA};
2
3/// Trait for types whose channels can be accessed as a fixed-size float array.
4///
5/// This is the foundation for channel arithmetic in the color system.
6/// Implementations exist for [`RGBA`] (4 channels) and [`RGB`] (3 channels).
7///
8/// The trait enables generic channel-wise operations:
9///
10/// ```
11/// use optic_color::*;
12///
13/// fn half_brightness<T: ChannelArray<N>, const N: usize>(c: T) -> T {
14///     channel_mul_scalar(c, 0.5)
15/// }
16/// ```
17///
18/// See also: [`channel_lerp`], [`channel_add`], [`channel_mul`].
19pub trait ChannelArray<const N: usize>: Copy {
20    /// Convert to a float array of length N.
21    fn to_array(self) -> [f32; N];
22    /// Construct from a float array of length N.
23    fn from_array(arr: [f32; N]) -> Self;
24}
25
26impl ChannelArray<4> for RGBA {
27    fn to_array(self) -> [f32; 4] { [self.0, self.1, self.2, self.3] }
28    fn from_array(a: [f32; 4]) -> Self { RGBA(a[0], a[1], a[2], a[3]) }
29}
30
31impl ChannelArray<3> for RGB {
32    fn to_array(self) -> [f32; 3] { [self.0, self.1, self.2] }
33    fn from_array(a: [f32; 3]) -> Self { RGB(a[0], a[1], a[2]) }
34}
35
36/// Linearly interpolate between two colors channel by channel.
37///
38/// `t` is clamped to 0..1.
39///
40/// ```
41/// use optic_color::*;
42///
43/// let mid = channel_lerp(RED, BLUE, 0.5);
44/// ```
45pub fn channel_lerp<T: ChannelArray<N>, const N: usize>(a: T, b: T, t: f32) -> T {
46    let (a, b) = (a.to_array(), b.to_array());
47    let t = t.clamp(0.0, 1.0);
48    let mut out = [0.0f32; N];
49    for i in 0..N { out[i] = a[i] + (b[i] - a[i]) * t; }
50    T::from_array(out)
51}
52
53/// Channel-wise addition of two colors.
54pub fn channel_add<T: ChannelArray<N>, const N: usize>(a: T, b: T) -> T {
55    let (a, b) = (a.to_array(), b.to_array());
56    let mut out = [0.0f32; N];
57    for i in 0..N { out[i] = a[i] + b[i]; }
58    T::from_array(out)
59}
60
61/// Channel-wise subtraction of two colors.
62pub fn channel_sub<T: ChannelArray<N>, const N: usize>(a: T, b: T) -> T {
63    let (a, b) = (a.to_array(), b.to_array());
64    let mut out = [0.0f32; N];
65    for i in 0..N { out[i] = a[i] - b[i]; }
66    T::from_array(out)
67}
68
69/// Channel-wise multiplication of two colors.
70pub fn channel_mul<T: ChannelArray<N>, const N: usize>(a: T, b: T) -> T {
71    let (a, b) = (a.to_array(), b.to_array());
72    let mut out = [0.0f32; N];
73    for i in 0..N { out[i] = a[i] * b[i]; }
74    T::from_array(out)
75}
76
77/// Multiply all channels by a scalar.
78pub fn channel_mul_scalar<T: ChannelArray<N>, const N: usize>(a: T, s: f32) -> T {
79    let a = a.to_array();
80    let mut out = [0.0f32; N];
81    for i in 0..N { out[i] = a[i] * s; }
82    T::from_array(out)
83}
84
85/// Divide all channels by a scalar.
86///
87/// Equivalent to `channel_mul_scalar(a, 1.0 / s)`.
88pub fn channel_div_scalar<T: ChannelArray<N>, const N: usize>(a: T, s: f32) -> T {
89    channel_mul_scalar(a, 1.0 / s)
90}
91
92macro_rules! impl_channel_ops {
93    ($ty:ty, $n:literal) => {
94        impl std::ops::Add for $ty {
95            type Output = Self;
96            fn add(self, rhs: Self) -> Self { channel_add(self, rhs) }
97        }
98        impl std::ops::Sub for $ty {
99            type Output = Self;
100            fn sub(self, rhs: Self) -> Self { channel_sub(self, rhs) }
101        }
102        impl std::ops::Mul<f32> for $ty {
103            type Output = Self;
104            fn mul(self, rhs: f32) -> Self { channel_mul_scalar(self, rhs) }
105        }
106        impl std::ops::Mul for $ty {
107            type Output = Self;
108            fn mul(self, rhs: Self) -> Self { channel_mul(self, rhs) }
109        }
110        impl std::ops::Div<f32> for $ty {
111            type Output = Self;
112            fn div(self, rhs: f32) -> Self { channel_div_scalar(self, rhs) }
113        }
114        impl From<[f32; $n]> for $ty {
115            fn from(arr: [f32; $n]) -> Self { Self::from_array(arr) }
116        }
117        impl From<$ty> for [f32; $n] {
118            fn from(c: $ty) -> Self { c.to_array() }
119        }
120    };
121}
122
123impl_channel_ops!(RGBA, 4);
124impl_channel_ops!(RGB, 3);
125
126impl From<(f32, f32, f32, f32)> for RGBA {
127    fn from(t: (f32, f32, f32, f32)) -> Self { RGBA(t.0, t.1, t.2, t.3) }
128}
129
130impl From<(f32, f32, f32)> for RGB {
131    fn from(t: (f32, f32, f32)) -> Self { RGB(t.0, t.1, t.2) }
132}