Implement Color Operations for Color (#13285)
# Objective - Fixes #13214 ## Solution Delegates to internal type when possible, otherwise uses `ChosenColorSpace` as an intermediary. This _will_ double convert, but this is considered an acceptable compromise since use of specific colour types in performance critical colour operations is already encouraged. `ChosenColorSpace` is `Oklcha` since it's perceptually uniform while supporting all required operations, and in my opinion is the "best" for this task. Using different spaces for different operations will make documenting this double-conversion behaviour more challenging. ## Testing Changes straightforward enough to not require testing beyond current CI in my opinion. --- ## Changelog - Implemented the following traits for `Color`: - `Luminance` - `Hue` - `Mix` - `EuclideanDistance` - `ClampColor` - Added documentation to `Color` explaining the behaviour of these operations (possible conversion, etc.)
This commit is contained in:
parent
519ed5de42
commit
6482a036cb
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Alpha, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba, Oklaba, Oklcha, Srgba, StandardColor, Xyza,
|
color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba,
|
||||||
|
Luminance, Mix, Oklaba, Oklcha, Srgba, StandardColor, Xyza,
|
||||||
};
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
|
|
||||||
@ -11,6 +12,33 @@ use bevy_reflect::prelude::*;
|
|||||||
/// <div>
|
/// <div>
|
||||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||||
/// </div>
|
/// </div>
|
||||||
|
///
|
||||||
|
/// # Operations
|
||||||
|
///
|
||||||
|
/// [`Color`] supports all the standard color operations, such as [mixing](Mix),
|
||||||
|
/// [luminance](Luminance) and [hue](Hue) adjustment, [clamping](ClampColor),
|
||||||
|
/// and [diffing](EuclideanDistance). These operations delegate to the concrete color space contained
|
||||||
|
/// by [`Color`], but will convert to [`Oklch`](Oklcha) for operations which aren't supported in the
|
||||||
|
/// current space. After performing the operation, if a conversion was required, the result will be
|
||||||
|
/// converted back into the original color space.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use bevy_color::{Hue, Color};
|
||||||
|
/// let red_hsv = Color::hsv(0., 1., 1.);
|
||||||
|
/// let red_srgb = Color::srgb(1., 0., 0.);
|
||||||
|
///
|
||||||
|
/// // HSV has a definition of hue, so it will be returned.
|
||||||
|
/// red_hsv.hue();
|
||||||
|
///
|
||||||
|
/// // SRGB doesn't have a native definition for hue.
|
||||||
|
/// // Converts to Oklch and returns that result.
|
||||||
|
/// red_srgb.hue();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Oklch`](Oklcha) has been chosen as the intermediary space in cases where conversion is required
|
||||||
|
/// due to its perceptual uniformity and broad support for Bevy's color operations.
|
||||||
|
/// To avoid the cost of repeated conversion, and ensure consistent results where that is desired,
|
||||||
|
/// first convert this [`Color`] into your desired color space.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
|
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
|
||||||
#[reflect(PartialEq, Default)]
|
#[reflect(PartialEq, Default)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@ -621,3 +649,158 @@ impl From<Color> for Xyza {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Color space chosen for operations on `Color`.
|
||||||
|
type ChosenColorSpace = Oklcha;
|
||||||
|
|
||||||
|
impl Luminance for Color {
|
||||||
|
fn luminance(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Color::Srgba(x) => x.luminance(),
|
||||||
|
Color::LinearRgba(x) => x.luminance(),
|
||||||
|
Color::Hsla(x) => x.luminance(),
|
||||||
|
Color::Hsva(x) => ChosenColorSpace::from(*x).luminance(),
|
||||||
|
Color::Hwba(x) => ChosenColorSpace::from(*x).luminance(),
|
||||||
|
Color::Laba(x) => x.luminance(),
|
||||||
|
Color::Lcha(x) => x.luminance(),
|
||||||
|
Color::Oklaba(x) => x.luminance(),
|
||||||
|
Color::Oklcha(x) => x.luminance(),
|
||||||
|
Color::Xyza(x) => x.luminance(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_luminance(&self, value: f32) -> Self {
|
||||||
|
let mut new = *self;
|
||||||
|
|
||||||
|
match &mut new {
|
||||||
|
Color::Srgba(x) => *x = x.with_luminance(value),
|
||||||
|
Color::LinearRgba(x) => *x = x.with_luminance(value),
|
||||||
|
Color::Hsla(x) => *x = x.with_luminance(value),
|
||||||
|
Color::Hsva(x) => *x = ChosenColorSpace::from(*x).with_luminance(value).into(),
|
||||||
|
Color::Hwba(x) => *x = ChosenColorSpace::from(*x).with_luminance(value).into(),
|
||||||
|
Color::Laba(x) => *x = x.with_luminance(value),
|
||||||
|
Color::Lcha(x) => *x = x.with_luminance(value),
|
||||||
|
Color::Oklaba(x) => *x = x.with_luminance(value),
|
||||||
|
Color::Oklcha(x) => *x = x.with_luminance(value),
|
||||||
|
Color::Xyza(x) => *x = x.with_luminance(value),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
fn darker(&self, amount: f32) -> Self {
|
||||||
|
let mut new = *self;
|
||||||
|
|
||||||
|
match &mut new {
|
||||||
|
Color::Srgba(x) => *x = x.darker(amount),
|
||||||
|
Color::LinearRgba(x) => *x = x.darker(amount),
|
||||||
|
Color::Hsla(x) => *x = x.darker(amount),
|
||||||
|
Color::Hsva(x) => *x = ChosenColorSpace::from(*x).darker(amount).into(),
|
||||||
|
Color::Hwba(x) => *x = ChosenColorSpace::from(*x).darker(amount).into(),
|
||||||
|
Color::Laba(x) => *x = x.darker(amount),
|
||||||
|
Color::Lcha(x) => *x = x.darker(amount),
|
||||||
|
Color::Oklaba(x) => *x = x.darker(amount),
|
||||||
|
Color::Oklcha(x) => *x = x.darker(amount),
|
||||||
|
Color::Xyza(x) => *x = x.darker(amount),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lighter(&self, amount: f32) -> Self {
|
||||||
|
let mut new = *self;
|
||||||
|
|
||||||
|
match &mut new {
|
||||||
|
Color::Srgba(x) => *x = x.lighter(amount),
|
||||||
|
Color::LinearRgba(x) => *x = x.lighter(amount),
|
||||||
|
Color::Hsla(x) => *x = x.lighter(amount),
|
||||||
|
Color::Hsva(x) => *x = ChosenColorSpace::from(*x).lighter(amount).into(),
|
||||||
|
Color::Hwba(x) => *x = ChosenColorSpace::from(*x).lighter(amount).into(),
|
||||||
|
Color::Laba(x) => *x = x.lighter(amount),
|
||||||
|
Color::Lcha(x) => *x = x.lighter(amount),
|
||||||
|
Color::Oklaba(x) => *x = x.lighter(amount),
|
||||||
|
Color::Oklcha(x) => *x = x.lighter(amount),
|
||||||
|
Color::Xyza(x) => *x = x.lighter(amount),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hue for Color {
|
||||||
|
fn with_hue(&self, hue: f32) -> Self {
|
||||||
|
let mut new = *self;
|
||||||
|
|
||||||
|
match &mut new {
|
||||||
|
Color::Srgba(x) => *x = ChosenColorSpace::from(*x).with_hue(hue).into(),
|
||||||
|
Color::LinearRgba(x) => *x = ChosenColorSpace::from(*x).with_hue(hue).into(),
|
||||||
|
Color::Hsla(x) => *x = x.with_hue(hue),
|
||||||
|
Color::Hsva(x) => *x = x.with_hue(hue),
|
||||||
|
Color::Hwba(x) => *x = x.with_hue(hue),
|
||||||
|
Color::Laba(x) => *x = ChosenColorSpace::from(*x).with_hue(hue).into(),
|
||||||
|
Color::Lcha(x) => *x = x.with_hue(hue),
|
||||||
|
Color::Oklaba(x) => *x = ChosenColorSpace::from(*x).with_hue(hue).into(),
|
||||||
|
Color::Oklcha(x) => *x = x.with_hue(hue),
|
||||||
|
Color::Xyza(x) => *x = ChosenColorSpace::from(*x).with_hue(hue).into(),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hue(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Color::Srgba(x) => ChosenColorSpace::from(*x).hue(),
|
||||||
|
Color::LinearRgba(x) => ChosenColorSpace::from(*x).hue(),
|
||||||
|
Color::Hsla(x) => x.hue(),
|
||||||
|
Color::Hsva(x) => x.hue(),
|
||||||
|
Color::Hwba(x) => x.hue(),
|
||||||
|
Color::Laba(x) => ChosenColorSpace::from(*x).hue(),
|
||||||
|
Color::Lcha(x) => x.hue(),
|
||||||
|
Color::Oklaba(x) => ChosenColorSpace::from(*x).hue(),
|
||||||
|
Color::Oklcha(x) => x.hue(),
|
||||||
|
Color::Xyza(x) => ChosenColorSpace::from(*x).hue(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_hue(&mut self, hue: f32) {
|
||||||
|
*self = self.with_hue(hue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mix for Color {
|
||||||
|
fn mix(&self, other: &Self, factor: f32) -> Self {
|
||||||
|
let mut new = *self;
|
||||||
|
|
||||||
|
match &mut new {
|
||||||
|
Color::Srgba(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::LinearRgba(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Hsla(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Hsva(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Hwba(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Laba(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Lcha(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Oklaba(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Oklcha(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
Color::Xyza(x) => *x = x.mix(&(*other).into(), factor),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EuclideanDistance for Color {
|
||||||
|
fn distance_squared(&self, other: &Self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Color::Srgba(x) => x.distance_squared(&(*other).into()),
|
||||||
|
Color::LinearRgba(x) => x.distance_squared(&(*other).into()),
|
||||||
|
Color::Hsla(x) => ChosenColorSpace::from(*x).distance_squared(&(*other).into()),
|
||||||
|
Color::Hsva(x) => ChosenColorSpace::from(*x).distance_squared(&(*other).into()),
|
||||||
|
Color::Hwba(x) => ChosenColorSpace::from(*x).distance_squared(&(*other).into()),
|
||||||
|
Color::Laba(x) => ChosenColorSpace::from(*x).distance_squared(&(*other).into()),
|
||||||
|
Color::Lcha(x) => ChosenColorSpace::from(*x).distance_squared(&(*other).into()),
|
||||||
|
Color::Oklaba(x) => x.distance_squared(&(*other).into()),
|
||||||
|
Color::Oklcha(x) => x.distance_squared(&(*other).into()),
|
||||||
|
Color::Xyza(x) => ChosenColorSpace::from(*x).distance_squared(&(*other).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user