Skip to content

Commit 5e63f68

Browse files
Made bevy_color a dependency of bevy_render (#12105)
# Objective - Fixes #12068 ## Solution - Split `bevy_render::color::colorspace` across the various space implementations in `bevy_color` as appropriate. - Moved `From` implementations involving `bevy_render::color::LegacyColor` into `bevy_render::color` ## Migration Guide ### `bevy_render::color::colorspace::SrgbColorSpace::<f32>::linear_to_nonlinear_srgb` Use `bevy_color::color::gamma_function_inverse` ### `bevy_render::color::colorspace::SrgbColorSpace::<f32>::nonlinear_to_linear_srgb` Use `bevy_color::color::gamma_function` ### `bevy_render::color::colorspace::SrgbColorSpace::<u8>::linear_to_nonlinear_srgb` Modify the `u8` value to instead be an `f32` (`|x| x as f32 / 255.`), use `bevy_color::color::gamma_function_inverse`, and back again. ### `bevy_render::color::colorspace::SrgbColorSpace::<u8>::nonlinear_to_linear_srgb` Modify the `u8` value to instead be an `f32` (`|x| x as f32 / 255.`), use `bevy_color::color::gamma_function`, and back again. ### `bevy_render::color::colorspace::HslRepresentation::hsl_to_nonlinear_srgb` Use `Hsla`'s implementation of `Into<Srgba>` ### `bevy_render::color::colorspace::HslRepresentation::nonlinear_srgb_to_hsl` Use `Srgba`'s implementation of `Into<Hsla>` ### `bevy_render::color::colorspace::LchRepresentation::lch_to_nonlinear_srgb` Use `Lcha`'s implementation of `Into<Srgba>` ### `bevy_render::color::colorspace::LchRepresentation::nonlinear_srgb_to_lch` Use `Srgba`'s implementation of `Into<Lcha>`
1 parent e5994a4 commit 5e63f68

File tree

13 files changed

+462
-1209
lines changed

13 files changed

+462
-1209
lines changed

crates/bevy_color/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bevy_color"
3-
version = "0.13.0"
3+
version = "0.14.0-dev"
44
edition = "2021"
55
description = "Types for representing and manipulating color values"
66
homepage = "https://bevyengine.org"
@@ -13,8 +13,8 @@ bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
1313
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
1414
"bevy",
1515
] }
16-
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
1716
serde = "1.0"
17+
thiserror = "1.0"
1818

1919
[lints]
2020
workspace = true

crates/bevy_color/src/color.rs

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::{Alpha, Hsla, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza};
22
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
3-
use bevy_render::color::LegacyColor;
43
use serde::{Deserialize, Serialize};
54

65
/// An enumerated type that can represent any of the color types in this crate.
@@ -29,14 +28,7 @@ impl StandardColor for Color {}
2928
impl Color {
3029
/// Return the color as a linear RGBA color.
3130
pub fn linear(&self) -> LinearRgba {
32-
match self {
33-
Color::Srgba(srgba) => (*srgba).into(),
34-
Color::LinearRgba(linear) => *linear,
35-
Color::Hsla(hsla) => (*hsla).into(),
36-
Color::Lcha(lcha) => (*lcha).into(),
37-
Color::Oklaba(oklab) => (*oklab).into(),
38-
Color::Xyza(xyza) => (*xyza).into(),
39-
}
31+
(*self).into()
4032
}
4133
}
4234

