colr 0.2.0

A general purpose, extensible color type unifying color models and their operations at the type level.
//! Linear interpolation between two colors in the same color space.

use crate::Color;
use crate::BackingStore;
use crate::illuminant::Illuminant;
use crate::model::{LCh, Lab, Oklab, Oklch, Rgb};
use crate::primaries::Primaries;
use crate::transfer::Linear;

macro_rules! impl_lerp_perceptual {
    ($name:ident, $bound:tt) => {
        impl<$bound> Color<[f32; 3], $name<$bound>>
        where
            $bound: Illuminant,
        {
            /// Linearly interpolate toward `rhs` by `t` in [0, 1].
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let [a0, a1, a2] = self.inner();
                let [b0, b1, b2] = rhs.inner();
                Color::new([a0 + t * (b0 - a0), a1 + t * (b1 - a1), a2 + t * (b2 - a2)])
            }
        }

        impl<$bound, const O: usize> Color<[f32; 4], $name<$bound, O>>
        where
            $bound: Illuminant,
        {
            /// Linearly interpolate toward `rhs` by `t` in [0, 1]. All four channels are interpolated.
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let [a0, a1, a2, a3] = self.inner();
                let [b0, b1, b2, b3] = rhs.inner();
                Color::new([
                    a0 + t * (b0 - a0),
                    a1 + t * (b1 - a1),
                    a2 + t * (b2 - a2),
                    a3 + t * (b3 - a3),
                ])
            }
        }
    };
    ($name:ident) => {
        impl Color<[f32; 3], $name> {
            /// Linearly interpolate toward `rhs` by `t` in [0, 1].
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let [a0, a1, a2] = self.inner();
                let [b0, b1, b2] = rhs.inner();
                Color::new([a0 + t * (b0 - a0), a1 + t * (b1 - a1), a2 + t * (b2 - a2)])
            }
        }

        impl<const O: usize> Color<[f32; 4], $name<O>> {
            /// Linearly interpolate toward `rhs` by `t` in [0, 1]. All four channels are interpolated.
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let [a0, a1, a2, a3] = self.inner();
                let [b0, b1, b2, b3] = rhs.inner();
                Color::new([
                    a0 + t * (b0 - a0),
                    a1 + t * (b1 - a1),
                    a2 + t * (b2 - a2),
                    a3 + t * (b3 - a3),
                ])
            }
        }
    };
}

impl_lerp_perceptual!(Lab, W);
impl_lerp_perceptual!(Oklab);

// Polar spaces: L and C interpolate linearly; hue takes the shortest arc.

#[inline(always)]
fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
    let delta = (b - a + core::f32::consts::PI).rem_euclid(core::f32::consts::TAU) - core::f32::consts::PI;
    a + t * delta
}

macro_rules! impl_lerp_polar {
    ($name:ident, $bound:tt) => {
        impl<$bound> Color<[f32; 3], $name<$bound>> where $bound: Illuminant {
            /// Interpolate toward `rhs` by `t`. L and C are linear; hue takes the shortest arc.
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let [al, ac, ah] = self.inner();
                let [bl, bc, bh] = rhs.inner();
                Color::new([
                    al + t * (bl - al),
                    ac + t * (bc - ac),
                    lerp_hue(ah, bh, t),
                ])
            }
        }
        impl<$bound, const O: usize> Color<[f32; 4], $name<$bound, O>> where $bound: Illuminant {
            /// Interpolate toward `rhs` by `t`. L and C are linear; hue takes the shortest arc.
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let s = self.inner();
                let r = rhs.inner();
                let ai = 3 - O * 3;
                let mut out = s;
                out[O]     = s[O]     + t * (r[O]     - s[O]);
                out[O + 1] = s[O + 1] + t * (r[O + 1] - s[O + 1]);
                out[O + 2] = lerp_hue(s[O + 2], r[O + 2], t);
                out[ai]    = s[ai]    + t * (r[ai]    - s[ai]);
                Color::new(out)
            }
        }
    };
    ($name:ident) => {
        impl Color<[f32; 3], $name> {
            /// Interpolate toward `rhs` by `t`. L and C are linear; hue takes the shortest arc.
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let [al, ac, ah] = self.inner();
                let [bl, bc, bh] = rhs.inner();
                Color::new([
                    al + t * (bl - al),
                    ac + t * (bc - ac),
                    lerp_hue(ah, bh, t),
                ])
            }
        }
        impl<const O: usize> Color<[f32; 4], $name<O>> {
            /// Interpolate toward `rhs` by `t`. L and C are linear; hue takes the shortest arc.
            #[inline(always)]
            pub fn lerp(self, rhs: Self, t: f32) -> Self {
                let s = self.inner();
                let r = rhs.inner();
                let ai = 3 - O * 3;
                let mut out = s;
                out[O]     = s[O]     + t * (r[O]     - s[O]);
                out[O + 1] = s[O + 1] + t * (r[O + 1] - s[O + 1]);
                out[O + 2] = lerp_hue(s[O + 2], r[O + 2], t);
                out[ai]    = s[ai]    + t * (r[ai]    - s[ai]);
                Color::new(out)
            }
        }
    };
}

impl_lerp_polar!(LCh, W);
impl_lerp_polar!(Oklch);

impl<P, L> Color<[f32; 3], Rgb<P, Linear, L>>
where
    P: Primaries,
    L: BackingStore<[f32; 3]>,
{
    /// Linearly interpolate toward `rhs` by `t` in [0, 1].
    #[inline(always)]
    pub fn lerp(self, rhs: Self, t: f32) -> Self {
        let [a0, a1, a2] = self.inner();
        let [b0, b1, b2] = rhs.inner();
        Color::new([a0 + t * (b0 - a0), a1 + t * (b1 - a1), a2 + t * (b2 - a2)])
    }
}

impl<P, L> Color<[f32; 4], Rgb<P, Linear, L>>
where
    P: Primaries,
    L: BackingStore<[f32; 4]>,
{
    /// Linearly interpolate toward `rhs` by `t` in [0, 1]. All four channels are interpolated.
    #[inline(always)]
    pub fn lerp(self, rhs: Self, t: f32) -> Self {
        let [a0, a1, a2, a3] = self.inner();
        let [b0, b1, b2, b3] = rhs.inner();
        Color::new([
            a0 + t * (b0 - a0),
            a1 + t * (b1 - a1),
            a2 + t * (b2 - a2),
            a3 + t * (b3 - a3),
        ])
    }
}