colr-types 0.3.1

Color model ZSTs and marker traits for colr.
Documentation
//! Compile-time matrix arithmetic ([`Mat3`]) and floating-point math abstraction ([`Float`]).

#[cfg(all(not(feature = "std"), not(feature = "libm")))]
compile_error!(
    "color requires either the `std` or `libm` feature. \
     Add color = {{ features = [\"std\"] }} to your Cargo.toml."
);

/// Floating-point scalar operations for color math.
///
/// Implemented for `f32` and `f64`. When `f16` stabilizes in Rust it will
/// implement this trait and all operations will work at half precision without
/// further changes. Transcendental functions are provided by the standard
/// library under the `std` feature and by `libm` under `no_std`.
pub trait Float:
    Copy
    + 'static
    + PartialOrd
    + core::ops::Add<Output = Self>
    + core::ops::Sub<Output = Self>
    + core::ops::Mul<Output = Self>
    + core::ops::Div<Output = Self>
    + core::ops::Neg<Output = Self>
{
    /// Additive identity.
    const ZERO: Self;
    /// Multiplicative identity.
    const ONE: Self;
    /// Smallest positive value.
    const MIN_POSITIVE: Self;

    /// Convert an `f64` literal to `Self`. Used to widen f32 constants in
    /// generic code; for f32 this narrows back from f64, which is lossless
    /// for all constants in this crate.
    fn from_f64(v: f64) -> Self;

    /// base^exp.
    fn powf(self, exp: Self) -> Self;
    /// Cube root.
    fn cbrt(self) -> Self;
    /// Square root.
    fn sqrt(self) -> Self;
    /// Natural logarithm.
    fn ln(self) -> Self;
    /// Natural exponential.
    fn exp(self) -> Self;
    /// Sine (radians).
    fn sin(self) -> Self;
    /// Cosine (radians).
    fn cos(self) -> Self;
    /// atan2(self, x).
    fn atan2(self, x: Self) -> Self;
    /// Round to nearest integer.
    fn round(self) -> Self;
    /// Integer power.
    fn powi(self, n: i32) -> Self;
    /// Absolute value.
    fn abs(self) -> Self;
    /// Euclidean remainder.
    fn rem_euclid(self, rhs: Self) -> Self;
    /// Clamp to [min, max].
    fn clamp(self, min: Self, max: Self) -> Self;
    /// Greater of self and other.
    fn max(self, other: Self) -> Self;
    /// Lesser of self and other.
    fn min(self, other: Self) -> Self;
}

// Generates Float impl for a concrete float type using std inherent methods.
#[cfg(feature = "std")]
macro_rules! impl_float_std {
    ($t:ty, $zero:expr, $one:expr, $min_pos:expr, $from_f64:expr) => {
        impl Float for $t {
            const ZERO: $t = $zero;
            const ONE: $t = $one;
            const MIN_POSITIVE: $t = $min_pos;
            #[inline(always)] fn from_f64(v: f64) -> $t { ($from_f64)(v) }
            #[inline(always)] fn powf(self, exp: $t) -> $t { self.powf(exp) }
            #[inline(always)] fn cbrt(self) -> $t { self.cbrt() }
            #[inline(always)] fn sqrt(self) -> $t { self.sqrt() }
            #[inline(always)] fn ln(self) -> $t { self.ln() }
            #[inline(always)] fn exp(self) -> $t { self.exp() }
            #[inline(always)] fn sin(self) -> $t { self.sin() }
            #[inline(always)] fn cos(self) -> $t { self.cos() }
            #[inline(always)] fn atan2(self, x: $t) -> $t { self.atan2(x) }
            #[inline(always)] fn round(self) -> $t { self.round() }
            #[inline(always)] fn powi(self, n: i32) -> $t { self.powi(n) }
            #[inline(always)] fn abs(self) -> $t { self.abs() }
            #[inline(always)] fn rem_euclid(self, rhs: $t) -> $t { self.rem_euclid(rhs) }
            #[inline(always)] fn clamp(self, min: $t, max: $t) -> $t { self.clamp(min, max) }
            #[inline(always)] fn max(self, other: $t) -> $t { self.max(other) }
            #[inline(always)] fn min(self, other: $t) -> $t { self.min(other) }
        }
    };
}