@@ -142,9 +134,9 @@ impl From<Color> for Hsla {
142134
Color::Srgba(srgba) => srgba.into(),
143135
Color::LinearRgba(linear) => linear.into(),
144136
Color::Hsla(hsla) => hsla,
145-
Color::Lcha(lcha) => LinearRgba::from(lcha).into(),
146-
Color::Oklaba(oklab) => LinearRgba::from(oklab).into(),
147-
Color::Xyza(xyza) => LinearRgba::from(xyza).into(),
137+
Color::Lcha(lcha) => lcha.into(),
138+
Color::Oklaba(oklab) => oklab.into(),
139+
Color::Xyza(xyza) => xyza.into(),
148140
}
149141
}
150142
}
@@ -154,10 +146,10 @@ impl From<Color> for Lcha {
154146
match value {
155147
Color::Srgba(srgba) => srgba.into(),
156148
Color::LinearRgba(linear) => linear.into(),
157-
Color::Hsla(hsla) => Srgba::from(hsla).into(),
149+
Color::Hsla(hsla) => hsla.into(),
158150
Color::Lcha(lcha) => lcha,
159-
Color::Oklaba(oklab) => LinearRgba::from(oklab).into(),
160-
Color::Xyza(xyza) => LinearRgba::from(xyza).into(),
151+
Color::Oklaba(oklab) => oklab.into(),
152+
Color::Xyza(xyza) => xyza.into(),
161153
}
162154
}
163155
}
@@ -167,10 +159,10 @@ impl From<Color> for Oklaba {
167159
match value {
168160
Color::Srgba(srgba) => srgba.into(),
169161
Color::LinearRgba(linear) => linear.into(),
170-
Color::Hsla(hsla) => Srgba::from(hsla).into(),
171-
Color::Lcha(lcha) => LinearRgba::from(lcha).into(),
162+
Color::Hsla(hsla) => hsla.into(),
163+
Color::Lcha(lcha) => lcha.into(),
172164
Color::Oklaba(oklab) => oklab,
173-
Color::Xyza(xyza) => LinearRgba::from(xyza).into(),
165+
Color::Xyza(xyza) => xyza.into(),
174166
}
175167
}
176168
}
@@ -187,27 +179,3 @@ impl From<Color> for Xyza {
187179
}
188180
}
189181
}
190-
191-
impl From<LegacyColor> for Color {
192-
fn from(value: LegacyColor) -> Self {
193-
match value {
194-
LegacyColor::Rgba { .. } => Srgba::from(value).into(),
195-
LegacyColor::RgbaLinear { .. } => LinearRgba::from(value).into(),
196-
LegacyColor::Hsla { .. } => Hsla::from(value).into(),
197-
LegacyColor::Lcha { .. } => Lcha::from(value).into(),
198-
}
199-
}
200-
}
201-
202-
impl From<Color> for LegacyColor {
203-
fn from(value: Color) -> Self {
204-
match value {
205-
Color::Srgba(x) => x.into(),
206-
Color::LinearRgba(x) => x.into(),
207-
Color::Hsla(x) => x.into(),
208-
Color::Lcha(x) => x.into(),
209-
Color::Oklaba(x) => x.into(),
210-
Color::Xyza(x) => x.into(),
211-
}
212-
}
213-
}

crates/bevy_color/src/hsla.rs

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::{Alpha, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor};
22
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
3-
use bevy_render::color::HslRepresentation;
43
use serde::{Deserialize, Serialize};
54

65
/// Color in Hue-Saturation-Lightness color space with alpha
@@ -129,35 +128,72 @@ impl Luminance for Hsla {
129128
}
130129

