Add hue traits (#12399)
# Objective Fixes #12200 . ## Solution I added a Hue Trait with the rotate_hue method to enable hue rotation. Additionally, I modified the implementation of animations in the animated_material sample. --- ## Changelog - Added a `Hue` trait to `bevy_color/src/color_ops.rs`. - Added the `Hue` trait implementation to `Hsla`, `Hsva`, `Hwba`, `Lcha`, and `Oklcha`. - Updated animated_material sample. ## Migration Guide Users of Oklcha need to change their usage to use the with_hue method instead of the with_h method. --------- Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
887bc27a6f
commit
0950348916
@ -62,6 +62,24 @@ pub trait Alpha: Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for manipulating the hue of a color.
|
||||||
|
pub trait Hue: Sized {
|
||||||
|
/// Return a new version of this color with the hue channel set to the given value.
|
||||||
|
fn with_hue(&self, hue: f32) -> Self;
|
||||||
|
|
||||||
|
/// Return the hue of this color [0.0, 360.0].
|
||||||
|
fn hue(&self) -> f32;
|
||||||
|
|
||||||
|
/// Sets the hue of this color.
|
||||||
|
fn set_hue(&mut self, hue: f32);
|
||||||
|
|
||||||
|
/// Return a new version of this color with the hue channel rotated by the given degrees.
|
||||||
|
fn rotate_hue(&self, degrees: f32) -> Self {
|
||||||
|
let rotated_hue = (self.hue() + degrees).rem_euclid(360.);
|
||||||
|
self.with_hue(rotated_hue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait with methods for asserting a colorspace is within bounds.
|
/// Trait with methods for asserting a colorspace is within bounds.
|
||||||
///
|
///
|
||||||
/// During ordinary usage (e.g. reading images from disk, rendering images, picking colors for UI), colors should always be within their ordinary bounds (such as 0 to 1 for RGB colors).
|
/// During ordinary usage (e.g. reading images from disk, rendering images, picking colors for UI), colors should always be within their ordinary bounds (such as 0 to 1 for RGB colors).
|
||||||
@ -78,3 +96,21 @@ pub trait ClampColor: Sized {
|
|||||||
/// Are all the fields of this color in bounds?
|
/// Are all the fields of this color in bounds?
|
||||||
fn is_within_bounds(&self) -> bool;
|
fn is_within_bounds(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::Hsla;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rotate_hue() {
|
||||||
|
let hsla = Hsla::hsl(180.0, 1.0, 0.5);
|
||||||
|
assert_eq!(hsla.rotate_hue(90.0), Hsla::hsl(270.0, 1.0, 0.5));
|
||||||
|
assert_eq!(hsla.rotate_hue(-90.0), Hsla::hsl(90.0, 1.0, 0.5));
|
||||||
|
assert_eq!(hsla.rotate_hue(180.0), Hsla::hsl(0.0, 1.0, 0.5));
|
||||||
|
assert_eq!(hsla.rotate_hue(-180.0), Hsla::hsl(0.0, 1.0, 0.5));
|
||||||
|
assert_eq!(hsla.rotate_hue(0.0), hsla);
|
||||||
|
assert_eq!(hsla.rotate_hue(360.0), hsla);
|
||||||
|
assert_eq!(hsla.rotate_hue(-360.0), hsla);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Alpha, ClampColor, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
|
Alpha, ClampColor, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor,
|
||||||
|
Xyza,
|
||||||
};
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -54,11 +55,6 @@ impl Hsla {
|
|||||||
Self::new(hue, saturation, lightness, 1.0)
|
Self::new(hue, saturation, lightness, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of this color with the hue channel set to the given value.
|
|
||||||
pub const fn with_hue(self, hue: f32) -> Self {
|
|
||||||
Self { hue, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a copy of this color with the saturation channel set to the given value.
|
/// Return a copy of this color with the saturation channel set to the given value.
|
||||||
pub const fn with_saturation(self, saturation: f32) -> Self {
|
pub const fn with_saturation(self, saturation: f32) -> Self {
|
||||||
Self { saturation, ..self }
|
Self { saturation, ..self }
|
||||||
@ -143,6 +139,23 @@ impl Alpha for Hsla {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hue for Hsla {
|
||||||
|
#[inline]
|
||||||
|
fn with_hue(&self, hue: f32) -> Self {
|
||||||
|
Self { hue, ..*self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn hue(&self) -> f32 {
|
||||||
|
self.hue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_hue(&mut self, hue: f32) {
|
||||||
|
self.hue = hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Luminance for Hsla {
|
impl Luminance for Hsla {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_luminance(&self, lightness: f32) -> Self {
|
fn with_luminance(&self, lightness: f32) -> Self {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{Alpha, ClampColor, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, Hue, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -52,11 +52,6 @@ impl Hsva {
|
|||||||
Self::new(hue, saturation, value, 1.0)
|
Self::new(hue, saturation, value, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of this color with the hue channel set to the given value.
|
|
||||||
pub const fn with_hue(self, hue: f32) -> Self {
|
|
||||||
Self { hue, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a copy of this color with the saturation channel set to the given value.
|
/// Return a copy of this color with the saturation channel set to the given value.
|
||||||
pub const fn with_saturation(self, saturation: f32) -> Self {
|
pub const fn with_saturation(self, saturation: f32) -> Self {
|
||||||
Self { saturation, ..self }
|
Self { saturation, ..self }
|
||||||
@ -91,6 +86,23 @@ impl Alpha for Hsva {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hue for Hsva {
|
||||||
|
#[inline]
|
||||||
|
fn with_hue(&self, hue: f32) -> Self {
|
||||||
|
Self { hue, ..*self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn hue(&self) -> f32 {
|
||||||
|
self.hue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_hue(&mut self, hue: f32) {
|
||||||
|
self.hue = hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ClampColor for Hsva {
|
impl ClampColor for Hsva {
|
||||||
fn clamped(&self) -> Self {
|
fn clamped(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//! in [_HWB - A More Intuitive Hue-Based Color Model_] by _Smith et al_.
|
//! in [_HWB - A More Intuitive Hue-Based Color Model_] by _Smith et al_.
|
||||||
//!
|
//!
|
||||||
//! [_HWB - A More Intuitive Hue-Based Color Model_]: https://web.archive.org/web/20240226005220/http://alvyray.com/Papers/CG/HWB_JGTv208.pdf
|
//! [_HWB - A More Intuitive Hue-Based Color Model_]: https://web.archive.org/web/20240226005220/http://alvyray.com/Papers/CG/HWB_JGTv208.pdf
|
||||||
use crate::{Alpha, ClampColor, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, Hue, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -56,11 +56,6 @@ impl Hwba {
|
|||||||
Self::new(hue, whiteness, blackness, 1.0)
|
Self::new(hue, whiteness, blackness, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of this color with the hue channel set to the given value.
|
|
||||||
pub const fn with_hue(self, hue: f32) -> Self {
|
|
||||||
Self { hue, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a copy of this color with the whiteness channel set to the given value.
|
/// Return a copy of this color with the whiteness channel set to the given value.
|
||||||
pub const fn with_whiteness(self, whiteness: f32) -> Self {
|
pub const fn with_whiteness(self, whiteness: f32) -> Self {
|
||||||
Self { whiteness, ..self }
|
Self { whiteness, ..self }
|
||||||
@ -95,6 +90,23 @@ impl Alpha for Hwba {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hue for Hwba {
|
||||||
|
#[inline]
|
||||||
|
fn with_hue(&self, hue: f32) -> Self {
|
||||||
|
Self { hue, ..*self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn hue(&self) -> f32 {
|
||||||
|
self.hue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_hue(&mut self, hue: f32) {
|
||||||
|
self.hue = hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ClampColor for Hwba {
|
impl ClampColor for Hwba {
|
||||||
fn clamped(&self) -> Self {
|
fn clamped(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{Alpha, ClampColor, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, Hue, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -56,11 +56,6 @@ impl Lcha {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of this color with the hue channel set to the given value.
|
|
||||||
pub const fn with_hue(self, hue: f32) -> Self {
|
|
||||||
Self { hue, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a copy of this color with the chroma channel set to the given value.
|
/// Return a copy of this color with the chroma channel set to the given value.
|
||||||
pub const fn with_chroma(self, chroma: f32) -> Self {
|
pub const fn with_chroma(self, chroma: f32) -> Self {
|
||||||
Self { chroma, ..self }
|
Self { chroma, ..self }
|
||||||
@ -137,6 +132,23 @@ impl Alpha for Lcha {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hue for Lcha {
|
||||||
|
#[inline]
|
||||||
|
fn with_hue(&self, hue: f32) -> Self {
|
||||||
|
Self { hue, ..*self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn hue(&self) -> f32 {
|
||||||
|
self.hue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_hue(&mut self, hue: f32) {
|
||||||
|
self.hue = hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Luminance for Lcha {
|
impl Luminance for Lcha {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_luminance(&self, lightness: f32) -> Self {
|
fn with_luminance(&self, lightness: f32) -> Self {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hwba, Laba, Lcha,
|
color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hue, Hwba, Laba, Lcha,
|
||||||
LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
||||||
};
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
@ -65,11 +65,6 @@ impl Oklcha {
|
|||||||
Self { chroma, ..self }
|
Self { chroma, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of this color with the 'hue' channel set to the given value.
|
|
||||||
pub const fn with_hue(self, hue: f32) -> Self {
|
|
||||||
Self { hue, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
|
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
|
||||||
/// color from a provided `index`.
|
/// color from a provided `index`.
|
||||||
///
|
///
|
||||||
@ -136,6 +131,23 @@ impl Alpha for Oklcha {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hue for Oklcha {
|
||||||
|
#[inline]
|
||||||
|
fn with_hue(&self, hue: f32) -> Self {
|
||||||
|
Self { hue, ..*self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn hue(&self) -> f32 {
|
||||||
|
self.hue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_hue(&mut self, hue: f32) {
|
||||||
|
self.hue = hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Luminance for Oklcha {
|
impl Luminance for Oklcha {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_luminance(&self, lightness: f32) -> Self {
|
fn with_luminance(&self, lightness: f32) -> Self {
|
||||||
|
@ -30,14 +30,19 @@ fn setup(
|
|||||||
));
|
));
|
||||||
|
|
||||||
let cube = meshes.add(Cuboid::new(0.5, 0.5, 0.5));
|
let cube = meshes.add(Cuboid::new(0.5, 0.5, 0.5));
|
||||||
|
|
||||||
|
const GOLDEN_ANGLE: f32 = 137.507_77;
|
||||||
|
|
||||||
|
let mut hsla = Hsla::hsl(0.0, 1.0, 0.5);
|
||||||
for x in -1..2 {
|
for x in -1..2 {
|
||||||
for z in -1..2 {
|
for z in -1..2 {
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: cube.clone(),
|
mesh: cube.clone(),
|
||||||
material: materials.add(Color::WHITE),
|
material: materials.add(Color::from(hsla)),
|
||||||
transform: Transform::from_translation(Vec3::new(x as f32, 0.0, z as f32)),
|
transform: Transform::from_translation(Vec3::new(x as f32, 0.0, z as f32)),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
hsla = hsla.rotate_hue(GOLDEN_ANGLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,13 +52,11 @@ fn animate_materials(
|
|||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
for (i, material_handle) in material_handles.iter().enumerate() {
|
for material_handle in material_handles.iter() {
|
||||||
if let Some(material) = materials.get_mut(material_handle) {
|
if let Some(material) = materials.get_mut(material_handle) {
|
||||||
material.base_color = Color::hsl(
|
if let Color::Hsla(ref mut hsla) = material.base_color {
|
||||||
((i as f32 * 2.345 + time.elapsed_seconds_wrapped()) * 100.0) % 360.0,
|
*hsla = hsla.rotate_hue(time.delta_seconds() * 100.0);
|
||||||
1.0,
|
}
|
||||||
0.5,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user