// Generates Float impl for a concrete float type using libm.
#[cfg(all(not(feature = "std"), feature = "libm"))]
macro_rules! impl_float_libm {
    ($t:ty, $zero:expr, $one:expr, $min_pos:expr, $from_f64:expr,
     $powf:path, $cbrt:path, $sqrt:path, $ln:path, $exp:path,
     $sin:path, $cos:path, $atan2:path, $round:path,
     $powi_base:path, $powi_cast:ty,
     $fabs:path) => {
        impl Float for $t {
            const ZERO: $t = $zero;
            const ONE: $t = $one;
            const MIN_POSITIVE: $t = $min_pos;
            #[inline(always)] fn from_f64(v: f64) -> $t { ($from_f64)(v) }
            #[inline(always)] fn powf(self, exp: $t) -> $t { $powf(self, exp) }
            #[inline(always)] fn cbrt(self) -> $t { $cbrt(self) }
            #[inline(always)] fn sqrt(self) -> $t { $sqrt(self) }
            #[inline(always)] fn ln(self) -> $t { $ln(self) }
            #[inline(always)] fn exp(self) -> $t { $exp(self) }
            #[inline(always)] fn sin(self) -> $t { $sin(self) }
            #[inline(always)] fn cos(self) -> $t { $cos(self) }
            #[inline(always)] fn atan2(self, x: $t) -> $t { $atan2(self, x) }
            #[inline(always)] fn round(self) -> $t { $round(self) }
            #[inline(always)] fn powi(self, n: i32) -> $t { $powi_base(self, n as $powi_cast) }
            #[inline(always)] fn abs(self) -> $t { $fabs(self) }
            #[inline(always)] fn rem_euclid(self, rhs: $t) -> $t {
                let r = self % rhs;
                if r < $zero { r + $fabs(rhs) } else { r }
            }
            #[inline(always)] fn clamp(self, min: $t, max: $t) -> $t {
                if self < min { min } else if self > max { max } else { self }
            }
            #[inline(always)] fn max(self, other: $t) -> $t { if self >= other { self } else { other } }
            #[inline(always)] fn min(self, other: $t) -> $t { if self <= other { self } else { other } }
        }
    };
}

#[cfg(feature = "std")]
impl_float_std!(f32, 0.0, 1.0, f32::MIN_POSITIVE, |v: f64| v as f32);
#[cfg(feature = "std")]
impl_float_std!(f64, 0.0, 1.0, f64::MIN_POSITIVE, |v: f64| v);

#[cfg(all(not(feature = "std"), feature = "libm"))]
impl_float_libm!(f32, 0.0f32, 1.0f32, f32::MIN_POSITIVE, |v: f64| v as f32,
    libm::powf, libm::cbrtf, libm::sqrtf, libm::logf, libm::expf,
    libm::sinf, libm::cosf, libm::atan2f, libm::roundf,
    libm::powf, f32,
    libm::fabsf);
#[cfg(all(not(feature = "std"), feature = "libm"))]
impl_float_libm!(f64, 0.0f64, 1.0f64, f64::MIN_POSITIVE, |v: f64| v,
    libm::pow, libm::cbrt, libm::sqrt, libm::log, libm::exp,
    libm::sin, libm::cos, libm::atan2, libm::round,
    libm::pow, f64,
    libm::fabs);