131130
impl From<Srgba> for Hsla {
132-
fn from(value: Srgba) -> Self {
133-
let (h, s, l) =
134-
HslRepresentation::nonlinear_srgb_to_hsl([value.red, value.green, value.blue]);
135-
Self::new(h, s, l, value.alpha)
136-
}
137-
}
131+
fn from(
132+
Srgba {
133+
red,
134+
green,
135+
blue,
136+
alpha,
137+
}: Srgba,
138+
) -> Self {
139+
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
140+
let x_max = red.max(green.max(blue));
141+
let x_min = red.min(green.min(blue));
142+
let chroma = x_max - x_min;
143+
let lightness = (x_max + x_min) / 2.0;
144+
let hue = if chroma == 0.0 {
145+
0.0
146+
} else if red == x_max {
147+
60.0 * (green - blue) / chroma
148+
} else if green == x_max {
149+
60.0 * (2.0 + (blue - red) / chroma)
150+
} else {
151+
60.0 * (4.0 + (red - green) / chroma)
152+
};
153+
let hue = if hue < 0.0 { 360.0 + hue } else { hue };
154+
let saturation = if lightness <= 0.0 || lightness >= 1.0 {
155+
0.0
156+
} else {
157+
(x_max - lightness) / lightness.min(1.0 - lightness)
158+
};
138159

139-
impl From<Hsla> for bevy_render::color::LegacyColor {
140-
fn from(value: Hsla) -> Self {
141-
bevy_render::color::LegacyColor::Hsla {
142-
hue: value.hue,
143-
saturation: value.saturation,
144-
lightness: value.lightness,
145-
alpha: value.alpha,
146-
}
160+
Self::new(hue, saturation, lightness, alpha)
147161
}
148162
}
149163

150-
impl From<bevy_render::color::LegacyColor> for Hsla {
151-
fn from(value: bevy_render::color::LegacyColor) -> Self {
152-
match value.as_hsla() {
153-
bevy_render::color::LegacyColor::Hsla {
154-
hue,
155-
saturation,
156-
lightness,
157-
alpha,
158-
} => Hsla::new(hue, saturation, lightness, alpha),
159-
_ => unreachable!(),
160-
}
164+
impl From<Hsla> for Srgba {
165+
fn from(
166+
Hsla {
167+
hue,
168+
saturation,
169+
lightness,
170+
alpha,
171+
}: Hsla,
172+
) -> Self {
173+
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
174+
let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
175+
let hue_prime = hue / 60.0;
176+
let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs());
177+
let (r_temp, g_temp, b_temp) = if hue_prime < 1.0 {
178+
(chroma, largest_component, 0.0)
179+
} else if hue_prime < 2.0 {
180+
(largest_component, chroma, 0.0)
181+
} else if hue_prime < 3.0 {
182+
(0.0, chroma, largest_component)
183+
} else if hue_prime < 4.0 {
184+
(0.0, largest_component, chroma)
185+
} else if hue_prime < 5.0 {
186+
(largest_component, 0.0, chroma)
187+
} else {
188+
(chroma, 0.0, largest_component)
189+
};
190+
let lightness_match = lightness - chroma / 2.0;
191+
192+
let red = r_temp + lightness_match;
193+
let green = g_temp + lightness_match;
194+
let blue = b_temp + lightness_match;
195+
196+
Self::new(red, green, blue, alpha)
161197
}
162198
}
163199

crates/bevy_color/src/lcha.rs

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use crate::{Alpha, Hsla, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor};
1+
use crate::{Alpha, Hsla, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza};
22
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
3-
use bevy_render::color::LchRepresentation;
43
use serde::{Deserialize, Serialize};
54

65
/// Color in LCH color space, with alpha
@@ -67,6 +66,16 @@ impl Lcha {
6766
pub const fn with_lightness(self, lightness: f32) -> Self {
6867
Self { lightness, ..self }
6968
}
69+
70+
/// CIE Epsilon Constant
71+
///
72+
/// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html)
73+
pub const CIE_EPSILON: f32 = 216.0 / 24389.0;
74+
75+
/// CIE Kappa Constant
76+
///
77+
/// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html)
78+
pub const CIE_KAPPA: f32 = 24389.0 / 27.0;
7079
}
7180

7281
impl Default for Lcha {
@@ -129,19 +138,116 @@ impl Luminance for Lcha {
129138
}
130139
}
131140

141+
impl From<Lcha> for Xyza {
142+
fn from(
143+
Lcha {
144+
lightness,
145+
chroma,
146+
hue,
147+
alpha,
148+
}: Lcha,
149+
) -> Self {
150+
let lightness = lightness * 100.0;
151+
let chroma = chroma * 100.0;
152+
153+
// convert LCH to Lab
154+
// http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html
155+
let l = lightness;
156+
let a = chroma * hue.to_radians().cos();
157+
let b = chroma * hue.to_radians().sin();
158+
159+
// convert Lab to XYZ
160+
// http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
161+
let fy = (l + 16.0) / 116.0;
162+
let fx = a / 500.0 + fy;
163+
let fz = fy - b / 200.0;
164+
let xr = {
165+
let fx3 = fx.powf(3.0);
166+
167+
if fx3 > Lcha::CIE_EPSILON {
168+
fx3
169+
} else {
170+
(116.0 * fx - 16.0) / Lcha::CIE_KAPPA
171+
}
172+
};
173+
let yr = if l > Lcha::CIE_EPSILON * Lcha::CIE_KAPPA {
174+
((l + 16.0) / 116.0).powf(3.0)
175+
} else {
176+
l / Lcha::CIE_KAPPA
177+
};
178+
let zr = {
179+
let fz3 = fz.powf(3.0);
180+
181+
if fz3 > Lcha::CIE_EPSILON {
182+
fz3
183+
} else {
184+
(116.0 * fz - 16.0) / Lcha::CIE_KAPPA
185+
}
186+
};
187+
let x = xr * Xyza::D65_WHITE.x;
188+
let y = yr * Xyza::D65_WHITE.y;
189+
let z = zr * Xyza::D65_WHITE.z;
190+
191+
Xyza::new(x, y, z, alpha)
192+
}
193+
}
194+
195+
impl From<Xyza> for Lcha {
196+
fn from(Xyza { x, y, z, alpha }: Xyza) -> Self {
197+
// XYZ to Lab
198+
// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
199+
let xr = x / Xyza::D65_WHITE.x;
200+
let yr = y / Xyza::D65_WHITE.y;
201+
let zr = z / Xyza::D65_WHITE.z;
202+
let fx = if xr > Lcha::CIE_EPSILON {
203+
xr.cbrt()
204+
} else {
205+
(Lcha::CIE_KAPPA * xr + 16.0) / 116.0
206+
};
207+
let fy = if yr > Lcha::CIE_EPSILON {
208+
yr.cbrt()
209+
} else {
210+
(Lcha::CIE_KAPPA * yr + 16.0) / 116.0
211+
};
212+
let fz = if yr > Lcha::CIE_EPSILON {
213+
zr.cbrt()
214+
} else {
215+
(Lcha::CIE_KAPPA * zr + 16.0) / 116.0
216+
};
217+
let l = 116.0 * fy - 16.0;
218+
let a = 500.0 * (fx - fy);
219+
let b = 200.0 * (fy - fz);
220+
221+
// Lab to LCH
222+
// http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html
223+
let c = (a.powf(2.0) + b.powf(2.0)).sqrt();
224+
let h = {
225+
let h = b.to_radians().atan2(a.to_radians()).to_degrees();
226+
227+
if h < 0.0 {
228+
h + 360.0
229+
} else {
230+
h
231+
}
232+
};
233+
234+
let lightness = (l / 100.0).clamp(0.0, 1.5);
235+
let chroma = (c / 100.0).clamp(0.0, 1.5);
236+
let hue = h;
237+
238+
Lcha::new(lightness, chroma, hue, alpha)
239+
}
240+
}
241+
132242
impl From<Srgba> for Lcha {
133243
fn from(value: Srgba) -> Self {
134-
let (l, c, h) =
135-
LchRepresentation::nonlinear_srgb_to_lch([value.red, value.green, value.blue]);
136-
Lcha::new(l, c, h, value.alpha)
244+
Xyza::from(value).into()
137245
}
138246
}
139247

140248
impl From<Lcha> for Srgba {
141249
fn from(value: Lcha) -> Self {
142-
let [r, g, b] =
143-
LchRepresentation::lch_to_nonlinear_srgb(value.lightness, value.chroma, value.hue);
144-
Srgba::new(r, g, b, value.alpha)
250+
Xyza::from(value).into()
145251
}
146252
}
147253

@@ -157,31 +263,6 @@ impl From<Lcha> for LinearRgba {
157263
}
158264
}
159265

160-
impl From<Lcha> for bevy_render::color::LegacyColor {
161-
fn from(value: Lcha) -> Self {
162-
bevy_render::color::LegacyColor::Lcha {
163-
hue: value.hue,
164-
chroma: value.chroma,
165-
lightness: value.lightness,
166-
alpha: value.alpha,
167-
}
168-
}
169-
}
170-
171-
impl From<bevy_render::color::LegacyColor> for Lcha {
172-
fn from(value: bevy_render::color::LegacyColor) -> Self {
173-
match value.as_lcha() {
174-
bevy_render::color::LegacyColor::Lcha {
175-
hue,
176-
chroma,
177-
lightness,
178-
alpha,
179-
} => Lcha::new(hue, chroma, lightness, alpha),
180-
_ => unreachable!(),
181-
}
182-
}
183-
}
184-
185266
impl From<Oklaba> for Lcha {
186267
fn from(value: Oklaba) -> Self {
187268
Srgba::from(value).into()

0 commit comments

Comments
 (0)