From 4e2dc4b15fe4a596e9e9b8384b44beac0ddb6005 Mon Sep 17 00:00:00 2001 From: Matt Thompson <111157855+boondocklabs@users.noreply.github.com> Date: Mon, 10 Mar 2025 14:52:04 -0700 Subject: [PATCH] Add Saturation trait to bevy_color (#18202) # Objective - Allow for convenient access and mutation of color saturation providing following the `Hue`, `Luminance` traits. - `with_saturation()` builder method - `saturation()` to get the saturation of a `Color` - `set_saturation()` to set the saturation of a mutable `Color` ## Solution - Defined `Saturation` trait in `color_ops.rs` - Implemented `Saturation` on `Hsla` and `Hsva` - Implemented `Saturation` on `Color` which proxies to other color space impls. - In the case of colorspaces which don't have native saturation components the color is converted to 'Hsla` internally ## Testing - Empirically tested --- ## Showcase ```rust fn next_golden(&mut self) -> Color { self.current .rotate_hue((1.0 / GOLDEN_RATIO) * 360.0) .with_saturation(self.rand_saturation()) .with_luminance(self.rand_luminance()) .into() } ``` --------- Co-authored-by: Alice Cecile --- crates/bevy_color/src/color.rs | 40 +++++++++++++++++++++++++++++- crates/bevy_color/src/color_ops.rs | 15 +++++++++++ crates/bevy_color/src/hsla.rs | 24 ++++++++++++++++-- crates/bevy_color/src/hsva.rs | 23 ++++++++++++++++- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/crates/bevy_color/src/color.rs b/crates/bevy_color/src/color.rs index d4754d204e..47c3fc599a 100644 --- a/crates/bevy_color/src/color.rs +++ b/crates/bevy_color/src/color.rs @@ -1,6 +1,6 @@ use crate::{ color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba, - Luminance, Mix, Oklaba, Oklcha, Srgba, StandardColor, Xyza, + Luminance, Mix, Oklaba, Oklcha, Saturation, Srgba, StandardColor, Xyza, }; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -810,6 +810,44 @@ impl Hue for Color { } } +impl Saturation for Color { + fn with_saturation(&self, saturation: f32) -> Self { + let mut new = *self; + + match &mut new { + Color::Srgba(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::LinearRgba(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::Hsla(x) => x.with_saturation(saturation).into(), + Color::Hsva(x) => x.with_saturation(saturation).into(), + Color::Hwba(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::Laba(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::Lcha(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::Oklaba(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::Oklcha(x) => Hsla::from(*x).with_saturation(saturation).into(), + Color::Xyza(x) => Hsla::from(*x).with_saturation(saturation).into(), + } + } + + fn saturation(&self) -> f32 { + match self { + Color::Srgba(x) => Hsla::from(*x).saturation(), + Color::LinearRgba(x) => Hsla::from(*x).saturation(), + Color::Hsla(x) => x.saturation(), + Color::Hsva(x) => x.saturation(), + Color::Hwba(x) => Hsla::from(*x).saturation(), + Color::Laba(x) => Hsla::from(*x).saturation(), + Color::Lcha(x) => Hsla::from(*x).saturation(), + Color::Oklaba(x) => Hsla::from(*x).saturation(), + Color::Oklcha(x) => Hsla::from(*x).saturation(), + Color::Xyza(x) => Hsla::from(*x).saturation(), + } + } + + fn set_saturation(&mut self, saturation: f32) { + *self = self.with_saturation(saturation); + } +} + impl Mix for Color { fn mix(&self, other: &Self, factor: f32) -> Self { let mut new = *self; diff --git a/crates/bevy_color/src/color_ops.rs b/crates/bevy_color/src/color_ops.rs index 235c8c8bf3..60a535d9fe 100644 --- a/crates/bevy_color/src/color_ops.rs +++ b/crates/bevy_color/src/color_ops.rs @@ -95,6 +95,21 @@ pub trait Hue: Sized { } } +/// Trait for manipulating the saturation of a color. +/// +/// When working with color spaces that do not have native saturation components +/// the operations are performed in [`crate::Hsla`]. +pub trait Saturation: Sized { + /// Return a new version of this color with the saturation channel set to the given value. + fn with_saturation(&self, saturation: f32) -> Self; + + /// Return the saturation of this color [0.0, 1.0]. + fn saturation(&self) -> f32; + + /// Sets the saturation of this color. + fn set_saturation(&mut self, saturation: f32); +} + /// Trait with methods for converting colors to non-color types pub trait ColorToComponents { /// Convert to an f32 array diff --git a/crates/bevy_color/src/hsla.rs b/crates/bevy_color/src/hsla.rs index 6b26fbff8d..048add290b 100644 --- a/crates/bevy_color/src/hsla.rs +++ b/crates/bevy_color/src/hsla.rs @@ -1,6 +1,6 @@ use crate::{ - Alpha, ColorToComponents, Gray, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, - StandardColor, Xyza, + Alpha, ColorToComponents, Gray, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Saturation, + Srgba, StandardColor, Xyza, }; use bevy_math::{Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] @@ -159,6 +159,26 @@ impl Hue for Hsla { } } +impl Saturation for Hsla { + #[inline] + fn with_saturation(&self, saturation: f32) -> Self { + Self { + saturation, + ..*self + } + } + + #[inline] + fn saturation(&self) -> f32 { + self.saturation + } + + #[inline] + fn set_saturation(&mut self, saturation: f32) { + self.saturation = saturation; + } +} + impl Luminance for Hsla { #[inline] fn with_luminance(&self, lightness: f32) -> Self { diff --git a/crates/bevy_color/src/hsva.rs b/crates/bevy_color/src/hsva.rs index e708ccf67e..6068ecd070 100644 --- a/crates/bevy_color/src/hsva.rs +++ b/crates/bevy_color/src/hsva.rs @@ -1,5 +1,6 @@ use crate::{ - Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Srgba, StandardColor, Xyza, + Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Saturation, Srgba, + StandardColor, Xyza, }; use bevy_math::{Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] @@ -129,6 +130,26 @@ impl Hue for Hsva { } } +impl Saturation for Hsva { + #[inline] + fn with_saturation(&self, saturation: f32) -> Self { + Self { + saturation, + ..*self + } + } + + #[inline] + fn saturation(&self) -> f32 { + self.saturation + } + + #[inline] + fn set_saturation(&mut self, saturation: f32) { + self.saturation = saturation; + } +} + impl From for Hwba { fn from( Hsva {