bevy_color: Add Laba to support Lab Color Model (#12115)

# Objective

- Improve compatibility with CSS Module 4
- Simplify `Lcha` conversion functions

## Solution

- Added `Laba` which implements the Lab color model.
- Updated `Color` and `LegacyColor` accordingly.

## Migration Guide

- Convert `Laba` to either `Xyza` or `Lcha` using the provided `From`
implementations and then handle accordingly.

## Notes

The Lab color space is a required stepping stone when converting between
XYZ and Lch, therefore we already use the Lab color model, just in an
nameless fashion prone to errors.

This PR also includes a slightly broader refactor of the `From`
implementations between the various colour spaces to better reflect the
graph of definitions. My goal was to keep domain specific knowledge of
each colour space contained to their respective files (e.g., the
`From<Oklaba> for LinearRgba` definition was in `linear_rgba.rs` when it
probably belongs in `oklaba.rs`, since Linear sRGB is a fundamental
space and Oklab is defined in its relation to it)
This commit is contained in:
Zachary Harrold 2024-02-27 09:30:50 +11:00 committed by GitHub
parent 3bdeac6a42
commit 4d325eb3f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 599 additions and 220 deletions

View File

@ -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()),

View File

@ -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<Lcha> for Color {
}
}
impl From<Laba> for Color {
fn from(value: Laba) -> Self {
Self::Laba(value)
}
}
impl From<Xyza> for Color {
fn from(value: Xyza) -> Self {
Self::Xyza(value)
@ -130,6 +140,7 @@ impl From<Color> 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<Color> 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<Color> 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<Color> 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<Color> 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<Color> 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<Color> 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<Color> 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<Color> 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,

View File

@ -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<Hsva> for Hsla {
}
}
// Derived Conversions
impl From<Hwba> for Hsla {
fn from(value: Hwba) -> Self {
Hsva::from(value).into()
}
}
impl From<Hsla> for Hwba {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}
impl From<Srgba> for Hsla {
fn from(value: Srgba) -> Self {
Hsva::from(value).into()
@ -188,20 +196,14 @@ impl From<Hsla> for Srgba {
}
}
impl From<Hsla> for Hwba {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}
impl From<LinearRgba> for Hsla {
fn from(value: LinearRgba) -> Self {
Hsva::from(value).into()
}
}
impl From<Oklaba> for Hsla {
fn from(value: Oklaba) -> Self {
impl From<Hsla> for LinearRgba {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}
@ -212,6 +214,24 @@ impl From<Lcha> for Hsla {
}
}
impl From<Hsla> for Lcha {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}
impl From<Xyza> for Hsla {
fn from(value: Xyza) -> Self {
Hsva::from(value).into()
}
}
impl From<Hsla> for Xyza {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -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<Hwba> for Hsva {
}
}
// Derived Conversions
impl From<Srgba> for Hsva {
fn from(value: Srgba) -> Self {
Hwba::from(value).into()
@ -152,18 +154,6 @@ impl From<Hsva> for Lcha {
}
}
impl From<Oklaba> for Hsva {
fn from(value: Oklaba) -> Self {
Hwba::from(value).into()
}
}
impl From<Hsva> for Oklaba {
fn from(value: Hsva) -> Self {
Hwba::from(value).into()
}
}
impl From<Xyza> for Hsva {
fn from(value: Xyza) -> Self {
Hwba::from(value).into()

View File

@ -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<Hwba> for Srgba {
}
}
// Derived Conversions
impl From<LinearRgba> for Hwba {
fn from(value: LinearRgba) -> Self {
Srgba::from(value).into()
@ -185,18 +187,6 @@ impl From<Hwba> for Lcha {
}
}
impl From<Oklaba> for Hwba {
fn from(value: Oklaba) -> Self {
Srgba::from(value).into()
}
}
impl From<Hwba> for Oklaba {
fn from(value: Hwba) -> Self {
Srgba::from(value).into()
}
}
impl From<Xyza> for Hwba {
fn from(value: Xyza) -> Self {
Srgba::from(value).into()

View File

@ -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<Laba> 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<Xyza> 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<Srgba> for Laba {
fn from(value: Srgba) -> Self {
Xyza::from(value).into()
}
}
impl From<Laba> for Srgba {
fn from(value: Laba) -> Self {
Xyza::from(value).into()
}
}
impl From<LinearRgba> for Laba {
fn from(value: LinearRgba) -> Self {
Xyza::from(value).into()
}
}
impl From<Laba> for LinearRgba {
fn from(value: Laba) -> Self {
Xyza::from(value).into()
}
}
impl From<Hsla> for Laba {
fn from(value: Hsla) -> Self {
Xyza::from(value).into()
}
}
impl From<Laba> for Hsla {
fn from(value: Laba) -> Self {
Xyza::from(value).into()
}
}
impl From<Hsva> for Laba {
fn from(value: Hsva) -> Self {
Xyza::from(value).into()
}
}
impl From<Laba> for Hsva {
fn from(value: Laba) -> Self {
Xyza::from(value).into()
}
}
impl From<Hwba> for Laba {
fn from(value: Hwba) -> Self {
Xyza::from(value).into()
}
}
impl From<Laba> for Hwba {
fn from(value: Laba) -> Self {
Xyza::from(value).into()
}
}
impl From<Oklaba> for Laba {
fn from(value: Oklaba) -> Self {
Xyza::from(value).into()
}
}
impl From<Laba> 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);
}
}
}

View File

@ -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<Lcha> for Xyza {
impl From<Lcha> for Laba {
fn from(
Lcha {
lightness,
@ -147,79 +137,25 @@ impl From<Lcha> 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<Xyza> 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<Laba> 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<Xyza> 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<Srgba> for Lcha {
fn from(value: Srgba) -> Self {
Xyza::from(value).into()
Laba::from(value).into()
}
}
impl From<Lcha> for Srgba {
fn from(value: Lcha) -> Self {
Xyza::from(value).into()
Laba::from(value).into()
}
}
impl From<LinearRgba> for Lcha {
fn from(value: LinearRgba) -> Self {
Srgba::from(value).into()
Laba::from(value).into()
}
}
impl From<Lcha> for LinearRgba {
fn from(value: Lcha) -> Self {
LinearRgba::from(Srgba::from(value))
Laba::from(value).into()
}
}
impl From<Oklaba> for Lcha {
fn from(value: Oklaba) -> Self {
Srgba::from(value).into()
impl From<Xyza> for Lcha {
fn from(value: Xyza) -> Self {
Laba::from(value).into()
}
}
impl From<Hsla> for Lcha {
fn from(value: Hsla) -> Self {
Srgba::from(value).into()
impl From<Lcha> for Xyza {
fn from(value: Lcha) -> Self {
Laba::from(value).into()
}
}

View File

@ -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<Hsla> + Into<Hsla>,
Self: From<Hsva> + Into<Hsva>,
Self: From<Hwba> + Into<Hwba>,
Self: From<Laba> + Into<Laba>,
Self: From<Lcha> + Into<Lcha>,
Self: From<Oklaba> + Into<Oklaba>,
Self: From<Xyza> + Into<Xyza>,

View File

@ -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<LinearRgba> for Vec4 {
}
}
#[allow(clippy::excessive_precision)]
impl From<Oklaba> 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<Hsla> for LinearRgba {
#[inline]
fn from(value: Hsla) -> Self {
LinearRgba::from(Srgba::from(value))
}
}
impl From<LinearRgba> for wgpu::Color {
fn from(color: LinearRgba) -> Self {
wgpu::Color {

View File

@ -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<LinearRgba> for Oklaba {
}
}
impl From<Srgba> for Oklaba {
fn from(value: Srgba) -> Self {
Oklaba::from(LinearRgba::from(value))
#[allow(clippy::excessive_precision)]
impl From<Oklaba> 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<Hsla> for Oklaba {
fn from(value: Hsla) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Oklaba> for Hsla {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Hsva> for Oklaba {
fn from(value: Hsva) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Oklaba> for Hsva {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Hwba> for Oklaba {
fn from(value: Hwba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Oklaba> for Hwba {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}
@ -158,8 +217,32 @@ impl From<Lcha> for Oklaba {
}
}
impl From<Hsla> for Oklaba {
fn from(value: Hsla) -> Self {
impl From<Oklaba> for Lcha {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Srgba> for Oklaba {
fn from(value: Srgba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Oklaba> for Srgba {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Xyza> for Oklaba {
fn from(value: Xyza) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Oklaba> for Xyza {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}

View File

@ -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<Srgba> for LinearRgba {
}
}
impl From<Oklaba> for Srgba {
fn from(value: Oklaba) -> Self {
Srgba::from(LinearRgba::from(value))
}
}
impl From<Srgba> for [f32; 4] {
fn from(color: Srgba) -> Self {
[color.red, color.green, color.blue, color.alpha]
@ -315,6 +308,20 @@ impl From<Srgba> for Vec4 {
}
}
// Derived Conversions
impl From<Xyza> for Srgba {
fn from(value: Xyza) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Srgba> 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 {

View File

@ -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),
},
];

View File

@ -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<Xyza> for LinearRgba {
}
}
impl From<Srgba> for Xyza {
fn from(value: Srgba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Xyza> for Srgba {
fn from(value: Xyza) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Hsla> for Xyza {
fn from(value: Hsla) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Xyza> for Hsla {
fn from(value: Xyza) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Oklaba> for Xyza {
fn from(value: Oklaba) -> Self {
LinearRgba::from(value).into()
}
}
impl From<Xyza> for Oklaba {
fn from(value: Xyza) -> Self {
LinearRgba::from(value).into()
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -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<Color> 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<Hwba> for LegacyColor {
}
}
impl From<Laba> for LegacyColor {
fn from(value: Laba) -> Self {
Lcha::from(value).into()
}
}
impl From<Lcha> for LegacyColor {
fn from(
Lcha {
@ -1056,6 +1065,12 @@ impl From<LegacyColor> for Lcha {
}
}
impl From<LegacyColor> for Laba {
fn from(value: LegacyColor) -> Self {
Color::from(value).into()
}
}
impl From<LegacyColor> for Oklaba {
fn from(value: LegacyColor) -> Self {
LinearRgba::from(value).into()