From b694ace2bfb303bb41e691f53cd1444c3ae50dcc Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 5 Nov 2024 08:44:28 -0800 Subject: [PATCH] Cleanups and renames In response to feedback on #3. --- color/src/color.rs | 279 ++++++++++++++++++++++------------------ color/src/colorspace.rs | 64 ++++----- color/src/css.rs | 23 ++-- color/src/floatfuncs.rs | 26 ++-- color/src/gradient.rs | 8 +- color/src/lib.rs | 2 +- color/src/tagged.rs | 34 ++--- 7 files changed, 234 insertions(+), 202 deletions(-) diff --git a/color/src/color.rs b/color/src/color.rs index ee3780b..f2c3a87 100644 --- a/color/src/color.rs +++ b/color/src/color.rs @@ -6,49 +6,60 @@ use core::any::TypeId; use core::marker::PhantomData; -use crate::{Colorspace, ColorspaceLayout}; +use crate::{ColorSpace, ColorSpaceLayout}; #[cfg(all(not(feature = "std"), not(test)))] use crate::floatfuncs::FloatFuncs; /// An opaque color. /// -/// A color in a colorspace known at compile time, without transparency. Note +/// A color in a color space known at compile time, without transparency. Note /// that "opaque" refers to the color, not the representation; the components /// are publicly accessible. +/// +/// Arithmetic traits are defined on this type, and operate component-wise. A +/// major motivation for including these is to enable weighted sums, including +/// for spline interpolation. For cylindrical color spaces, hue fixup should +/// be applied before interpolation. #[derive(Clone, Copy, Debug)] -pub struct OpaqueColor { +pub struct OpaqueColor { pub components: [f32; 3], - pub cs: PhantomData, + pub cs: PhantomData, } /// A color with an alpha channel. /// -/// A color in a colorspace known at compile time, with an alpha channel. +/// A color in a color space known at compile time, with an alpha channel. +/// +/// See [`OpaqueColor`] for a discussion of arithmetic traits and interpolation. #[derive(Clone, Copy, Debug)] -pub struct AlphaColor { +pub struct AlphaColor { pub components: [f32; 4], - pub cs: PhantomData, + pub cs: PhantomData, } /// A color with premultiplied alpha. /// -/// A color in a colorspace known at compile time, with a premultiplied +/// A color in a color space known at compile time, with a premultiplied /// alpha channel. /// /// Following the convention of CSS Color 4, in cylindrical color spaces /// the hue channel is not premultiplied. If it were, interpolation would /// give undesirable results. +/// +/// See [`OpaqueColor`] for a discussion of arithmetic traits and interpolation. #[derive(Clone, Copy, Debug)] -pub struct PremulColor { +pub struct PremulColor { pub components: [f32; 4], - pub cs: PhantomData, + pub cs: PhantomData, } /// The hue direction for interpolation. /// -/// This type corresponds to `hue-interpolation-method` in the CSS Color +/// This type corresponds to [`hue-interpolation-method`] in the CSS Color /// 4 spec. +/// +/// [`hue-interpolation-method`]: https://developer.mozilla.org/en-US/docs/Web/CSS/hue-interpolation-method #[derive(Clone, Copy, Default, Debug)] #[non_exhaustive] pub enum HueDirection { @@ -94,7 +105,7 @@ fn fixup_hue(h1: f32, h2: &mut f32, direction: HueDirection) { pub(crate) fn fixup_hues_for_interpolate( a: [f32; 3], b: &mut [f32; 3], - layout: ColorspaceLayout, + layout: ColorSpaceLayout, direction: HueDirection, ) { if let Some(ix) = layout.hue_channel() { @@ -102,17 +113,17 @@ pub(crate) fn fixup_hues_for_interpolate( } } -impl OpaqueColor { +impl OpaqueColor { pub const fn new(components: [f32; 3]) -> Self { let cs = PhantomData; Self { components, cs } } - pub fn convert(self) -> OpaqueColor { - if TypeId::of::() == TypeId::of::() { + pub fn convert(self) -> OpaqueColor { + if TypeId::of::() == TypeId::of::() { OpaqueColor::new(self.components) } else { - let lin_rgb = T::to_linear_srgb(self.components); + let lin_rgb = CS::to_linear_srgb(self.components); OpaqueColor::new(U::from_linear_srgb(lin_rgb)) } } @@ -120,7 +131,7 @@ impl OpaqueColor { /// Add an alpha channel. /// /// This function is the inverse of [`AlphaColor::split`]. - pub const fn with_alpha(self, alpha: f32) -> AlphaColor { + pub const fn with_alpha(self, alpha: f32) -> AlphaColor { AlphaColor::new(add_alpha(self.components, alpha)) } @@ -134,7 +145,7 @@ impl OpaqueColor { /// Linearly interpolate colors, without hue fixup. /// - /// This method produces meaningful results in rectangular colorspaces, + /// This method produces meaningful results in rectangular color spaces, /// or if hue fixup has been applied. #[must_use] pub fn lerp_rect(self, other: Self, t: f32) -> Self { @@ -146,7 +157,12 @@ impl OpaqueColor { /// Adjust the hue angle of `other` so that linear interpolation results in /// the expected hue direction. pub fn fixup_hues(self, other: &mut Self, direction: HueDirection) { - fixup_hues_for_interpolate(self.components, &mut other.components, T::LAYOUT, direction); + fixup_hues_for_interpolate( + self.components, + &mut other.components, + CS::LAYOUT, + direction, + ); } /// Linearly interpolate colors, with hue fixup if needed. @@ -161,7 +177,7 @@ impl OpaqueColor { /// See [`Colorspace::scale_chroma`] for more details. #[must_use] pub fn scale_chroma(self, scale: f32) -> Self { - Self::new(T::scale_chroma(self.components, scale)) + Self::new(CS::scale_chroma(self.components, scale)) } /// Compute the relative luminance of the color. @@ -169,20 +185,20 @@ impl OpaqueColor { /// This can be useful for choosing contrasting colors, and follows the /// WCAG 2.1 spec. pub fn relative_luminance(self) -> f32 { - let rgb = T::to_linear_srgb(self.components); + let rgb = CS::to_linear_srgb(self.components); 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] } } -pub(crate) const fn split_alpha(x: [f32; 4]) -> ([f32; 3], f32) { - ([x[0], x[1], x[2]], x[3]) +pub(crate) const fn split_alpha([x, y, z, a]: [f32; 4]) -> ([f32; 3], f32) { + ([x, y, z], a) } -pub(crate) const fn add_alpha(x: [f32; 3], a: f32) -> [f32; 4] { - [x[0], x[1], x[2], a] +pub(crate) const fn add_alpha([x, y, z]: [f32; 3], a: f32) -> [f32; 4] { + [x, y, z, a] } -impl AlphaColor { +impl AlphaColor { pub const fn new(components: [f32; 4]) -> Self { let cs = PhantomData; Self { components, cs } @@ -192,26 +208,26 @@ impl AlphaColor { /// /// This function is the inverse of [`OpaqueColor::with_alpha`]. #[must_use] - pub const fn split(self) -> (OpaqueColor, f32) { + pub const fn split(self) -> (OpaqueColor, f32) { let (opaque, alpha) = split_alpha(self.components); (OpaqueColor::new(opaque), alpha) } #[must_use] - pub fn convert(self) -> AlphaColor { - if TypeId::of::() == TypeId::of::() { + pub fn convert(self) -> AlphaColor { + if TypeId::of::() == TypeId::of::() { AlphaColor::new(self.components) } else { let (opaque, alpha) = split_alpha(self.components); - let lin_rgb = T::to_linear_srgb(opaque); + let lin_rgb = CS::to_linear_srgb(opaque); AlphaColor::new(add_alpha(U::from_linear_srgb(lin_rgb), alpha)) } } #[must_use] - pub const fn premultiply(self) -> PremulColor { + pub const fn premultiply(self) -> PremulColor { let (opaque, alpha) = split_alpha(self.components); - PremulColor::new(add_alpha(T::LAYOUT.scale(opaque, alpha), alpha)) + PremulColor::new(add_alpha(CS::LAYOUT.scale(opaque, alpha), alpha)) } #[must_use] @@ -240,41 +256,41 @@ impl AlphaColor { #[must_use] pub fn scale_chroma(self, scale: f32) -> Self { let (opaque, alpha) = split_alpha(self.components); - Self::new(add_alpha(T::scale_chroma(opaque, scale), alpha)) + Self::new(add_alpha(CS::scale_chroma(opaque, scale), alpha)) } } -impl PremulColor { +impl PremulColor { pub const fn new(components: [f32; 4]) -> Self { let cs = PhantomData; Self { components, cs } } #[must_use] - pub fn convert(self) -> PremulColor { - if TypeId::of::() == TypeId::of::() { + pub fn convert(self) -> PremulColor { + if TypeId::of::() == TypeId::of::() { PremulColor::new(self.components) - } else if U::IS_LINEAR && T::IS_LINEAR { + } else if TargetCS::IS_LINEAR && CS::IS_LINEAR { let (multiplied, alpha) = split_alpha(self.components); - let lin_rgb = T::to_linear_srgb(multiplied); - PremulColor::new(add_alpha(U::from_linear_srgb(lin_rgb), alpha)) + let lin_rgb = CS::to_linear_srgb(multiplied); + PremulColor::new(add_alpha(TargetCS::from_linear_srgb(lin_rgb), alpha)) } else { self.un_premultiply().convert().premultiply() } } #[must_use] - pub fn un_premultiply(self) -> AlphaColor { + pub fn un_premultiply(self) -> AlphaColor { let (multiplied, alpha) = split_alpha(self.components); let scale = if alpha == 0.0 { 1.0 } else { 1.0 / alpha }; - AlphaColor::new(add_alpha(T::LAYOUT.scale(multiplied, scale), alpha)) + AlphaColor::new(add_alpha(CS::LAYOUT.scale(multiplied, scale), alpha)) } /// Interpolate colors. /// /// Note: this function doesn't fix up hue in cylindrical spaces. It is - /// still be useful if the hue angles are compatible, particularly if - /// the fixup has been applied. + /// still useful if the hue angles are compatible, particularly if the + /// fixup has been applied. #[must_use] pub fn lerp_rect(self, other: Self, t: f32) -> Self { self + t * (other - self) @@ -285,7 +301,7 @@ impl PremulColor { /// Adjust the hue angle of `other` so that linear interpolation results in /// the expected hue direction. pub fn fixup_hues(self, other: &mut Self, direction: HueDirection) { - if let Some(ix) = T::LAYOUT.hue_channel() { + if let Some(ix) = CS::LAYOUT.hue_channel() { fixup_hue(self.components[ix], &mut other.components[ix], direction); } } @@ -300,39 +316,33 @@ impl PremulColor { #[must_use] pub const fn mul_alpha(self, rhs: f32) -> Self { let (multiplied, alpha) = split_alpha(self.components); - Self::new(add_alpha(T::LAYOUT.scale(multiplied, rhs), alpha * rhs)) + Self::new(add_alpha(CS::LAYOUT.scale(multiplied, rhs), alpha * rhs)) } /// Difference between two colors by Euclidean metric. #[must_use] pub fn difference(self, other: Self) -> f32 { - let x = self.components; - let y = other.components; - let (d0, d1, d2, d3) = (x[0] - y[0], x[1] - y[1], x[2] - y[2], x[3] - y[3]); - (d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3).sqrt() + let d = (self - other).components; + (d[0] * d[0] + d[1] * d[1] + d[2] * d[2] + d[3] * d[3]).sqrt() } } // Lossless conversion traits. -impl From> for AlphaColor { - fn from(value: OpaqueColor) -> Self { +impl From> for AlphaColor { + fn from(value: OpaqueColor) -> Self { value.with_alpha(1.0) } } -impl From> for PremulColor { - fn from(value: OpaqueColor) -> Self { +impl From> for PremulColor { + fn from(value: OpaqueColor) -> Self { Self::new(add_alpha(value.components, 1.0)) } } -// Arithmetic traits. A major motivation for providing these is to enable -// weighted sums, which are well defined when the weights sum to 1. In -// addition, multiplication by alpha is well defined for premultiplied -// colors in rectangular colorspaces. - -impl core::ops::Mul for OpaqueColor { +/// Multiply components by a scalar. +impl core::ops::Mul for OpaqueColor { type Output = Self; fn mul(self, rhs: f32) -> Self { @@ -340,27 +350,28 @@ impl core::ops::Mul for OpaqueColor { } } -impl core::ops::Mul> for f32 { - type Output = OpaqueColor; +/// Multiply components by a scalar. +impl core::ops::Mul> for f32 { + type Output = OpaqueColor; - fn mul(self, rhs: OpaqueColor) -> Self::Output { + fn mul(self, rhs: OpaqueColor) -> Self::Output { rhs * self } } -impl core::ops::Div for OpaqueColor { +/// Divide components by a scalar. +impl core::ops::Div for OpaqueColor { type Output = Self; - #[expect( - clippy::suspicious_arithmetic_impl, - reason = "somebody please teach clippy about multiplicative inverses" - )] + // https://github.com/rust-lang/rust-clippy/issues/13652 has been filed + #[expect(clippy::suspicious_arithmetic_impl, reason = "multiplicative inverse")] fn div(self, rhs: f32) -> Self { self * rhs.recip() } } -impl core::ops::Add for OpaqueColor { +/// Component-wise addition of components. +impl core::ops::Add for OpaqueColor { type Output = Self; fn add(self, rhs: Self) -> Self { @@ -370,7 +381,8 @@ impl core::ops::Add for OpaqueColor { } } -impl core::ops::Sub for OpaqueColor { +/// Component-wise subtraction of components. +impl core::ops::Sub for OpaqueColor { type Output = Self; fn sub(self, rhs: Self) -> Self { @@ -380,7 +392,8 @@ impl core::ops::Sub for OpaqueColor { } } -impl core::ops::Mul for AlphaColor { +/// Multiply components by a scalar. +impl core::ops::Mul for AlphaColor { type Output = Self; fn mul(self, rhs: f32) -> Self { @@ -388,27 +401,27 @@ impl core::ops::Mul for AlphaColor { } } -impl core::ops::Mul> for f32 { - type Output = AlphaColor; +/// Multiply components by a scalar. +impl core::ops::Mul> for f32 { + type Output = AlphaColor; - fn mul(self, rhs: AlphaColor) -> Self::Output { + fn mul(self, rhs: AlphaColor) -> Self::Output { rhs * self } } -impl core::ops::Div for AlphaColor { +/// Divide components by a scalar. +impl core::ops::Div for AlphaColor { type Output = Self; - #[expect( - clippy::suspicious_arithmetic_impl, - reason = "somebody please teach clippy about multiplicative inverses" - )] + #[expect(clippy::suspicious_arithmetic_impl, reason = "multiplicative inverse")] fn div(self, rhs: f32) -> Self { self * rhs.recip() } } -impl core::ops::Add for AlphaColor { +/// Component-wise addition of components. +impl core::ops::Add for AlphaColor { type Output = Self; fn add(self, rhs: Self) -> Self { @@ -418,7 +431,8 @@ impl core::ops::Add for AlphaColor { } } -impl core::ops::Sub for AlphaColor { +/// Component-wise subtraction of components. +impl core::ops::Sub for AlphaColor { type Output = Self; fn sub(self, rhs: Self) -> Self { @@ -428,7 +442,12 @@ impl core::ops::Sub for AlphaColor { } } -impl core::ops::Mul for PremulColor { +/// Multiply components by a scalar. +/// +/// For rectangular color spaces, this is equivalent to multiplying +/// alpha, but for cylindrical color spaces, [`PremulColor::mul_alpha`] +/// is the preferred method. +impl core::ops::Mul for PremulColor { type Output = Self; fn mul(self, rhs: f32) -> Self { @@ -436,27 +455,27 @@ impl core::ops::Mul for PremulColor { } } -impl core::ops::Mul> for f32 { - type Output = PremulColor; +/// Multiply components by a scalar. +impl core::ops::Mul> for f32 { + type Output = PremulColor; - fn mul(self, rhs: PremulColor) -> Self::Output { + fn mul(self, rhs: PremulColor) -> Self::Output { rhs * self } } -impl core::ops::Div for PremulColor { +/// Divide components by a scalar. +impl core::ops::Div for PremulColor { type Output = Self; - #[expect( - clippy::suspicious_arithmetic_impl, - reason = "somebody please teach clippy about multiplicative inverses" - )] + #[expect(clippy::suspicious_arithmetic_impl, reason = "multiplicative inverse")] fn div(self, rhs: f32) -> Self { self * rhs.recip() } } -impl core::ops::Add for PremulColor { +/// Component-wise addition of components. +impl core::ops::Add for PremulColor { type Output = Self; fn add(self, rhs: Self) -> Self { @@ -466,7 +485,8 @@ impl core::ops::Add for PremulColor { } } -impl core::ops::Sub for PremulColor { +/// Component-wise subtraction of components. +impl core::ops::Sub for PremulColor { type Output = Self; fn sub(self, rhs: Self) -> Self { @@ -477,54 +497,59 @@ impl core::ops::Sub for PremulColor { } #[cfg(test)] -mod test { +mod tests { use super::{fixup_hue, HueDirection}; #[test] - fn test_hue_fixup() { + fn hue_fixup() { // Verify that the hue arc matches the spec for all hues specified // within [0,360). for h1 in [0.0, 10.0, 180.0, 190.0, 350.0] { for h2 in [0.0, 10.0, 180.0, 190.0, 350.0] { let dh = h2 - h1; - let mut fixed_h2 = h2; - fixup_hue(h1, &mut fixed_h2, HueDirection::Shorter); - let (mut spec_h1, mut spec_h2) = (h1, h2); - if dh > 180.0 { - spec_h1 += 360.0; - } else if dh < -180.0 { - spec_h2 += 360.0; + { + let mut fixed_h2 = h2; + fixup_hue(h1, &mut fixed_h2, HueDirection::Shorter); + let (mut spec_h1, mut spec_h2) = (h1, h2); + if dh > 180.0 { + spec_h1 += 360.0; + } else if dh < -180.0 { + spec_h2 += 360.0; + } + assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); } - assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); - - fixed_h2 = h2; - fixup_hue(h1, &mut fixed_h2, HueDirection::Longer); - spec_h1 = h1; - spec_h2 = h2; - if 0.0 < dh && dh < 180.0 { - spec_h1 += 360.0; - } else if -180.0 < dh && dh <= 0.0 { - spec_h2 += 360.0; + + { + let mut fixed_h2 = h2; + fixup_hue(h1, &mut fixed_h2, HueDirection::Longer); + let (mut spec_h1, mut spec_h2) = (h1, h2); + if 0.0 < dh && dh < 180.0 { + spec_h1 += 360.0; + } else if -180.0 < dh && dh <= 0.0 { + spec_h2 += 360.0; + } + assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); } - assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); - - fixed_h2 = h2; - fixup_hue(h1, &mut fixed_h2, HueDirection::Increasing); - spec_h1 = h1; - spec_h2 = h2; - if dh < 0.0 { - spec_h2 += 360.0; + + { + let mut fixed_h2 = h2; + fixup_hue(h1, &mut fixed_h2, HueDirection::Increasing); + let (spec_h1, mut spec_h2) = (h1, h2); + if dh < 0.0 { + spec_h2 += 360.0; + } + assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); } - assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); - - fixed_h2 = h2; - fixup_hue(h1, &mut fixed_h2, HueDirection::Decreasing); - spec_h1 = h1; - spec_h2 = h2; - if dh > 0.0 { - spec_h1 += 360.0; + + { + let mut fixed_h2 = h2; + fixup_hue(h1, &mut fixed_h2, HueDirection::Decreasing); + let (mut spec_h1, spec_h2) = (h1, h2); + if dh > 0.0 { + spec_h1 += 360.0; + } + assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); } - assert_eq!(fixed_h2 - h1, spec_h2 - spec_h1); } } } diff --git a/color/src/colorspace.rs b/color/src/colorspace.rs index 34465fc..24789f2 100644 --- a/color/src/colorspace.rs +++ b/color/src/colorspace.rs @@ -8,35 +8,35 @@ use crate::{matmul, tagged::ColorspaceTag}; #[cfg(all(not(feature = "std"), not(test)))] use crate::floatfuncs::FloatFuncs; -/// The main trait for colorspaces. +/// The main trait for color spaces. /// /// This can be implemented by clients for conversions in and out of -/// new colorspaces. It is expected to be a zero-sized type. +/// new color spaces. It is expected to be a zero-sized type. /// -/// The linear sRGB colorspace is central, and other colorspaces are -/// defined as conversions in and out of that. A colorspace does not +/// The linear sRGB color space is central, and other color spaces are +/// defined as conversions in and out of that. A color space does not /// explicitly define a gamut, so generally conversions will succeed /// and round-trip, subject to numerical precision. /// /// White point is not explicitly represented. For color spaces with a /// white point other than D65 (the native white point for sRGB), use /// a linear Bradford chromatic adaptation, following CSS Color 4. -pub trait Colorspace: Clone + Copy + 'static { - /// Whether the colorspace is linear. +pub trait ColorSpace: Clone + Copy + 'static { + /// Whether the color space is linear. /// - /// Calculations in linear colorspaces can sometimes be simplified, + /// Calculations in linear colors paces can sometimes be simplified, /// for example it is not necessary to undo premultiplication when /// converting. const IS_LINEAR: bool = false; - /// The layout of the colorspace. + /// The layout of the color space. /// /// The layout primarily identifies the hue channel for cylindrical - /// colorspaces, which is important because hue is not premultiplied. - const LAYOUT: ColorspaceLayout = ColorspaceLayout::Rectangular; + /// color spaces, which is important because hue is not premultiplied. + const LAYOUT: ColorSpaceLayout = ColorSpaceLayout::Rectangular; - /// The tag corresponding to this colorspace, if a matching tag exists. - const CS_TAG: Option = None; + /// The tag corresponding to this color space, if a matching tag exists. + const TAG: Option = None; /// Convert an opaque color to linear sRGB. /// @@ -50,8 +50,8 @@ pub trait Colorspace: Clone + Copy + 'static { /// Scale the chroma by the given amount. /// - /// In colorspaces with a natural representation of chroma, scale - /// directly. In other colorspaces, equivalent results as scaling + /// In color spaces with a natural representation of chroma, scale + /// directly. In other color spaces, equivalent results as scaling /// chroma in Oklab. fn scale_chroma(src: [f32; 3], scale: f32) -> [f32; 3] { let rgb = Self::to_linear_srgb(src); @@ -60,16 +60,16 @@ pub trait Colorspace: Clone + Copy + 'static { } } -/// The layout of a colorspace, particularly the hue channel. +/// The layout of a color space, particularly the hue channel. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[non_exhaustive] -pub enum ColorspaceLayout { +pub enum ColorSpaceLayout { Rectangular, HueFirst, HueThird, } -impl ColorspaceLayout { +impl ColorSpaceLayout { /// Multiply all components except for hue by scale. /// /// This function is used for both premultiplying and un-premultiplying. See @@ -98,10 +98,10 @@ impl ColorspaceLayout { #[derive(Clone, Copy, Debug)] pub struct LinearSrgb; -impl Colorspace for LinearSrgb { +impl ColorSpace for LinearSrgb { const IS_LINEAR: bool = true; - const CS_TAG: Option = Some(ColorspaceTag::LinearSrgb); + const TAG: Option = Some(ColorspaceTag::LinearSrgb); fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { src @@ -144,8 +144,8 @@ fn lin_to_srgb(x: f32) -> f32 { } } -impl Colorspace for Srgb { - const CS_TAG: Option = Some(ColorspaceTag::Srgb); +impl ColorSpace for Srgb { + const TAG: Option = Some(ColorspaceTag::Srgb); fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { src.map(srgb_to_lin) @@ -159,8 +159,8 @@ impl Colorspace for Srgb { #[derive(Clone, Copy, Debug)] pub struct DisplayP3; -impl Colorspace for DisplayP3 { - const CS_TAG: Option = Some(ColorspaceTag::DisplayP3); +impl ColorSpace for DisplayP3 { + const TAG: Option = Some(ColorspaceTag::DisplayP3); fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { const LINEAR_DISPLAYP3_TO_SRGB: [[f32; 3]; 3] = [ @@ -184,10 +184,10 @@ impl Colorspace for DisplayP3 { #[derive(Clone, Copy, Debug)] pub struct XyzD65; -impl Colorspace for XyzD65 { +impl ColorSpace for XyzD65 { const IS_LINEAR: bool = true; - const CS_TAG: Option = Some(ColorspaceTag::XyzD65); + const TAG: Option = Some(ColorspaceTag::XyzD65); fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { const XYZ_TO_LINEAR_SRGB: [[f32; 3]; 3] = [ @@ -211,7 +211,9 @@ impl Colorspace for XyzD65 { #[derive(Clone, Copy, Debug)] pub struct Oklab; -// Matrices taken from Oklab blog post, precision reduced to f32 +// Matrices taken from [Oklab] blog post, precision reduced to f32 +// +// [Oklab]: https://bottosson.github.io/posts/oklab/ const OKLAB_LAB_TO_LMS: [[f32; 3]; 3] = [ [1.0, 0.396_337_78, 0.215_803_76], [1.0, -0.105_561_346, -0.063_854_17], @@ -236,8 +238,8 @@ const OKLAB_LMS_TO_LAB: [[f32; 3]; 3] = [ [0.025_904_037, 0.782_771_77, -0.808_675_77], ]; -impl Colorspace for Oklab { - const CS_TAG: Option = Some(ColorspaceTag::Oklab); +impl ColorSpace for Oklab { + const TAG: Option = Some(ColorspaceTag::Oklab); fn to_linear_srgb(src: [f32; 3]) -> [f32; 3] { let lms = matmul(&OKLAB_LAB_TO_LMS, src).map(|x| x * x * x); @@ -257,10 +259,10 @@ impl Colorspace for Oklab { #[derive(Clone, Copy, Debug)] pub struct Oklch; -impl Colorspace for Oklch { - const CS_TAG: Option = Some(ColorspaceTag::Oklch); +impl ColorSpace for Oklch { + const TAG: Option = Some(ColorspaceTag::Oklch); - const LAYOUT: ColorspaceLayout = ColorspaceLayout::HueThird; + const LAYOUT: ColorSpaceLayout = ColorSpaceLayout::HueThird; fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] { let lab = Oklab::from_linear_srgb(src); diff --git a/color/src/css.rs b/color/src/css.rs index b080f8e..8a67578 100644 --- a/color/src/css.rs +++ b/color/src/css.rs @@ -5,7 +5,7 @@ use crate::{ color::{add_alpha, fixup_hues_for_interpolate, split_alpha}, - AlphaColor, Bitset, Colorspace, ColorspaceLayout, ColorspaceTag, HueDirection, TaggedColor, + AlphaColor, Bitset, ColorSpace, ColorSpaceLayout, ColorspaceTag, HueDirection, TaggedColor, }; #[derive(Clone, Copy, Debug)] @@ -24,7 +24,7 @@ pub struct CssColor { pub struct Interpolator { premul1: [f32; 3], alpha1: f32, - premul2: [f32; 3], + delta_premul: [f32; 3], alpha2: f32, cs: ColorspaceTag, missing: Bitset, @@ -48,12 +48,12 @@ impl CssColor { } } - pub fn to_alpha_color(self) -> AlphaColor { + pub fn to_alpha_color(self) -> AlphaColor { self.to_tagged_color().to_alpha_color() } #[must_use] - pub fn from_alpha_color(color: AlphaColor) -> Self { + pub fn from_alpha_color(color: AlphaColor) -> Self { TaggedColor::from_alpha_color(color).into() } @@ -140,7 +140,7 @@ impl CssColor { // and there is some controversy in discussion threads. For example, // in Lab-like spaces, if L is 0 do the other components become powerless? const POWERLESS_EPSILON: f32 = 1e-6; - if self.cs.layout() != ColorspaceLayout::Rectangular + if self.cs.layout() != ColorSpaceLayout::Rectangular && self.components[1] < POWERLESS_EPSILON { self.cs @@ -175,10 +175,15 @@ impl CssColor { let (premul1, alpha1) = a.premultiply_split(); let (mut premul2, alpha2) = b.premultiply_split(); fixup_hues_for_interpolate(premul1, &mut premul2, cs.layout(), direction); + let delta_premul = [ + premul2[0] - premul1[0], + premul2[1] - premul1[1], + premul2[2] - premul1[2], + ]; Interpolator { premul1, alpha1, - premul2, + delta_premul, alpha2, cs, missing, @@ -202,9 +207,9 @@ impl CssColor { impl Interpolator { pub fn eval(&self, t: f32) -> CssColor { let premul = [ - self.premul1[0] + t * (self.premul2[0] - self.premul1[0]), - self.premul1[1] + t * (self.premul2[1] - self.premul1[1]), - self.premul1[2] + t * (self.premul2[2] - self.premul1[2]), + self.premul1[0] + t * self.delta_premul[0], + self.premul1[1] + t * self.delta_premul[1], + self.premul1[2] + t * self.delta_premul[2], ]; let alpha = self.alpha1 + t * (self.alpha2 - self.alpha1); let opaque = if alpha == 0.0 || alpha == 1.0 { diff --git a/color/src/floatfuncs.rs b/color/src/floatfuncs.rs index fbfb76d..2b932f6 100644 --- a/color/src/floatfuncs.rs +++ b/color/src/floatfuncs.rs @@ -7,7 +7,7 @@ macro_rules! define_float_funcs { ($( fn $name:ident(self $(,$arg:ident: $arg_ty:ty)*) -> $ret:ty - => $lname:ident/$lfname:ident; + => $lfname:ident; )+) => { /// Since core doesn't depend upon libm, this provides libm implementations @@ -35,17 +35,17 @@ macro_rules! define_float_funcs { } define_float_funcs! { - fn abs(self) -> Self => fabs/fabsf; - fn atan2(self, other: Self) -> Self => atan2/atan2f; - fn cbrt(self) -> Self => cbrt/cbrtf; - fn ceil(self) -> Self => ceil/ceilf; - fn copysign(self, sign: Self) -> Self => copysign/copysignf; - fn floor(self) -> Self => floor/floorf; - fn hypot(self, other: Self) -> Self => hypot/hypotf; + fn abs(self) -> Self => fabsf; + fn atan2(self, other: Self) -> Self => atan2f; + fn cbrt(self) -> Self => cbrtf; + fn ceil(self) -> Self => ceilf; + fn copysign(self, sign: Self) -> Self => copysignf; + fn floor(self) -> Self => floorf; + fn hypot(self, other: Self) -> Self => hypotf; // Note: powi is missing because its libm implementation is not efficient - fn powf(self, n: Self) -> Self => pow/powf; - fn round(self) -> Self => round/roundf; - fn sin_cos(self) -> (Self, Self) => sincos/sincosf; - fn sqrt(self) -> Self => sqrt/sqrtf; - fn trunc(self) -> Self => trunc/truncf; + fn powf(self, n: Self) -> Self => powf; + fn round(self) -> Self => roundf; + fn sin_cos(self) -> (Self, Self) => sincosf; + fn sqrt(self) -> Self => sqrtf; + fn trunc(self) -> Self => truncf; } diff --git a/color/src/gradient.rs b/color/src/gradient.rs index 2911ed3..0286ab5 100644 --- a/color/src/gradient.rs +++ b/color/src/gradient.rs @@ -1,10 +1,10 @@ // Copyright 2024 the Color Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::{Colorspace, ColorspaceTag, CssColor, HueDirection, Interpolator, Oklab, PremulColor}; +use crate::{ColorSpace, ColorspaceTag, CssColor, HueDirection, Interpolator, Oklab, PremulColor}; #[expect(missing_debug_implementations, reason = "it's an iterator")] -pub struct GradientIter { +pub struct GradientIter { interpolator: Interpolator, // This is in deltaEOK units tolerance: f32, @@ -16,7 +16,7 @@ pub struct GradientIter { end_color: PremulColor, } -pub fn gradient( +pub fn gradient( mut color0: CssColor, mut color1: CssColor, interp_cs: ColorspaceTag, @@ -44,7 +44,7 @@ pub fn gradient( } } -impl Iterator for GradientIter { +impl Iterator for GradientIter { type Item = (f32, PremulColor); fn next(&mut self) -> Option { diff --git a/color/src/lib.rs b/color/src/lib.rs index f854196..63c79c8 100644 --- a/color/src/lib.rs +++ b/color/src/lib.rs @@ -36,7 +36,7 @@ mod floatfuncs; pub use bitset::Bitset; pub use color::{AlphaColor, HueDirection, OpaqueColor, PremulColor}; pub use colorspace::{ - Colorspace, ColorspaceLayout, DisplayP3, LinearSrgb, Oklab, Oklch, Srgb, XyzD65, + ColorSpace, ColorSpaceLayout, DisplayP3, LinearSrgb, Oklab, Oklch, Srgb, XyzD65, }; pub use css::{CssColor, Interpolator}; pub use gradient::{gradient, GradientIter}; diff --git a/color/src/tagged.rs b/color/src/tagged.rs index dc3e9d9..3f524c1 100644 --- a/color/src/tagged.rs +++ b/color/src/tagged.rs @@ -1,23 +1,23 @@ // Copyright 2024 the Color Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Colors with runtime choice of colorspace. +//! Colors with runtime choice of color space. use crate::{ color::{add_alpha, split_alpha}, - AlphaColor, Bitset, Colorspace, ColorspaceLayout, DisplayP3, LinearSrgb, Oklab, Oklch, Srgb, + AlphaColor, Bitset, ColorSpace, ColorSpaceLayout, DisplayP3, LinearSrgb, Oklab, Oklch, Srgb, XyzD65, }; -/// The colorspace tag for tagged colors. +/// The colo rspace tag for tagged colors. /// -/// This represents a fixed set of known colorspaces. The set is +/// This represents a fixed set of known color spaces. The set is /// based on the CSS Color 4 spec, but might also extend to a small -/// set of colorspaces used in 3D graphics. +/// set of color spaces used in 3D graphics. /// /// Note: this has some tags not yet implemented. /// -/// Note: when adding an RGB-like colorspace, add to `same_analogous`. +/// Note: when adding an RGB-like color space, add to `same_analogous`. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[non_exhaustive] pub enum ColorspaceTag { @@ -33,7 +33,7 @@ pub enum ColorspaceTag { XyzD65, } -/// A color with a runtime colorspace tag. This type will likely get merged with +/// A color with a runtime color space tag. This type will likely get merged with /// [`CssColor`][crate::css::CssColor]. #[derive(Clone, Copy, Debug)] pub struct TaggedColor { @@ -42,15 +42,15 @@ pub struct TaggedColor { } impl ColorspaceTag { - pub(crate) fn layout(self) -> ColorspaceLayout { + pub(crate) fn layout(self) -> ColorSpaceLayout { match self { - Self::Lch | Self::Oklch => ColorspaceLayout::HueThird, - Self::Hsl | Self::Hwb => ColorspaceLayout::HueFirst, - _ => ColorspaceLayout::Rectangular, + Self::Lch | Self::Oklch => ColorSpaceLayout::HueThird, + Self::Hsl | Self::Hwb => ColorSpaceLayout::HueFirst, + _ => ColorSpaceLayout::Rectangular, } } - // Note: if colorspaces are the same, then they're also analogous, but + // Note: if color spaces are the same, then they're also analogous, but // in that case we wouldn't do the conversion, so this function is not // guaranteed to return the correct answer in those cases. pub(crate) fn same_analogous(self, other: Self) -> bool { @@ -170,8 +170,8 @@ impl TaggedColor { Self { cs, components } } - pub fn from_alpha_color(color: AlphaColor) -> Self { - if let Some(cs) = T::CS_TAG { + pub fn from_alpha_color(color: AlphaColor) -> Self { + if let Some(cs) = CS::TAG { Self { cs, components: color.components, @@ -185,13 +185,13 @@ impl TaggedColor { } } - pub fn to_alpha_color(&self) -> AlphaColor { - if T::CS_TAG == Some(self.cs) { + pub fn to_alpha_color(&self) -> AlphaColor { + if CS::TAG == Some(self.cs) { AlphaColor::new(self.components) } else { let (opaque, alpha) = split_alpha(self.components); let rgb = self.cs.to_linear_srgb(opaque); - let components = add_alpha(T::from_linear_srgb(rgb), alpha); + let components = add_alpha(CS::from_linear_srgb(rgb), alpha); AlphaColor::new(components) } }