diff --git a/crates/bevy_color/crates/gen_tests/src/main.rs b/crates/bevy_color/crates/gen_tests/src/main.rs index bd26e6921c..c58c2d8501 100644 --- a/crates/bevy_color/crates/gen_tests/src/main.rs +++ b/crates/bevy_color/crates/gen_tests/src/main.rs @@ -1,4 +1,4 @@ -use palette::{Hsl, Hsv, Hwb, IntoColor, Lch, LinSrgb, Oklab, Srgb, Xyz}; +use palette::{Hsl, Hsv, Hwb, IntoColor, Lab, Lch, LinSrgb, Oklab, Srgb, Xyz}; const TEST_COLORS: &[(f32, f32, f32, &str)] = &[ (0., 0., 0., "black"), @@ -25,7 +25,7 @@ fn main() { println!( "// Generated by gen_tests. Do not edit. #[cfg(test)] -use crate::{{Hsla, Hsva, Hwba, Srgba, LinearRgba, Oklaba, Lcha, Xyza}}; +use crate::{{Hsla, Hsva, Hwba, Srgba, LinearRgba, Oklaba, Laba, Lcha, Xyza}}; #[cfg(test)] pub struct TestColor {{ @@ -35,6 +35,7 @@ pub struct TestColor {{ pub hsl: Hsla, pub hsv: Hsva, pub hwb: Hwba, + pub lab: Laba, pub lch: Lcha, pub oklab: Oklaba, pub xyz: Xyza, @@ -51,6 +52,7 @@ pub struct TestColor {{ let hsl: Hsl = srgb.into_color(); let hsv: Hsv = srgb.into_color(); let hwb: Hwb = srgb.into_color(); + let lab: Lab = srgb.into_color(); let lch: Lch = srgb.into_color(); let oklab: Oklab = srgb.into_color(); let xyz: Xyz = srgb.into_color(); @@ -63,6 +65,7 @@ pub struct TestColor {{ hsl: Hsla::new({}, {}, {}, 1.0), hsv: Hsva::new({}, {}, {}, 1.0), hwb: Hwba::new({}, {}, {}, 1.0), + lab: Laba::new({}, {}, {}, 1.0), lch: Lcha::new({}, {}, {}, 1.0), oklab: Oklaba::new({}, {}, {}, 1.0), xyz: Xyza::new({}, {}, {}, 1.0), @@ -82,6 +85,9 @@ pub struct TestColor {{ VariablePrecision(hwb.hue.into_positive_degrees()), VariablePrecision(hwb.whiteness), VariablePrecision(hwb.blackness), + VariablePrecision(lab.l / 100.0), + VariablePrecision(lab.a / 100.0), + VariablePrecision(lab.b / 100.0), VariablePrecision(lch.l / 100.0), VariablePrecision(lch.chroma / 100.0), VariablePrecision(lch.hue.into_positive_degrees()), diff --git a/crates/bevy_color/src/color.rs b/crates/bevy_color/src/color.rs index 8cde1b2282..d1d24e2833 100644 --- a/crates/bevy_color/src/color.rs +++ b/crates/bevy_color/src/color.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, Hsla, Hsva, Hwba, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza}; +use crate::{Alpha, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -19,6 +19,8 @@ pub enum Color { Hsva(Hsva), /// A color in the HWB color space with alpha. Hwba(Hwba), + /// A color in the LAB color space with alpha. + Laba(Laba), /// A color in the LCH color space with alpha. Lcha(Lcha), /// A color in the Oklaba color space with alpha. @@ -52,6 +54,7 @@ impl Alpha for Color { Color::Hsla(x) => *x = x.with_alpha(alpha), Color::Hsva(x) => *x = x.with_alpha(alpha), Color::Hwba(x) => *x = x.with_alpha(alpha), + Color::Laba(x) => *x = x.with_alpha(alpha), Color::Lcha(x) => *x = x.with_alpha(alpha), Color::Oklaba(x) => *x = x.with_alpha(alpha), Color::Xyza(x) => *x = x.with_alpha(alpha), @@ -67,6 +70,7 @@ impl Alpha for Color { Color::Hsla(x) => x.alpha(), Color::Hsva(x) => x.alpha(), Color::Hwba(x) => x.alpha(), + Color::Laba(x) => x.alpha(), Color::Lcha(x) => x.alpha(), Color::Oklaba(x) => x.alpha(), Color::Xyza(x) => x.alpha(), @@ -116,6 +120,12 @@ impl From for Color { } } +impl From for Color { + fn from(value: Laba) -> Self { + Self::Laba(value) + } +} + impl From for Color { fn from(value: Xyza) -> Self { Self::Xyza(value) @@ -130,6 +140,7 @@ impl From for Srgba { Color::Hsla(hsla) => hsla.into(), Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(lcha) => lcha.into(), Color::Oklaba(oklab) => oklab.into(), Color::Xyza(xyza) => xyza.into(), @@ -145,6 +156,7 @@ impl From for LinearRgba { Color::Hsla(hsla) => hsla.into(), Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(lcha) => lcha.into(), Color::Oklaba(oklab) => oklab.into(), Color::Xyza(xyza) => xyza.into(), @@ -160,6 +172,7 @@ impl From for Hsla { Color::Hsla(hsla) => hsla, Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(lcha) => lcha.into(), Color::Oklaba(oklab) => oklab.into(), Color::Xyza(xyza) => xyza.into(), @@ -175,6 +188,7 @@ impl From for Hsva { Color::Hsla(hsla) => hsla.into(), Color::Hsva(hsva) => hsva, Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(lcha) => lcha.into(), Color::Oklaba(oklab) => oklab.into(), Color::Xyza(xyza) => xyza.into(), @@ -190,6 +204,23 @@ impl From for Hwba { Color::Hsla(hsla) => hsla.into(), Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba, + Color::Laba(laba) => laba.into(), + Color::Lcha(lcha) => lcha.into(), + Color::Oklaba(oklab) => oklab.into(), + Color::Xyza(xyza) => xyza.into(), + } + } +} + +impl From for Laba { + fn from(value: Color) -> Self { + match value { + Color::Srgba(srgba) => srgba.into(), + Color::LinearRgba(linear) => linear.into(), + Color::Hsla(hsla) => hsla.into(), + Color::Hsva(hsva) => hsva.into(), + Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba, Color::Lcha(lcha) => lcha.into(), Color::Oklaba(oklab) => oklab.into(), Color::Xyza(xyza) => xyza.into(), @@ -205,6 +236,7 @@ impl From for Lcha { Color::Hsla(hsla) => hsla.into(), Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(lcha) => lcha, Color::Oklaba(oklab) => oklab.into(), Color::Xyza(xyza) => xyza.into(), @@ -220,6 +252,7 @@ impl From for Oklaba { Color::Hsla(hsla) => hsla.into(), Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(lcha) => lcha.into(), Color::Oklaba(oklab) => oklab, Color::Xyza(xyza) => xyza.into(), @@ -235,6 +268,7 @@ impl From for Xyza { Color::Hsla(x) => x.into(), Color::Hsva(hsva) => hsva.into(), Color::Hwba(hwba) => hwba.into(), + Color::Laba(laba) => laba.into(), Color::Lcha(x) => x.into(), Color::Oklaba(x) => x.into(), Color::Xyza(xyza) => xyza, diff --git a/crates/bevy_color/src/hsla.rs b/crates/bevy_color/src/hsla.rs index 462562a2a3..3739c4b144 100644 --- a/crates/bevy_color/src/hsla.rs +++ b/crates/bevy_color/src/hsla.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor}; +use crate::{Alpha, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -170,12 +170,20 @@ impl From for Hsla { } } +// Derived Conversions + impl From for Hsla { fn from(value: Hwba) -> Self { Hsva::from(value).into() } } +impl From for Hwba { + fn from(value: Hsla) -> Self { + Hsva::from(value).into() + } +} + impl From for Hsla { fn from(value: Srgba) -> Self { Hsva::from(value).into() @@ -188,20 +196,14 @@ impl From for Srgba { } } -impl From for Hwba { - fn from(value: Hsla) -> Self { - Hsva::from(value).into() - } -} - impl From for Hsla { fn from(value: LinearRgba) -> Self { Hsva::from(value).into() } } -impl From for Hsla { - fn from(value: Oklaba) -> Self { +impl From for LinearRgba { + fn from(value: Hsla) -> Self { Hsva::from(value).into() } } @@ -212,6 +214,24 @@ impl From for Hsla { } } +impl From for Lcha { + fn from(value: Hsla) -> Self { + Hsva::from(value).into() + } +} + +impl From for Hsla { + fn from(value: Xyza) -> Self { + Hsva::from(value).into() + } +} + +impl From for Xyza { + fn from(value: Hsla) -> Self { + Hsva::from(value).into() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_color/src/hsva.rs b/crates/bevy_color/src/hsva.rs index 30f204ed1d..0583491e68 100644 --- a/crates/bevy_color/src/hsva.rs +++ b/crates/bevy_color/src/hsva.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, Hwba, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza}; +use crate::{Alpha, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -116,6 +116,8 @@ impl From for Hsva { } } +// Derived Conversions + impl From for Hsva { fn from(value: Srgba) -> Self { Hwba::from(value).into() @@ -152,18 +154,6 @@ impl From for Lcha { } } -impl From for Hsva { - fn from(value: Oklaba) -> Self { - Hwba::from(value).into() - } -} - -impl From for Oklaba { - fn from(value: Hsva) -> Self { - Hwba::from(value).into() - } -} - impl From for Hsva { fn from(value: Xyza) -> Self { Hwba::from(value).into() diff --git a/crates/bevy_color/src/hwba.rs b/crates/bevy_color/src/hwba.rs index b6c7003a2a..5105e59d6a 100644 --- a/crates/bevy_color/src/hwba.rs +++ b/crates/bevy_color/src/hwba.rs @@ -2,7 +2,7 @@ //! in [_HWB - A More Intuitive Hue-Based Color Model_] by _Smith et al_. //! //! [_HWB - A More Intuitive Hue-Based Color Model_]: https://web.archive.org/web/20240226005220/http://alvyray.com/Papers/CG/HWB_JGTv208.pdf -use crate::{Alpha, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza}; +use crate::{Alpha, Lcha, LinearRgba, Srgba, StandardColor, Xyza}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -161,6 +161,8 @@ impl From for Srgba { } } +// Derived Conversions + impl From for Hwba { fn from(value: LinearRgba) -> Self { Srgba::from(value).into() @@ -185,18 +187,6 @@ impl From for Lcha { } } -impl From for Hwba { - fn from(value: Oklaba) -> Self { - Srgba::from(value).into() - } -} - -impl From for Oklaba { - fn from(value: Hwba) -> Self { - Srgba::from(value).into() - } -} - impl From for Hwba { fn from(value: Xyza) -> Self { Srgba::from(value).into() diff --git a/crates/bevy_color/src/laba.rs b/crates/bevy_color/src/laba.rs new file mode 100644 index 0000000000..4b4714a3a8 --- /dev/null +++ b/crates/bevy_color/src/laba.rs @@ -0,0 +1,347 @@ +use crate::{ + Alpha, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza, +}; +use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; +use serde::{Deserialize, Serialize}; + +/// Color in LAB color space, with alpha +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] +#[reflect(PartialEq, Serialize, Deserialize)] +pub struct Laba { + /// The lightness channel. [0.0, 1.5] + pub lightness: f32, + /// The a axis. [-1.5, 1.5] + pub a: f32, + /// The b axis. [-1.5, 1.5] + pub b: f32, + /// The alpha channel. [0.0, 1.0] + pub alpha: f32, +} + +impl StandardColor for Laba {} + +impl Laba { + /// Construct a new [`Laba`] color from components. + /// + /// # Arguments + /// + /// * `lightness` - Lightness channel. [0.0, 1.5] + /// * `a` - a axis. [-1.5, 1.5] + /// * `b` - b axis. [-1.5, 1.5] + /// * `alpha` - Alpha channel. [0.0, 1.0] + pub const fn new(lightness: f32, a: f32, b: f32, alpha: f32) -> Self { + Self { + lightness, + a, + b, + alpha, + } + } + + /// Construct a new [`Laba`] color from (l, a, b) components, with the default alpha (1.0). + /// + /// # Arguments + /// + /// * `lightness` - Lightness channel. [0.0, 1.5] + /// * `a` - a axis. [-1.5, 1.5] + /// * `b` - b axis. [-1.5, 1.5] + pub const fn lab(lightness: f32, a: f32, b: f32) -> Self { + Self { + lightness, + a, + b, + alpha: 1.0, + } + } + + /// Return a copy of this color with the lightness channel set to the given value. + pub const fn with_lightness(self, lightness: f32) -> Self { + Self { lightness, ..self } + } + + /// CIE Epsilon Constant + /// + /// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html) + pub const CIE_EPSILON: f32 = 216.0 / 24389.0; + + /// CIE Kappa Constant + /// + /// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html) + pub const CIE_KAPPA: f32 = 24389.0 / 27.0; +} + +impl Default for Laba { + fn default() -> Self { + Self::new(1., 0., 0., 1.) + } +} + +impl Mix for Laba { + #[inline] + fn mix(&self, other: &Self, factor: f32) -> Self { + let n_factor = 1.0 - factor; + Self { + lightness: self.lightness * n_factor + other.lightness * factor, + a: self.a * n_factor + other.a * factor, + b: self.b * n_factor + other.b * factor, + alpha: self.alpha * n_factor + other.alpha * factor, + } + } +} + +impl Alpha for Laba { + #[inline] + fn with_alpha(&self, alpha: f32) -> Self { + Self { alpha, ..*self } + } + + #[inline] + fn alpha(&self) -> f32 { + self.alpha + } +} + +impl Luminance for Laba { + #[inline] + fn with_luminance(&self, lightness: f32) -> Self { + Self { lightness, ..*self } + } + + fn luminance(&self) -> f32 { + self.lightness + } + + fn darker(&self, amount: f32) -> Self { + Self::new( + (self.lightness - amount).max(0.), + self.a, + self.b, + self.alpha, + ) + } + + fn lighter(&self, amount: f32) -> Self { + Self::new( + (self.lightness + amount).min(1.), + self.a, + self.b, + self.alpha, + ) + } +} + +impl From for Xyza { + fn from( + Laba { + lightness, + a, + b, + alpha, + }: Laba, + ) -> Self { + // Based on http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html + let l = 100. * lightness; + let a = 100. * a; + let b = 100. * b; + + let fy = (l + 16.0) / 116.0; + let fx = a / 500.0 + fy; + let fz = fy - b / 200.0; + let xr = { + let fx3 = fx.powf(3.0); + + if fx3 > Laba::CIE_EPSILON { + fx3 + } else { + (116.0 * fx - 16.0) / Laba::CIE_KAPPA + } + }; + let yr = if l > Laba::CIE_EPSILON * Laba::CIE_KAPPA { + ((l + 16.0) / 116.0).powf(3.0) + } else { + l / Laba::CIE_KAPPA + }; + let zr = { + let fz3 = fz.powf(3.0); + + if fz3 > Laba::CIE_EPSILON { + fz3 + } else { + (116.0 * fz - 16.0) / Laba::CIE_KAPPA + } + }; + let x = xr * Xyza::D65_WHITE.x; + let y = yr * Xyza::D65_WHITE.y; + let z = zr * Xyza::D65_WHITE.z; + + Xyza::new(x, y, z, alpha) + } +} + +impl From for Laba { + fn from(Xyza { x, y, z, alpha }: Xyza) -> Self { + // Based on http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html + let xr = x / Xyza::D65_WHITE.x; + let yr = y / Xyza::D65_WHITE.y; + let zr = z / Xyza::D65_WHITE.z; + let fx = if xr > Laba::CIE_EPSILON { + xr.cbrt() + } else { + (Laba::CIE_KAPPA * xr + 16.0) / 116.0 + }; + let fy = if yr > Laba::CIE_EPSILON { + yr.cbrt() + } else { + (Laba::CIE_KAPPA * yr + 16.0) / 116.0 + }; + let fz = if yr > Laba::CIE_EPSILON { + zr.cbrt() + } else { + (Laba::CIE_KAPPA * zr + 16.0) / 116.0 + }; + let l = 1.16 * fy - 0.16; + let a = 5.00 * (fx - fy); + let b = 2.00 * (fy - fz); + + Laba::new(l, a, b, alpha) + } +} + +// Derived Conversions + +impl From for Laba { + fn from(value: Srgba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Srgba { + fn from(value: Laba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Laba { + fn from(value: LinearRgba) -> Self { + Xyza::from(value).into() + } +} + +impl From for LinearRgba { + fn from(value: Laba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Laba { + fn from(value: Hsla) -> Self { + Xyza::from(value).into() + } +} + +impl From for Hsla { + fn from(value: Laba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Laba { + fn from(value: Hsva) -> Self { + Xyza::from(value).into() + } +} + +impl From for Hsva { + fn from(value: Laba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Laba { + fn from(value: Hwba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Hwba { + fn from(value: Laba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Laba { + fn from(value: Oklaba) -> Self { + Xyza::from(value).into() + } +} + +impl From for Oklaba { + fn from(value: Laba) -> Self { + Xyza::from(value).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq, + Srgba, + }; + + #[test] + fn test_to_from_srgba() { + for color in TEST_COLORS.iter() { + let rgb2: Srgba = (color.lab).into(); + let laba: Laba = (color.rgb).into(); + assert!( + color.rgb.distance(&rgb2) < 0.0001, + "{}: {:?} != {:?}", + color.name, + color.rgb, + rgb2 + ); + assert_approx_eq!(color.lab.lightness, laba.lightness, 0.001); + if laba.lightness > 0.01 { + assert_approx_eq!(color.lab.a, laba.a, 0.1); + } + if laba.lightness > 0.01 && laba.a > 0.01 { + assert!( + (color.lab.b - laba.b).abs() < 1.7, + "{:?} != {:?}", + color.lab, + laba + ); + } + assert_approx_eq!(color.lab.alpha, laba.alpha, 0.001); + } + } + + #[test] + fn test_to_from_linear() { + for color in TEST_COLORS.iter() { + let rgb2: LinearRgba = (color.lab).into(); + let laba: Laba = (color.linear_rgb).into(); + assert!( + color.linear_rgb.distance(&rgb2) < 0.0001, + "{}: {:?} != {:?}", + color.name, + color.linear_rgb, + rgb2 + ); + assert_approx_eq!(color.lab.lightness, laba.lightness, 0.001); + if laba.lightness > 0.01 { + assert_approx_eq!(color.lab.a, laba.a, 0.1); + } + if laba.lightness > 0.01 && laba.a > 0.01 { + assert!( + (color.lab.b - laba.b).abs() < 1.7, + "{:?} != {:?}", + color.lab, + laba + ); + } + assert_approx_eq!(color.lab.alpha, laba.alpha, 0.001); + } + } +} diff --git a/crates/bevy_color/src/lcha.rs b/crates/bevy_color/src/lcha.rs index c56995a397..2a01147a68 100644 --- a/crates/bevy_color/src/lcha.rs +++ b/crates/bevy_color/src/lcha.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, Hsla, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza}; +use crate::{Alpha, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -66,16 +66,6 @@ impl Lcha { pub const fn with_lightness(self, lightness: f32) -> Self { Self { lightness, ..self } } - - /// CIE Epsilon Constant - /// - /// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html) - pub const CIE_EPSILON: f32 = 216.0 / 24389.0; - - /// CIE Kappa Constant - /// - /// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html) - pub const CIE_KAPPA: f32 = 24389.0 / 27.0; } impl Default for Lcha { @@ -138,7 +128,7 @@ impl Luminance for Lcha { } } -impl From for Xyza { +impl From for Laba { fn from( Lcha { lightness, @@ -147,79 +137,25 @@ impl From for Xyza { alpha, }: Lcha, ) -> Self { - let lightness = lightness * 100.0; - let chroma = chroma * 100.0; - - // convert LCH to Lab - // http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html + // Based on http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html let l = lightness; let a = chroma * hue.to_radians().cos(); let b = chroma * hue.to_radians().sin(); - // convert Lab to XYZ - // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html - let fy = (l + 16.0) / 116.0; - let fx = a / 500.0 + fy; - let fz = fy - b / 200.0; - let xr = { - let fx3 = fx.powf(3.0); - - if fx3 > Lcha::CIE_EPSILON { - fx3 - } else { - (116.0 * fx - 16.0) / Lcha::CIE_KAPPA - } - }; - let yr = if l > Lcha::CIE_EPSILON * Lcha::CIE_KAPPA { - ((l + 16.0) / 116.0).powf(3.0) - } else { - l / Lcha::CIE_KAPPA - }; - let zr = { - let fz3 = fz.powf(3.0); - - if fz3 > Lcha::CIE_EPSILON { - fz3 - } else { - (116.0 * fz - 16.0) / Lcha::CIE_KAPPA - } - }; - let x = xr * Xyza::D65_WHITE.x; - let y = yr * Xyza::D65_WHITE.y; - let z = zr * Xyza::D65_WHITE.z; - - Xyza::new(x, y, z, alpha) + Laba::new(l, a, b, alpha) } } -impl From for Lcha { - fn from(Xyza { x, y, z, alpha }: Xyza) -> Self { - // XYZ to Lab - // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html - let xr = x / Xyza::D65_WHITE.x; - let yr = y / Xyza::D65_WHITE.y; - let zr = z / Xyza::D65_WHITE.z; - let fx = if xr > Lcha::CIE_EPSILON { - xr.cbrt() - } else { - (Lcha::CIE_KAPPA * xr + 16.0) / 116.0 - }; - let fy = if yr > Lcha::CIE_EPSILON { - yr.cbrt() - } else { - (Lcha::CIE_KAPPA * yr + 16.0) / 116.0 - }; - let fz = if yr > Lcha::CIE_EPSILON { - zr.cbrt() - } else { - (Lcha::CIE_KAPPA * zr + 16.0) / 116.0 - }; - let l = 116.0 * fy - 16.0; - let a = 500.0 * (fx - fy); - let b = 200.0 * (fy - fz); - - // Lab to LCH - // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html +impl From for Lcha { + fn from( + Laba { + lightness, + a, + b, + alpha, + }: Laba, + ) -> Self { + // Based on http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html let c = (a.powf(2.0) + b.powf(2.0)).sqrt(); let h = { let h = b.to_radians().atan2(a.to_radians()).to_degrees(); @@ -231,47 +167,48 @@ impl From for Lcha { } }; - let lightness = (l / 100.0).clamp(0.0, 1.5); - let chroma = (c / 100.0).clamp(0.0, 1.5); + let chroma = c.clamp(0.0, 1.5); let hue = h; Lcha::new(lightness, chroma, hue, alpha) } } +// Derived Conversions + impl From for Lcha { fn from(value: Srgba) -> Self { - Xyza::from(value).into() + Laba::from(value).into() } } impl From for Srgba { fn from(value: Lcha) -> Self { - Xyza::from(value).into() + Laba::from(value).into() } } impl From for Lcha { fn from(value: LinearRgba) -> Self { - Srgba::from(value).into() + Laba::from(value).into() } } impl From for LinearRgba { fn from(value: Lcha) -> Self { - LinearRgba::from(Srgba::from(value)) + Laba::from(value).into() } } -impl From for Lcha { - fn from(value: Oklaba) -> Self { - Srgba::from(value).into() +impl From for Lcha { + fn from(value: Xyza) -> Self { + Laba::from(value).into() } } -impl From for Lcha { - fn from(value: Hsla) -> Self { - Srgba::from(value).into() +impl From for Xyza { + fn from(value: Lcha) -> Self { + Laba::from(value).into() } } diff --git a/crates/bevy_color/src/lib.rs b/crates/bevy_color/src/lib.rs index 3bdce2df24..a90c2fd627 100644 --- a/crates/bevy_color/src/lib.rs +++ b/crates/bevy_color/src/lib.rs @@ -7,6 +7,7 @@ //! - [`Hsla`] (hue, saturation, lightness, alpha) //! - [`Hsva`] (hue, saturation, value, alpha) //! - [`Hwba`] (hue, whiteness, blackness, alpha) +//! - [`Laba`] (lightness, a-axis, b-axis, alpha) //! - [`Lcha`] (lightness, chroma, hue, alpha) //! - [`Oklaba`] (lightness, a-axis, b-axis, alpha) //! - [`Xyza`] (x-axis, y-axis, z-axis, alpha) @@ -83,6 +84,7 @@ mod color_range; mod hsla; mod hsva; mod hwba; +mod laba; mod lcha; mod linear_rgba; mod oklaba; @@ -100,6 +102,7 @@ pub use color_range::*; pub use hsla::*; pub use hsva::*; pub use hwba::*; +pub use laba::*; pub use lcha::*; pub use linear_rgba::*; pub use oklaba::*; @@ -122,6 +125,7 @@ where Self: From + Into, Self: From + Into, Self: From + Into, + Self: From + Into, Self: From + Into, Self: From + Into, Self: From + Into, diff --git a/crates/bevy_color/src/linear_rgba.rs b/crates/bevy_color/src/linear_rgba.rs index 375d910c12..9a682b2ac9 100644 --- a/crates/bevy_color/src/linear_rgba.rs +++ b/crates/bevy_color/src/linear_rgba.rs @@ -1,7 +1,4 @@ -use crate::{ - color_difference::EuclideanDistance, oklaba::Oklaba, Alpha, Hsla, Luminance, Mix, Srgba, - StandardColor, -}; +use crate::{color_difference::EuclideanDistance, Alpha, Luminance, Mix, StandardColor}; use bevy_math::Vec4; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -202,40 +199,6 @@ impl From for Vec4 { } } -#[allow(clippy::excessive_precision)] -impl From for LinearRgba { - fn from(value: Oklaba) -> Self { - let Oklaba { l, a, b, alpha } = value; - - // From https://github.com/Ogeon/palette/blob/e75eab2fb21af579353f51f6229a510d0d50a311/palette/src/oklab.rs#L312-L332 - let l_ = l + 0.3963377774 * a + 0.2158037573 * b; - let m_ = l - 0.1055613458 * a - 0.0638541728 * b; - let s_ = l - 0.0894841775 * a - 1.2914855480 * b; - - let l = l_ * l_ * l_; - let m = m_ * m_ * m_; - let s = s_ * s_ * s_; - - let red = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; - let green = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; - let blue = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; - - Self { - red, - green, - blue, - alpha, - } - } -} - -impl From for LinearRgba { - #[inline] - fn from(value: Hsla) -> Self { - LinearRgba::from(Srgba::from(value)) - } -} - impl From for wgpu::Color { fn from(color: LinearRgba) -> Self { wgpu::Color { diff --git a/crates/bevy_color/src/oklaba.rs b/crates/bevy_color/src/oklaba.rs index dceac2157e..5913dcbfa5 100644 --- a/crates/bevy_color/src/oklaba.rs +++ b/crates/bevy_color/src/oklaba.rs @@ -1,6 +1,6 @@ use crate::{ - color_difference::EuclideanDistance, Alpha, Hsla, Lcha, LinearRgba, Luminance, Mix, Srgba, - StandardColor, + color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, + Srgba, StandardColor, Xyza, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -146,9 +146,68 @@ impl From for Oklaba { } } -impl From for Oklaba { - fn from(value: Srgba) -> Self { - Oklaba::from(LinearRgba::from(value)) +#[allow(clippy::excessive_precision)] +impl From for LinearRgba { + fn from(value: Oklaba) -> Self { + let Oklaba { l, a, b, alpha } = value; + + // From https://github.com/Ogeon/palette/blob/e75eab2fb21af579353f51f6229a510d0d50a311/palette/src/oklab.rs#L312-L332 + let l_ = l + 0.3963377774 * a + 0.2158037573 * b; + let m_ = l - 0.1055613458 * a - 0.0638541728 * b; + let s_ = l - 0.0894841775 * a - 1.2914855480 * b; + + let l = l_ * l_ * l_; + let m = m_ * m_ * m_; + let s = s_ * s_ * s_; + + let red = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; + let green = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; + let blue = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; + + Self { + red, + green, + blue, + alpha, + } + } +} + +// Derived Conversions + +impl From for Oklaba { + fn from(value: Hsla) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Hsla { + fn from(value: Oklaba) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Oklaba { + fn from(value: Hsva) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Hsva { + fn from(value: Oklaba) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Oklaba { + fn from(value: Hwba) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Hwba { + fn from(value: Oklaba) -> Self { + LinearRgba::from(value).into() } } @@ -158,8 +217,32 @@ impl From for Oklaba { } } -impl From for Oklaba { - fn from(value: Hsla) -> Self { +impl From for Lcha { + fn from(value: Oklaba) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Oklaba { + fn from(value: Srgba) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Srgba { + fn from(value: Oklaba) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Oklaba { + fn from(value: Xyza) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Xyza { + fn from(value: Oklaba) -> Self { LinearRgba::from(value).into() } } diff --git a/crates/bevy_color/src/srgba.rs b/crates/bevy_color/src/srgba.rs index 761496d334..696484e7ce 100644 --- a/crates/bevy_color/src/srgba.rs +++ b/crates/bevy_color/src/srgba.rs @@ -1,6 +1,5 @@ use crate::color_difference::EuclideanDistance; -use crate::oklaba::Oklaba; -use crate::{Alpha, LinearRgba, Luminance, Mix, StandardColor}; +use crate::{Alpha, LinearRgba, Luminance, Mix, StandardColor, Xyza}; use bevy_math::Vec4; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -297,12 +296,6 @@ impl From for LinearRgba { } } -impl From for Srgba { - fn from(value: Oklaba) -> Self { - Srgba::from(LinearRgba::from(value)) - } -} - impl From for [f32; 4] { fn from(color: Srgba) -> Self { [color.red, color.green, color.blue, color.alpha] @@ -315,6 +308,20 @@ impl From for Vec4 { } } +// Derived Conversions + +impl From for Srgba { + fn from(value: Xyza) -> Self { + LinearRgba::from(value).into() + } +} + +impl From for Xyza { + fn from(value: Srgba) -> Self { + LinearRgba::from(value).into() + } +} + /// Error returned if a hex string could not be parsed as a color. #[derive(Debug, Error, PartialEq, Eq)] pub enum HexColorError { diff --git a/crates/bevy_color/src/test_colors.rs b/crates/bevy_color/src/test_colors.rs index d1b1372241..027eb5e3a7 100644 --- a/crates/bevy_color/src/test_colors.rs +++ b/crates/bevy_color/src/test_colors.rs @@ -1,6 +1,6 @@ // Generated by gen_tests. Do not edit. #[cfg(test)] -use crate::{Hsla, Hsva, Hwba, Lcha, LinearRgba, Oklaba, Srgba, Xyza}; +use crate::{Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Srgba, Xyza}; #[cfg(test)] pub struct TestColor { @@ -10,6 +10,7 @@ pub struct TestColor { pub hsl: Hsla, pub hsv: Hsva, pub hwb: Hwba, + pub lab: Laba, pub lch: Lcha, pub oklab: Oklaba, pub xyz: Xyza, @@ -27,6 +28,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.0, 0.0, 0.0000136603785, 1.0), hsv: Hsva::new(0.0, 0.0, 0.0, 1.0), hwb: Hwba::new(0.0, 0.0, 1.0, 1.0), + lab: Laba::new(0.0, 0.0, 0.0, 1.0), oklab: Oklaba::new(0.0, 0.0, 0.0, 1.0), xyz: Xyza::new(0.0, 0.0, 0.0, 1.0), }, @@ -39,6 +41,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(1.0, 0.0, 0.0000136603785, 1.0), hsv: Hsva::new(0.0, 0.0, 1.0, 1.0), hwb: Hwba::new(0.0, 1.0, 0.0, 1.0), + lab: Laba::new(1.0, 0.0, 0.0, 1.0), oklab: Oklaba::new(1.0, 0.0, 0.000000059604645, 1.0), xyz: Xyza::new(0.95047, 1.0, 1.08883, 1.0), }, @@ -52,6 +55,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.6279554, 0.22486295, 0.1258463, 1.0), hsv: Hsva::new(0.0, 1.0, 1.0, 1.0), hwb: Hwba::new(0.0, 0.0, 0.0, 1.0), + lab: Laba::new(0.532408, 0.8009243, 0.6720321, 1.0), xyz: Xyza::new(0.4124564, 0.2126729, 0.0193339, 1.0), }, // green @@ -63,6 +67,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.87734723, 1.1977587, 136.01595, 1.0), hsv: Hsva::new(120.0, 1.0, 1.0, 1.0), hwb: Hwba::new(120.0, 0.0, 0.0, 1.0), + lab: Laba::new(0.8773472, -0.86182654, 0.8317931, 1.0), oklab: Oklaba::new(0.8664396, -0.2338874, 0.1794985, 1.0), xyz: Xyza::new(0.3575761, 0.7151522, 0.119192, 1.0), }, @@ -76,6 +81,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.4520137, -0.032456964, -0.31152815, 1.0), hsv: Hsva::new(240.0, 1.0, 1.0, 1.0), hwb: Hwba::new(240.0, 0.0, 0.0, 1.0), + lab: Laba::new(0.32297015, 0.7918751, -1.0786015, 1.0), xyz: Xyza::new(0.1804375, 0.072175, 0.9503041, 1.0), }, // yellow @@ -88,6 +94,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.9679827, -0.07136908, 0.19856972, 1.0), hsv: Hsva::new(60.0, 1.0, 1.0, 1.0), hwb: Hwba::new(60.0, 0.0, 0.0, 1.0), + lab: Laba::new(0.9713927, -0.21553725, 0.94477975, 1.0), xyz: Xyza::new(0.7700325, 0.9278251, 0.1385259, 1.0), }, // magenta @@ -98,6 +105,7 @@ pub const TEST_COLORS: &[TestColor] = &[ hsl: Hsla::new(300.0, 1.0, 0.5, 1.0), hsv: Hsva::new(300.0, 1.0, 1.0, 1.0), hwb: Hwba::new(300.0, 0.0, 0.0, 1.0), + lab: Laba::new(0.6032421, 0.9823433, -0.60824895, 1.0), lch: Lcha::new(0.6032421, 1.1554068, 328.23495, 1.0), oklab: Oklaba::new(0.7016738, 0.27456632, -0.16915613, 1.0), xyz: Xyza::new(0.5928939, 0.28484792, 0.969638, 1.0), @@ -112,6 +120,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.90539926, -0.1494439, -0.039398134, 1.0), hsv: Hsva::new(180.0, 1.0, 1.0, 1.0), hwb: Hwba::new(180.0, 0.0, 0.0, 1.0), + lab: Laba::new(0.9111321, -0.4808751, -0.14131188, 1.0), xyz: Xyza::new(0.5380136, 0.78732723, 1.069496, 1.0), }, // gray @@ -124,6 +133,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.5981807, 0.00000011920929, 0.0, 1.0), hsv: Hsva::new(0.0, 0.0, 0.5, 1.0), hwb: Hwba::new(0.0, 0.5, 0.5, 1.0), + lab: Laba::new(0.5338897, 0.0, 0.0, 1.0), xyz: Xyza::new(0.2034397, 0.21404117, 0.23305441, 1.0), }, // olive @@ -135,6 +145,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.51677734, 0.57966936, 102.851265, 1.0), hsv: Hsva::new(60.0, 1.0, 0.5, 1.0), hwb: Hwba::new(60.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.51677734, -0.1289308, 0.5651491, 1.0), oklab: Oklaba::new(0.57902855, -0.042691574, 0.11878061, 1.0), xyz: Xyza::new(0.16481864, 0.19859275, 0.029650241, 1.0), }, @@ -147,6 +158,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.29655674, 0.69114214, 328.23495, 1.0), hsv: Hsva::new(300.0, 1.0, 0.5, 1.0), hwb: Hwba::new(300.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.29655674, 0.58761847, -0.3638428, 1.0), oklab: Oklaba::new(0.41972777, 0.1642403, -0.10118592, 1.0), xyz: Xyza::new(0.12690368, 0.060969174, 0.20754242, 1.0), }, @@ -160,6 +172,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.54159236, -0.08939436, -0.02356726, 1.0), hsv: Hsva::new(180.0, 1.0, 0.5, 1.0), hwb: Hwba::new(180.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.4807306, -0.28765023, -0.084530115, 1.0), xyz: Xyza::new(0.11515705, 0.16852042, 0.22891617, 1.0), }, // maroon @@ -170,6 +183,7 @@ pub const TEST_COLORS: &[TestColor] = &[ hsl: Hsla::new(0.0, 1.0, 0.25, 1.0), hsv: Hsva::new(0.0, 1.0, 0.5, 1.0), hwb: Hwba::new(0.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.2541851, 0.47909766, 0.37905872, 1.0), lch: Lcha::new(0.2541851, 0.61091745, 38.350803, 1.0), oklab: Oklaba::new(0.3756308, 0.13450874, 0.07527886, 1.0), xyz: Xyza::new(0.08828264, 0.045520753, 0.0041382504, 1.0), @@ -182,6 +196,7 @@ pub const TEST_COLORS: &[TestColor] = &[ hsl: Hsla::new(120.0, 1.0, 0.25, 1.0), hsv: Hsva::new(120.0, 1.0, 0.5, 1.0), hwb: Hwba::new(120.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.46052113, -0.5155285, 0.4975627, 1.0), lch: Lcha::new(0.46052113, 0.71647626, 136.01596, 1.0), oklab: Oklaba::new(0.5182875, -0.13990697, 0.10737252, 1.0), xyz: Xyza::new(0.076536, 0.153072, 0.025511991, 1.0), @@ -195,6 +210,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.12890343, 0.8004114, 306.28494, 1.0), hsv: Hsva::new(240.0, 1.0, 0.5, 1.0), hwb: Hwba::new(240.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.12890343, 0.4736844, -0.64519864, 1.0), oklab: Oklaba::new(0.27038592, -0.01941514, -0.18635012, 1.0), xyz: Xyza::new(0.03862105, 0.01544842, 0.20340417, 1.0), }, @@ -207,6 +223,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.51677734, 0.57966936, 102.851265, 1.0), hsv: Hsva::new(60.0, 1.0, 0.5, 1.0), hwb: Hwba::new(60.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.51677734, -0.1289308, 0.5651491, 1.0), oklab: Oklaba::new(0.57902855, -0.042691574, 0.11878061, 1.0), xyz: Xyza::new(0.16481864, 0.19859275, 0.029650241, 1.0), }, @@ -219,6 +236,7 @@ pub const TEST_COLORS: &[TestColor] = &[ lch: Lcha::new(0.29655674, 0.69114214, 328.23495, 1.0), hsv: Hsva::new(300.0, 1.0, 0.5, 1.0), hwb: Hwba::new(300.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.29655674, 0.58761847, -0.3638428, 1.0), oklab: Oklaba::new(0.41972777, 0.1642403, -0.10118592, 1.0), xyz: Xyza::new(0.12690368, 0.060969174, 0.20754242, 1.0), }, @@ -232,6 +250,7 @@ pub const TEST_COLORS: &[TestColor] = &[ oklab: Oklaba::new(0.54159236, -0.08939436, -0.02356726, 1.0), hsv: Hsva::new(180.0, 1.0, 0.5, 1.0), hwb: Hwba::new(180.0, 0.0, 0.5, 1.0), + lab: Laba::new(0.4807306, -0.28765023, -0.084530115, 1.0), xyz: Xyza::new(0.11515705, 0.16852042, 0.22891617, 1.0), }, ]; diff --git a/crates/bevy_color/src/xyza.rs b/crates/bevy_color/src/xyza.rs index 6a11749852..6694ee38e0 100644 --- a/crates/bevy_color/src/xyza.rs +++ b/crates/bevy_color/src/xyza.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, Hsla, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor}; +use crate::{Alpha, LinearRgba, Luminance, Mix, StandardColor}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; @@ -162,42 +162,6 @@ impl From for LinearRgba { } } -impl From for Xyza { - fn from(value: Srgba) -> Self { - LinearRgba::from(value).into() - } -} - -impl From for Srgba { - fn from(value: Xyza) -> Self { - LinearRgba::from(value).into() - } -} - -impl From for Xyza { - fn from(value: Hsla) -> Self { - LinearRgba::from(value).into() - } -} - -impl From for Hsla { - fn from(value: Xyza) -> Self { - LinearRgba::from(value).into() - } -} - -impl From for Xyza { - fn from(value: Oklaba) -> Self { - LinearRgba::from(value).into() - } -} - -impl From for Oklaba { - fn from(value: Xyza) -> Self { - LinearRgba::from(value).into() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index a68204d765..b669abb878 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -1,4 +1,6 @@ -use bevy_color::{Color, HexColorError, Hsla, Hsva, Hwba, Lcha, LinearRgba, Oklaba, Srgba, Xyza}; +use bevy_color::{ + Color, HexColorError, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Srgba, Xyza, +}; use bevy_math::{Vec3, Vec4}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; @@ -924,6 +926,7 @@ impl From for LegacyColor { Color::Hsla(x) => x.into(), Color::Hsva(x) => x.into(), Color::Hwba(x) => x.into(), + Color::Laba(x) => x.into(), Color::Lcha(x) => x.into(), Color::Oklaba(x) => x.into(), Color::Xyza(x) => x.into(), @@ -1032,6 +1035,12 @@ impl From for LegacyColor { } } +impl From for LegacyColor { + fn from(value: Laba) -> Self { + Lcha::from(value).into() + } +} + impl From for LegacyColor { fn from( Lcha { @@ -1056,6 +1065,12 @@ impl From for Lcha { } } +impl From for Laba { + fn from(value: LegacyColor) -> Self { + Color::from(value).into() + } +} + impl From for Oklaba { fn from(value: LegacyColor) -> Self { LinearRgba::from(value).into()