Skip to content

Commit 6774e04

Browse files
bevy_color: Add Oklch Color Space (#12168)
# Objective - Complete compatibility with CSS Module 4 ## Solution - Added `Oklcha` which implements the Oklch color model. - Updated `Color` and `LegacyColor` accordingly. ## Migration Guide - Convert `Oklcha` to `Oklaba` using the provided `From` implementations and then handle accordingly. ## Notes This is the _last_ color space missing from the CSS Module 4 standard, and is also the one I believe we should recommend users actually work with for hand-crafting colours. It has all the uniformity benefits of Oklab combined with the intuition chroma and hue provide (when compared to a-axis and b-axis parameters).
1 parent 6f849d3 commit 6774e04

File tree

6 files changed

+425
-7
lines changed

6 files changed

+425
-7
lines changed

crates/bevy_color/crates/gen_tests/src/main.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use palette::{Hsl, Hsv, Hwb, IntoColor, Lab, Lch, LinSrgb, Oklab, Srgb, Xyz};
1+
use palette::{Hsl, Hsv, Hwb, IntoColor, Lab, Lch, LinSrgb, Oklab, Oklch, Srgb, Xyz};
22

33
const TEST_COLORS: &[(f32, f32, f32, &str)] = &[
44
(0., 0., 0., "black"),
@@ -25,7 +25,7 @@ fn main() {
2525
println!(
2626
"// Generated by gen_tests. Do not edit.
2727
#[cfg(test)]
28-
use crate::{{Hsla, Hsva, Hwba, Srgba, LinearRgba, Oklaba, Laba, Lcha, Xyza}};
28+
use crate::{{Hsla, Hsva, Hwba, Srgba, LinearRgba, Oklaba, Oklcha, Laba, Lcha, Xyza}};
2929
3030
#[cfg(test)]
3131
pub struct TestColor {{
@@ -38,6 +38,7 @@ pub struct TestColor {{
3838
pub lab: Laba,
3939
pub lch: Lcha,
4040
pub oklab: Oklaba,
41+
pub oklch: Oklcha,
4142
pub xyz: Xyza,
4243
}}
4344
"
@@ -55,6 +56,7 @@ pub struct TestColor {{
5556
let lab: Lab = srgb.into_color();
5657
let lch: Lch = srgb.into_color();
5758
let oklab: Oklab = srgb.into_color();
59+
let oklch: Oklch = srgb.into_color();
5860
let xyz: Xyz = srgb.into_color();
5961
println!(" // {name}");
6062
println!(
@@ -68,6 +70,7 @@ pub struct TestColor {{
6870
lab: Laba::new({}, {}, {}, 1.0),
6971
lch: Lcha::new({}, {}, {}, 1.0),
7072
oklab: Oklaba::new({}, {}, {}, 1.0),
73+
oklch: Oklcha::new({}, {}, {}, 1.0),
7174
xyz: Xyza::new({}, {}, {}, 1.0),
7275
}},",
7376
VariablePrecision(srgb.red),
@@ -94,6 +97,9 @@ pub struct TestColor {{
9497
VariablePrecision(oklab.l),
9598
VariablePrecision(oklab.a),
9699
VariablePrecision(oklab.b),
100+
VariablePrecision(oklch.l),
101+
VariablePrecision(oklch.chroma),
102+
VariablePrecision(oklch.hue.into_positive_degrees()),
97103
VariablePrecision(xyz.x),
98104
VariablePrecision(xyz.y),
99105
VariablePrecision(xyz.z),

crates/bevy_color/src/color.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{Alpha, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Srgba, StandardColor, Xyza};
1+
use crate::{
2+
Alpha, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Oklcha, Srgba, StandardColor, Xyza,
3+
};
24
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
35
use serde::{Deserialize, Serialize};
46

@@ -29,6 +31,8 @@ pub enum Color {
2931
Lcha(Lcha),
3032
/// A color in the Oklaba color space with alpha.
3133
Oklaba(Oklaba),
34+
/// A color in the Oklcha color space with alpha.
35+
Oklcha(Oklcha),
3236
/// A color in the XYZ color space with alpha.
3337
Xyza(Xyza),
3438
}
@@ -196,6 +200,26 @@ impl Color {
196200
})
197201
}
198202

203+
/// Creates a new [`Color`] object storing a [`Oklcha`] color.
204+
pub const fn oklcha(lightness: f32, chroma: f32, hue: f32, alpha: f32) -> Self {
205+
Self::Oklcha(Oklcha {
206+
lightness,
207+
chroma,
208+
hue,
209+
alpha,
210+
})
211+
}
212+
213+
/// Creates a new [`Color`] object storing a [`Oklcha`] color with an alpha of 1.0.
214+
pub const fn oklch(lightness: f32, chroma: f32, hue: f32) -> Self {
215+
Self::Oklcha(Oklcha {
216+
lightness,
217+
chroma,
218+
hue,
219+
alpha: 1.0,
220+
})
221+
}
222+
199223
/// Creates a new [`Color`] object storing a [`Xyza`] color.
200224
pub const fn xyza(x: f32, y: f32, z: f32, alpha: f32) -> Self {
201225
Self::Xyza(Xyza { x, y, z, alpha })
@@ -241,6 +265,7 @@ impl Alpha for Color {
241265
Color::Laba(x) => *x = x.with_alpha(alpha),
242266
Color::Lcha(x) => *x = x.with_alpha(alpha),
243267
Color::Oklaba(x) => *x = x.with_alpha(alpha),
268+
Color::Oklcha(x) => *x = x.with_alpha(alpha),
244269
Color::Xyza(x) => *x = x.with_alpha(alpha),
245270
}
246271

@@ -257,6 +282,7 @@ impl Alpha for Color {
257282
Color::Laba(x) => x.alpha(),
258283
Color::Lcha(x) => x.alpha(),
259284
Color::Oklaba(x) => x.alpha(),
285+
Color::Oklcha(x) => x.alpha(),
260286
Color::Xyza(x) => x.alpha(),
261287
}
262288
}
@@ -298,6 +324,12 @@ impl From<Oklaba> for Color {
298324
}
299325
}
300326

327+
impl From<Oklcha> for Color {
328+
fn from(value: Oklcha) -> Self {
329+
Self::Oklcha(value)
330+
}
331+
}
332+
301333
impl From<Lcha> for Color {
302334
fn from(value: Lcha) -> Self {
303335
Self::Lcha(value)
@@ -327,6 +359,7 @@ impl From<Color> for Srgba {
327359
Color::Laba(laba) => laba.into(),
328360
Color::Lcha(lcha) => lcha.into(),
329361
Color::Oklaba(oklab) => oklab.into(),
362+
Color::Oklcha(oklch) => oklch.into(),
330363
Color::Xyza(xyza) => xyza.into(),
331364
}
332365
}
@@ -343,6 +376,7 @@ impl From<Color> for LinearRgba {
343376
Color::Laba(laba) => laba.into(),
344377
Color::Lcha(lcha) => lcha.into(),
345378
Color::Oklaba(oklab) => oklab.into(),
379+
Color::Oklcha(oklch) => oklch.into(),
346380
Color::Xyza(xyza) => xyza.into(),
347381
}
348382
}
@@ -359,6 +393,7 @@ impl From<Color> for Hsla {
359393
Color::Laba(laba) => laba.into(),
360394
Color::Lcha(lcha) => lcha.into(),
361395
Color::Oklaba(oklab) => oklab.into(),
396+
Color::Oklcha(oklch) => oklch.into(),
362397
Color::Xyza(xyza) => xyza.into(),
363398
}
364399
}
@@ -375,6 +410,7 @@ impl From<Color> for Hsva {
375410
Color::Laba(laba) => laba.into(),
376411
Color::Lcha(lcha) => lcha.into(),
377412
Color::Oklaba(oklab) => oklab.into(),
413+
Color::Oklcha(oklch) => oklch.into(),
378414
Color::Xyza(xyza) => xyza.into(),
379415
}
380416
}
@@ -391,6 +427,7 @@ impl From<Color> for Hwba {
391427
Color::Laba(laba) => laba.into(),
392428
Color::Lcha(lcha) => lcha.into(),
393429
Color::Oklaba(oklab) => oklab.into(),
430+
Color::Oklcha(oklch) => oklch.into(),
394431
Color::Xyza(xyza) => xyza.into(),
395432
}
396433
}
@@ -407,6 +444,7 @@ impl From<Color> for Laba {
407444
Color::Laba(laba) => laba,
408445
Color::Lcha(lcha) => lcha.into(),
409446
Color::Oklaba(oklab) => oklab.into(),
447+
Color::Oklcha(oklch) => oklch.into(),
410448
Color::Xyza(xyza) => xyza.into(),
411449
}
412450
}
@@ -423,6 +461,7 @@ impl From<Color> for Lcha {
423461
Color::Laba(laba) => laba.into(),
424462
Color::Lcha(lcha) => lcha,
425463
Color::Oklaba(oklab) => oklab.into(),
464+
Color::Oklcha(oklch) => oklch.into(),
426465
Color::Xyza(xyza) => xyza.into(),
427466
}
428467
}
@@ -439,6 +478,24 @@ impl From<Color> for Oklaba {
439478
Color::Laba(laba) => laba.into(),
440479
Color::Lcha(lcha) => lcha.into(),
441480
Color::Oklaba(oklab) => oklab,
481+
Color::Oklcha(oklch) => oklch.into(),
482+
Color::Xyza(xyza) => xyza.into(),
483+
}
484+
}
485+
}
486+
487+
impl From<Color> for Oklcha {
488+
fn from(value: Color) -> Self {
489+
match value {
490+
Color::Srgba(srgba) => srgba.into(),
491+
Color::LinearRgba(linear) => linear.into(),
492+
Color::Hsla(hsla) => hsla.into(),
493+
Color::Hsva(hsva) => hsva.into(),
494+
Color::Hwba(hwba) => hwba.into(),
495+
Color::Laba(laba) => laba.into(),
496+
Color::Lcha(lcha) => lcha.into(),
497+
Color::Oklaba(oklab) => oklab.into(),
498+
Color::Oklcha(oklch) => oklch,
442499
Color::Xyza(xyza) => xyza.into(),
443500
}
444501
}
@@ -455,6 +512,7 @@ impl From<Color> for Xyza {
455512
Color::Laba(laba) => laba.into(),
456513
Color::Lcha(x) => x.into(),
457514
Color::Oklaba(x) => x.into(),
515+
Color::Oklcha(oklch) => oklch.into(),
458516
Color::Xyza(xyza) => xyza,
459517
}
460518
}

crates/bevy_color/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
//! and an analog of lightness in the form of value. In contrast, HWB instead uses whiteness and blackness
4040
//! parameters, which can be used to lighten and darken a particular hue respectively.
4141
//!
42-
//! Oklab is a perceptually uniform color space that is designed to be used for tasks such
43-
//! as image processing. It is not as widely used as the other color spaces, but it is useful
42+
//! Oklab and Okclch are perceptually uniform color spaces that are designed to be used for tasks such
43+
//! as image processing. They are not as widely used as the other color spaces, but are useful
4444
//! for tasks such as color correction and image analysis, where it is important to be able
4545
//! to do things like change color saturation without causing hue shifts.
4646
//!
@@ -87,6 +87,7 @@ mod laba;
8787
mod lcha;
8888
mod linear_rgba;
8989
mod oklaba;
90+
mod oklcha;
9091
pub mod palettes;
9192
mod srgba;
9293
#[cfg(test)]
@@ -106,6 +107,7 @@ pub mod prelude {
106107
pub use crate::lcha::*;
107108
pub use crate::linear_rgba::*;
108109
pub use crate::oklaba::*;
110+
pub use crate::oklcha::*;
109111
pub use crate::srgba::*;
110112
pub use crate::xyza::*;
111113
}
@@ -120,6 +122,7 @@ pub use laba::*;
120122
pub use lcha::*;
121123
pub use linear_rgba::*;
122124
pub use oklaba::*;
125+
pub use oklcha::*;
123126
pub use srgba::*;
124127
pub use xyza::*;
125128

@@ -142,6 +145,7 @@ where
142145
Self: From<Laba> + Into<Laba>,
143146
Self: From<Lcha> + Into<Lcha>,
144147
Self: From<Oklaba> + Into<Oklaba>,
148+
Self: From<Oklcha> + Into<Oklcha>,
145149
Self: From<Xyza> + Into<Xyza>,
146150
Self: Alpha,
147151
{

0 commit comments

Comments
 (0)