Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions crates/bevy_color/crates/gen_tests/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use palette::{Hsl, IntoColor, Lch, LinSrgb, Oklab, Srgb, Xyz};
use palette::{Hsl, Hsv, Hwb, IntoColor, Lch, LinSrgb, Oklab, Srgb, Xyz};

const TEST_COLORS: &[(f32, f32, f32, &str)] = &[
(0., 0., 0., "black"),
Expand All @@ -25,14 +25,16 @@ fn main() {
println!(
"// Generated by gen_tests. Do not edit.
#[cfg(test)]
use crate::{{Hsla, Srgba, LinearRgba, Oklaba, Lcha, Xyza}};
use crate::{{Hsla, Hsva, Hwba, Srgba, LinearRgba, Oklaba, Lcha, Xyza}};

#[cfg(test)]
pub struct TestColor {{
pub name: &'static str,
pub rgb: Srgba,
pub linear_rgb: LinearRgba,
pub hsl: Hsla,
pub hsv: Hsva,
pub hwb: Hwba,
pub lch: Lcha,
pub oklab: Oklaba,
pub xyz: Xyza,
Expand All @@ -47,6 +49,8 @@ pub struct TestColor {{
let srgb = Srgb::new(*r, *g, *b);
let linear_rgb: LinSrgb = srgb.into_color();
let hsl: Hsl = srgb.into_color();
let hsv: Hsv = srgb.into_color();
let hwb: Hwb = srgb.into_color();
let lch: Lch = srgb.into_color();
let oklab: Oklab = srgb.into_color();
let xyz: Xyz = srgb.into_color();
Expand All @@ -57,6 +61,8 @@ pub struct TestColor {{
rgb: Srgba::new({}, {}, {}, 1.0),
linear_rgb: LinearRgba::new({}, {}, {}, 1.0),
hsl: Hsla::new({}, {}, {}, 1.0),
hsv: Hsva::new({}, {}, {}, 1.0),
hwb: Hwba::new({}, {}, {}, 1.0),
lch: Lcha::new({}, {}, {}, 1.0),
oklab: Oklaba::new({}, {}, {}, 1.0),
xyz: Xyza::new({}, {}, {}, 1.0),
Expand All @@ -70,6 +76,12 @@ pub struct TestColor {{
VariablePrecision(hsl.hue.into_positive_degrees()),
VariablePrecision(hsl.saturation),
VariablePrecision(hsl.lightness),
VariablePrecision(hsv.hue.into_positive_degrees()),
VariablePrecision(hsv.saturation),
VariablePrecision(hsv.value),
VariablePrecision(hwb.hue.into_positive_degrees()),
VariablePrecision(hwb.whiteness),
VariablePrecision(hwb.blackness),
VariablePrecision(lch.l / 100.0),
VariablePrecision(lch.chroma / 100.0),
VariablePrecision(lch.hue.into_positive_degrees()),
Expand Down
64 changes: 63 additions & 1 deletion crates/bevy_color/src/color.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Alpha, Hsla, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza};
use crate::{Alpha, Hsla, Hsva, Hwba, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use serde::{Deserialize, Serialize};

Expand All @@ -15,6 +15,10 @@ pub enum Color {
LinearRgba(LinearRgba),
/// A color in the HSL color space with alpha.
Hsla(Hsla),
/// A color in the HSV color space with alpha.
Hsva(Hsva),
/// A color in the HWB color space with alpha.
Hwba(Hwba),
/// A color in the LCH color space with alpha.
Lcha(Lcha),
/// A color in the Oklaba color space with alpha.
Expand Down Expand Up @@ -46,6 +50,8 @@ impl Alpha for Color {
Color::Srgba(x) => *x = x.with_alpha(alpha),
Color::LinearRgba(x) => *x = x.with_alpha(alpha),
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::Lcha(x) => *x = x.with_alpha(alpha),
Color::Oklaba(x) => *x = x.with_alpha(alpha),
Color::Xyza(x) => *x = x.with_alpha(alpha),
Expand All @@ -59,6 +65,8 @@ impl Alpha for Color {
Color::Srgba(x) => x.alpha(),
Color::LinearRgba(x) => x.alpha(),
Color::Hsla(x) => x.alpha(),
Color::Hsva(x) => x.alpha(),
Color::Hwba(x) => x.alpha(),
Color::Lcha(x) => x.alpha(),
Color::Oklaba(x) => x.alpha(),
Color::Xyza(x) => x.alpha(),
Expand All @@ -84,6 +92,18 @@ impl From<Hsla> for Color {
}
}

impl From<Hsva> for Color {
fn from(value: Hsva) -> Self {
Self::Hsva(value)
}
}

impl From<Hwba> for Color {
fn from(value: Hwba) -> Self {
Self::Hwba(value)
}
}

impl From<Oklaba> for Color {
fn from(value: Oklaba) -> Self {
Self::Oklaba(value)
Expand All @@ -108,6 +128,8 @@ impl From<Color> for Srgba {
Color::Srgba(srgba) => srgba,
Color::LinearRgba(linear) => linear.into(),
Color::Hsla(hsla) => hsla.into(),
Color::Hsva(hsva) => hsva.into(),
Color::Hwba(hwba) => hwba.into(),
Color::Lcha(lcha) => lcha.into(),
Color::Oklaba(oklab) => oklab.into(),
Color::Xyza(xyza) => xyza.into(),
Expand All @@ -121,6 +143,8 @@ impl From<Color> for LinearRgba {
Color::Srgba(srgba) => srgba.into(),
Color::LinearRgba(linear) => linear,
Color::Hsla(hsla) => hsla.into(),
Color::Hsva(hsva) => hsva.into(),
Color::Hwba(hwba) => hwba.into(),
Color::Lcha(lcha) => lcha.into(),
Color::Oklaba(oklab) => oklab.into(),
Color::Xyza(xyza) => xyza.into(),
Expand All @@ -134,6 +158,38 @@ impl From<Color> for Hsla {
Color::Srgba(srgba) => srgba.into(),
Color::LinearRgba(linear) => linear.into(),
Color::Hsla(hsla) => hsla,
Color::Hsva(hsva) => hsva.into(),
Color::Hwba(hwba) => hwba.into(),
Color::Lcha(lcha) => lcha.into(),
Color::Oklaba(oklab) => oklab.into(),
Color::Xyza(xyza) => xyza.into(),
}
}
}

impl From<Color> for Hsva {
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,
Color::Hwba(hwba) => hwba.into(),
Color::Lcha(lcha) => lcha.into(),
Color::Oklaba(oklab) => oklab.into(),
Color::Xyza(xyza) => xyza.into(),
}
}
}

impl From<Color> for Hwba {
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,
Color::Lcha(lcha) => lcha.into(),
Color::Oklaba(oklab) => oklab.into(),
Color::Xyza(xyza) => xyza.into(),
Expand All @@ -147,6 +203,8 @@ impl From<Color> for Lcha {
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::Lcha(lcha) => lcha,
Color::Oklaba(oklab) => oklab.into(),
Color::Xyza(xyza) => xyza.into(),
Expand All @@ -160,6 +218,8 @@ impl From<Color> for Oklaba {
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::Lcha(lcha) => lcha.into(),
Color::Oklaba(oklab) => oklab,
Color::Xyza(xyza) => xyza.into(),
Expand All @@ -173,6 +233,8 @@ impl From<Color> for Xyza {
Color::Srgba(x) => x.into(),
Color::LinearRgba(x) => x.into(),
Color::Hsla(x) => x.into(),
Color::Hsva(hsva) => hsva.into(),
Color::Hwba(hwba) => hwba.into(),
Color::Lcha(x) => x.into(),
Color::Oklaba(x) => x.into(),
Color::Xyza(xyza) => xyza,
Expand Down
105 changes: 51 additions & 54 deletions crates/bevy_color/src/hsla.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::{Alpha, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor};
use crate::{Alpha, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use serde::{Deserialize, Serialize};

/// Color in Hue-Saturation-Lightness color space with alpha
/// Color in Hue-Saturation-Lightness (HSL) color space with alpha.
/// Further information on this color model can be found on [Wikipedia](https://en.wikipedia.org/wiki/HSL_and_HSV).
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub struct Hsla {
Expand Down Expand Up @@ -127,91 +128,87 @@ impl Luminance for Hsla {
}
}

impl From<Srgba> for Hsla {
impl From<Hsla> for Hsva {
fn from(
Srgba {
red,
green,
blue,
Hsla {
hue,
saturation,
lightness,
alpha,
}: Srgba,
}: Hsla,
) -> Self {
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
let x_max = red.max(green.max(blue));
let x_min = red.min(green.min(blue));
let chroma = x_max - x_min;
let lightness = (x_max + x_min) / 2.0;
let hue = if chroma == 0.0 {
0.0
} else if red == x_max {
60.0 * (green - blue) / chroma
} else if green == x_max {
60.0 * (2.0 + (blue - red) / chroma)
// Based on https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV
let value = lightness + saturation * lightness.min(1. - lightness);
let saturation = if value == 0. {
0.
} else {
60.0 * (4.0 + (red - green) / chroma)
};
let hue = if hue < 0.0 { 360.0 + hue } else { hue };
let saturation = if lightness <= 0.0 || lightness >= 1.0 {
0.0
} else {
(x_max - lightness) / lightness.min(1.0 - lightness)
2. * (1. - (lightness / value))
};

Self::new(hue, saturation, lightness, alpha)
Hsva::new(hue, saturation, value, alpha)
}
}

impl From<Hsla> for Srgba {
impl From<Hsva> for Hsla {
fn from(
Hsla {
Hsva {
hue,
saturation,
lightness,
value,
alpha,
}: Hsla,
}: Hsva,
) -> Self {
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
let hue_prime = hue / 60.0;
let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs());
let (r_temp, g_temp, b_temp) = if hue_prime < 1.0 {
(chroma, largest_component, 0.0)
} else if hue_prime < 2.0 {
(largest_component, chroma, 0.0)
} else if hue_prime < 3.0 {
(0.0, chroma, largest_component)
} else if hue_prime < 4.0 {
(0.0, largest_component, chroma)
} else if hue_prime < 5.0 {
(largest_component, 0.0, chroma)
// Based on https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL
let lightness = value * (1. - saturation / 2.);
let saturation = if lightness == 0. || lightness == 1. {
0.
} else {
(chroma, 0.0, largest_component)
(value - lightness) / lightness.min(1. - lightness)
};
let lightness_match = lightness - chroma / 2.0;

let red = r_temp + lightness_match;
let green = g_temp + lightness_match;
let blue = b_temp + lightness_match;
Hsla::new(hue, saturation, lightness, alpha)
}
}

impl From<Hwba> for Hsla {
fn from(value: Hwba) -> Self {
Hsva::from(value).into()
}
}

impl From<Srgba> for Hsla {
fn from(value: Srgba) -> Self {
Hsva::from(value).into()
}
}

impl From<Hsla> for Srgba {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}

Self::new(red, green, blue, alpha)
impl From<Hsla> for Hwba {
fn from(value: Hsla) -> Self {
Hsva::from(value).into()
}
}

impl From<LinearRgba> for Hsla {
fn from(value: LinearRgba) -> Self {
Srgba::from(value).into()
Hsva::from(value).into()
}
}

impl From<Oklaba> for Hsla {
fn from(value: Oklaba) -> Self {
Srgba::from(value).into()
Hsva::from(value).into()
}
}

impl From<Lcha> for Hsla {
fn from(value: Lcha) -> Self {
Srgba::from(value).into()
Hsva::from(value).into()
}
}

Expand Down
Loading