Skip to content

Commit

Permalink
Solve some oklaba inconsistencies (#12526)
Browse files Browse the repository at this point in the history
# Objective

- Even if we have `Laba` and `Oklcha` colorspaces using lightness as the
L field name, `Oklaba` doesn't do the same
- The shorthand function for creating a new color should be named
`Oklaba::lab`, but is named `lch`

## Solution

- Rename field l in `Oklaba` to lightness
- Rename `Oklaba::lch` to `Oklaba::lab`

---

## Changelog

### Changed
- Changed name in l field in `Oklaba` to lightness
- Changed method name `Oklaba::lch` to `Oklaba::lab`

## Migration Guide

If you were creating a Oklaba instance directly, instead of using L, you
should use lightness
```rust
// Before
let oklaba = Oklaba { l: 1., ..Default::default() };

// Now
let oklaba = Oklaba { lightness: 1., ..Default::default() };
``` 

if you were using the function `Oklaba::lch`, now the method is named
`Oklaba::lab`
  • Loading branch information
pablo-lua authored Mar 17, 2024
1 parent 1067eaa commit 1610738
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 28 deletions.
13 changes: 9 additions & 4 deletions crates/bevy_color/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,19 @@ impl Color {
}

/// Creates a new [`Color`] object storing a [`Oklaba`] color.
pub const fn oklaba(l: f32, a: f32, b: f32, alpha: f32) -> Self {
Self::Oklaba(Oklaba { l, a, b, alpha })
pub const fn oklaba(lightness: f32, a: f32, b: f32, alpha: f32) -> Self {
Self::Oklaba(Oklaba {
lightness,
a,
b,
alpha,
})
}

/// Creates a new [`Color`] object storing a [`Oklaba`] color with an alpha of 1.0.
pub const fn oklab(l: f32, a: f32, b: f32) -> Self {
pub const fn oklab(lightness: f32, a: f32, b: f32) -> Self {
Self::Oklaba(Oklaba {
l,
lightness,
a,
b,
alpha: 1.0,
Expand Down
66 changes: 44 additions & 22 deletions crates/bevy_color/src/oklaba.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
#[reflect(PartialEq, Serialize, Deserialize, Default)]
pub struct Oklaba {
/// The 'l' channel. [0.0, 1.0]
pub l: f32,
pub lightness: f32,
/// The 'a' channel. [-1.0, 1.0]
pub a: f32,
/// The 'b' channel. [-1.0, 1.0]
Expand All @@ -30,33 +30,38 @@ impl Oklaba {
///
/// # Arguments
///
/// * `l` - Lightness channel. [0.0, 1.0]
/// * `lightness` - Lightness channel. [0.0, 1.0]
/// * `a` - Green-red channel. [-1.0, 1.0]
/// * `b` - Blue-yellow channel. [-1.0, 1.0]
/// * `alpha` - Alpha channel. [0.0, 1.0]
pub const fn new(l: f32, a: f32, b: f32, alpha: f32) -> Self {
Self { l, a, b, alpha }
pub const fn new(lightness: f32, a: f32, b: f32, alpha: f32) -> Self {
Self {
lightness,
a,
b,
alpha,
}
}

/// Construct a new [`Oklaba`] color from (l, a, b) components, with the default alpha (1.0).
///
/// # Arguments
///
/// * `l` - Lightness channel. [0.0, 1.0]
/// * `lightness` - Lightness channel. [0.0, 1.0]
/// * `a` - Green-red channel. [-1.0, 1.0]
/// * `b` - Blue-yellow channel. [-1.0, 1.0]
pub const fn lch(l: f32, a: f32, b: f32) -> Self {
pub const fn lab(lightness: f32, a: f32, b: f32) -> Self {
Self {
l,
lightness,
a,
b,
alpha: 1.0,
}
}

/// Return a copy of this color with the 'l' channel set to the given value.
pub const fn with_l(self, l: f32) -> Self {
Self { l, ..self }
pub const fn with_lightness(self, lightness: f32) -> Self {
Self { lightness, ..self }
}

/// Return a copy of this color with the 'a' channel set to the given value.
Expand All @@ -81,7 +86,7 @@ impl Mix for Oklaba {
fn mix(&self, other: &Self, factor: f32) -> Self {
let n_factor = 1.0 - factor;
Self {
l: self.l * n_factor + other.l * factor,
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,
Expand All @@ -108,27 +113,39 @@ impl Alpha for Oklaba {

impl Luminance for Oklaba {
#[inline]
fn with_luminance(&self, l: f32) -> Self {
Self { l, ..*self }
fn with_luminance(&self, lightness: f32) -> Self {
Self { lightness, ..*self }
}

fn luminance(&self) -> f32 {
self.l
self.lightness
}

fn darker(&self, amount: f32) -> Self {
Self::new((self.l - amount).max(0.), self.a, self.b, self.alpha)
Self::new(
(self.lightness - amount).max(0.),
self.a,
self.b,
self.alpha,
)
}

fn lighter(&self, amount: f32) -> Self {
Self::new((self.l + amount).min(1.), self.a, self.b, self.alpha)
Self::new(
(self.lightness + amount).min(1.),
self.a,
self.b,
self.alpha,
)
}
}

impl EuclideanDistance for Oklaba {
#[inline]
fn distance_squared(&self, other: &Self) -> f32 {
(self.l - other.l).powi(2) + (self.a - other.a).powi(2) + (self.b - other.b).powi(2)
(self.lightness - other.lightness).powi(2)
+ (self.a - other.a).powi(2)
+ (self.b - other.b).powi(2)
}
}

Expand Down Expand Up @@ -158,12 +175,17 @@ impl From<LinearRgba> for Oklaba {
#[allow(clippy::excessive_precision)]
impl From<Oklaba> for LinearRgba {
fn from(value: Oklaba) -> Self {
let Oklaba { l, a, b, alpha } = value;
let Oklaba {
lightness,
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_ = lightness + 0.3963377774 * a + 0.2158037573 * b;
let m_ = lightness - 0.1055613458 * a - 0.0638541728 * b;
let s_ = lightness - 0.0894841775 * a - 1.2914855480 * b;

let l = l_ * l_ * l_;
let m = m_ * m_ * m_;
Expand Down Expand Up @@ -266,7 +288,7 @@ mod tests {
let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
let srgba: Srgba = oklaba.into();
let oklaba2: Oklaba = srgba.into();
assert_approx_eq!(oklaba.l, oklaba2.l, 0.001);
assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
Expand Down Expand Up @@ -299,7 +321,7 @@ mod tests {
let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
let linear: LinearRgba = oklaba.into();
let oklaba2: Oklaba = linear.into();
assert_approx_eq!(oklaba.l, oklaba2.l, 0.001);
assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
Expand Down
10 changes: 8 additions & 2 deletions crates/bevy_color/src/oklcha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,14 @@ impl EuclideanDistance for Oklcha {
}

impl From<Oklaba> for Oklcha {
fn from(Oklaba { l, a, b, alpha }: Oklaba) -> Self {
let lightness = l;
fn from(
Oklaba {
lightness,
a,
b,
alpha,
}: Oklaba,
) -> Self {
let chroma = a.hypot(b);
let hue = b.atan2(a).to_degrees();

Expand Down

0 comments on commit 1610738

Please sign in to comment.