macro_rules! impl_mat3 {
    ($name:ident, $scalar:ty, $zero:expr) => {
        /// Column-major 3x3 matrix with `const fn` arithmetic.
        ///
        /// Each column is `[$scalar; 4]` padded to 16 bytes for SIMD alignment.
        /// All methods are `const fn` for compile-time matrix derivation.
        #[repr(C, align(16))]
        #[derive(Debug, Clone, Copy, PartialEq)]
        pub struct $name {
            /// Column 0, rows [0..2] with padding at index 3.
            pub col0: [$scalar; 4],
            /// Column 1, rows [0..2] with padding at index 3.
            pub col1: [$scalar; 4],
            /// Column 2, rows [0..2] with padding at index 3.
            pub col2: [$scalar; 4],
        }

        impl $name {
            /// Left-multiply `a * b`. `const fn`.
            pub const fn mul(a: &Self, b: &Self) -> Self {
                macro_rules! e {
                    ($r:expr, $c:expr) => {
                        a.col0[$r] * $c[0] + a.col1[$r] * $c[1] + a.col2[$r] * $c[2]
                    };
                }
                Self {
                    col0: [e!(0, b.col0), e!(1, b.col0), e!(2, b.col0), $zero],
                    col1: [e!(0, b.col1), e!(1, b.col1), e!(2, b.col1), $zero],
                    col2: [e!(0, b.col2), e!(1, b.col2), e!(2, b.col2), $zero],
                }
            }

            /// Invert via Cramer's rule. `const fn`. Returns a matrix with `NaN`
            /// or `INFINITY` entries if the determinant is zero or near-zero.
            /// All standard RGB primary matrices have non-zero determinants.
            pub const fn invert(m: &Self) -> Self {
                let [a, b, c, _] = m.col0;
                let [d, e, f, _] = m.col1;
                let [g, h, i, _] = m.col2;
                let det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
                Self {
                    col0: [
                        (e * i - f * h) / det,
                        -(b * i - c * h) / det,
                        (b * f - c * e) / det,
                        $zero,
                    ],
                    col1: [
                        -(d * i - f * g) / det,
                        (a * i - c * g) / det,
                        -(a * f - c * d) / det,
                        $zero,
                    ],
                    col2: [
                        (d * h - e * g) / det,
                        -(a * h - b * g) / det,
                        (a * e - b * d) / det,
                        $zero,
                    ],
                }
            }

            /// Construct from row-major `[[$scalar; 3]; 3]`. `const fn`.
            pub const fn from_rows(m: [[$scalar; 3]; 3]) -> Self {
                Self {
                    col0: [m[0][0], m[1][0], m[2][0], $zero],
                    col1: [m[0][1], m[1][1], m[2][1], $zero],
                    col2: [m[0][2], m[1][2], m[2][2], $zero],
                }
            }

            /// Convert to row-major `[[$scalar; 3]; 3]`. `const fn`.
            pub const fn to_rows(self) -> [[$scalar; 3]; 3] {
                [
                    [self.col0[0], self.col1[0], self.col2[0]],
                    [self.col0[1], self.col1[1], self.col2[1]],
                    [self.col0[2], self.col1[2], self.col2[2]],
                ]
            }

            /// Apply to a `[$scalar; 3]`. `const fn`.
            #[inline(always)]
            pub const fn apply(&self, v: [$scalar; 3]) -> [$scalar; 3] {
                [
                    self.col0[0] * v[0] + self.col1[0] * v[1] + self.col2[0] * v[2],
                    self.col0[1] * v[0] + self.col1[1] * v[1] + self.col2[1] * v[2],
                    self.col0[2] * v[0] + self.col1[2] * v[1] + self.col2[2] * v[2],
                ]
            }

            /// Y row as luminance weights `[w_r, w_g, w_b]`. `const fn`.
            #[inline(always)]
            pub const fn luminance_weights(&self) -> [$scalar; 3] {
                [self.col0[1], self.col1[1], self.col2[1]]
            }
        }
    };
}

impl_mat3!(Mat3, f32, 0.0f32);
impl_mat3!(DMat3, f64, 0.0f64);

impl Mat3 {
    /// Apply to a `glam::Vec4`. Lane 3 preserved.
    #[cfg(feature = "glam")]
    #[inline]
    pub fn apply_glam(&self, v: glam::Vec4) -> glam::Vec4 {
        let m = glam::Mat3::from_cols(
            glam::Vec3::from_array([self.col0[0], self.col0[1], self.col0[2]]),
            glam::Vec3::from_array([self.col1[0], self.col1[1], self.col1[2]]),
            glam::Vec3::from_array([self.col2[0], self.col2[1], self.col2[2]]),
        );
        (m * v.truncate()).extend(v.w)
    }
}