mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Add trait for clamping colors (#12525)
# Objective - Resolves #12463 ## Solution - Added `ClampColor` Due to consistency, `is_within_bounds` is a method of `ClampColor`, like `is_fully_transparent` is a method of `Alpha` --- ## Changelog ### Added - `ClampColor` trait --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
737b719dda
commit
509a5a0761
11 changed files with 384 additions and 12 deletions
|
@ -61,3 +61,20 @@ pub trait Alpha: Sized {
|
||||||
self.alpha() >= 1.0
|
self.alpha() >= 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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).
|
||||||
|
/// However, some applications, such as high dynamic range rendering or bloom rely on unbounded colors to naturally represent a wider array of choices.
|
||||||
|
pub trait ClampColor: Sized {
|
||||||
|
/// Return a new version of this color clamped, with all fields in bounds.
|
||||||
|
fn clamped(&self) -> Self;
|
||||||
|
|
||||||
|
/// Changes all the fields of this color to ensure they are within bounds.
|
||||||
|
fn clamp(&mut self) {
|
||||||
|
*self = self.clamped();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Are all the fields of this color in bounds?
|
||||||
|
fn is_within_bounds(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::{Alpha, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza};
|
use crate::{
|
||||||
|
Alpha, ClampColor, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
|
||||||
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -166,6 +168,24 @@ impl Luminance for Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Hsla {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
hue: self.hue.rem_euclid(360.),
|
||||||
|
saturation: self.saturation.clamp(0., 1.),
|
||||||
|
lightness: self.lightness.clamp(0., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=360.).contains(&self.hue)
|
||||||
|
&& (0. ..=1.).contains(&self.saturation)
|
||||||
|
&& (0. ..=1.).contains(&self.lightness)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Hsla> for Hsva {
|
impl From<Hsla> for Hsva {
|
||||||
fn from(
|
fn from(
|
||||||
Hsla {
|
Hsla {
|
||||||
|
@ -357,4 +377,21 @@ mod tests {
|
||||||
assert_approx_eq!(color.hue, reference.hue, 0.001);
|
assert_approx_eq!(color.hue, reference.hue, 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Hsla::hsl(361., 2., -1.);
|
||||||
|
let color_2 = Hsla::hsl(250.2762, 1., 0.67);
|
||||||
|
let mut color_3 = Hsla::hsl(-50., 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Hsla::hsl(1., 1., 0.));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Hsla::hsl(310., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Alpha, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -91,6 +91,24 @@ impl Alpha for Hsva {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Hsva {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
hue: self.hue.rem_euclid(360.),
|
||||||
|
saturation: self.saturation.clamp(0., 1.),
|
||||||
|
value: self.value.clamp(0., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=360.).contains(&self.hue)
|
||||||
|
&& (0. ..=1.).contains(&self.saturation)
|
||||||
|
&& (0. ..=1.).contains(&self.value)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Hsva> for Hwba {
|
impl From<Hsva> for Hwba {
|
||||||
fn from(
|
fn from(
|
||||||
Hsva {
|
Hsva {
|
||||||
|
@ -212,4 +230,21 @@ mod tests {
|
||||||
assert_approx_eq!(color.hsv.alpha, hsv2.alpha, 0.001);
|
assert_approx_eq!(color.hsv.alpha, hsv2.alpha, 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Hsva::hsv(361., 2., -1.);
|
||||||
|
let color_2 = Hsva::hsv(250.2762, 1., 0.67);
|
||||||
|
let mut color_3 = Hsva::hsv(-50., 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Hsva::hsv(1., 1., 0.));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Hsva::hsv(310., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, Lcha, LinearRgba, Srgba, StandardColor, Xyza};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -95,6 +95,24 @@ impl Alpha for Hwba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Hwba {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
hue: self.hue.rem_euclid(360.),
|
||||||
|
whiteness: self.whiteness.clamp(0., 1.),
|
||||||
|
blackness: self.blackness.clamp(0., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=360.).contains(&self.hue)
|
||||||
|
&& (0. ..=1.).contains(&self.whiteness)
|
||||||
|
&& (0. ..=1.).contains(&self.blackness)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Srgba> for Hwba {
|
impl From<Srgba> for Hwba {
|
||||||
fn from(
|
fn from(
|
||||||
Srgba {
|
Srgba {
|
||||||
|
@ -245,4 +263,21 @@ mod tests {
|
||||||
assert_approx_eq!(color.hwb.alpha, hwb2.alpha, 0.001);
|
assert_approx_eq!(color.hwb.alpha, hwb2.alpha, 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Hwba::hwb(361., 2., -1.);
|
||||||
|
let color_2 = Hwba::hwb(250.2762, 1., 0.67);
|
||||||
|
let mut color_3 = Hwba::hwb(-50., 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Hwba::hwb(1., 1., 0.));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Hwba::hwb(310., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Alpha, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
Alpha, ClampColor, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor,
|
||||||
|
Xyza,
|
||||||
};
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -110,6 +111,24 @@ impl Alpha for Laba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Laba {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
lightness: self.lightness.clamp(0., 1.5),
|
||||||
|
a: self.a.clamp(-1.5, 1.5),
|
||||||
|
b: self.b.clamp(-1.5, 1.5),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.5).contains(&self.lightness)
|
||||||
|
&& (-1.5..=1.5).contains(&self.a)
|
||||||
|
&& (-1.5..=1.5).contains(&self.b)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Luminance for Laba {
|
impl Luminance for Laba {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_luminance(&self, lightness: f32) -> Self {
|
fn with_luminance(&self, lightness: f32) -> Self {
|
||||||
|
@ -353,4 +372,21 @@ mod tests {
|
||||||
assert_approx_eq!(color.lab.alpha, laba.alpha, 0.001);
|
assert_approx_eq!(color.lab.alpha, laba.alpha, 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Laba::lab(-1., 2., -2.);
|
||||||
|
let color_2 = Laba::lab(1., 1.5, -1.2);
|
||||||
|
let mut color_3 = Laba::lab(-0.4, 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Laba::lab(0., 1.5, -1.5));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Laba::lab(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Alpha, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -166,6 +166,24 @@ impl Luminance for Lcha {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Lcha {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
lightness: self.lightness.clamp(0., 1.5),
|
||||||
|
chroma: self.chroma.clamp(0., 1.5),
|
||||||
|
hue: self.hue.rem_euclid(360.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.5).contains(&self.lightness)
|
||||||
|
&& (0. ..=1.5).contains(&self.chroma)
|
||||||
|
&& (0. ..=360.).contains(&self.hue)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Lcha> for Laba {
|
impl From<Lcha> for Laba {
|
||||||
fn from(
|
fn from(
|
||||||
Lcha {
|
Lcha {
|
||||||
|
@ -313,4 +331,21 @@ mod tests {
|
||||||
assert_approx_eq!(color.lch.alpha, lcha.alpha, 0.001);
|
assert_approx_eq!(color.lch.alpha, lcha.alpha, 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Lcha::lch(-1., 2., 400.);
|
||||||
|
let color_2 = Lcha::lch(1., 1.5, 249.54);
|
||||||
|
let mut color_3 = Lcha::lch(-0.4, 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Lcha::lch(0., 1.5, 40.));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Lcha::lch(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use std::ops::{Div, Mul};
|
use std::ops::{Div, Mul};
|
||||||
|
|
||||||
use crate::{color_difference::EuclideanDistance, Alpha, Luminance, Mix, StandardColor};
|
use crate::{
|
||||||
|
color_difference::EuclideanDistance, Alpha, ClampColor, Luminance, Mix, StandardColor,
|
||||||
|
};
|
||||||
use bevy_math::Vec4;
|
use bevy_math::Vec4;
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
@ -256,6 +258,24 @@ impl EuclideanDistance for LinearRgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for LinearRgba {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
red: self.red.clamp(0., 1.),
|
||||||
|
green: self.green.clamp(0., 1.),
|
||||||
|
blue: self.blue.clamp(0., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.).contains(&self.red)
|
||||||
|
&& (0. ..=1.).contains(&self.green)
|
||||||
|
&& (0. ..=1.).contains(&self.blue)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LinearRgba> for [f32; 4] {
|
impl From<LinearRgba> for [f32; 4] {
|
||||||
fn from(color: LinearRgba) -> Self {
|
fn from(color: LinearRgba) -> Self {
|
||||||
[color.red, color.green, color.blue, color.alpha]
|
[color.red, color.green, color.blue, color.alpha]
|
||||||
|
@ -455,4 +475,21 @@ mod tests {
|
||||||
let twice_as_light = color.lighter(0.2);
|
let twice_as_light = color.lighter(0.2);
|
||||||
assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
|
assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = LinearRgba::rgb(2., -1., 0.4);
|
||||||
|
let color_2 = LinearRgba::rgb(0.031, 0.749, 1.);
|
||||||
|
let mut color_3 = LinearRgba::rgb(-1., 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), LinearRgba::rgb(1., 0., 0.4));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, LinearRgba::rgb(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix,
|
color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hwba, Lcha, LinearRgba,
|
||||||
Srgba, StandardColor, Xyza,
|
Luminance, Mix, Srgba, StandardColor, Xyza,
|
||||||
};
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -149,6 +149,24 @@ impl EuclideanDistance for Oklaba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Oklaba {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
lightness: self.lightness.clamp(0., 1.),
|
||||||
|
a: self.a.clamp(-1., 1.),
|
||||||
|
b: self.b.clamp(-1., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.).contains(&self.lightness)
|
||||||
|
&& (-1. ..=1.).contains(&self.a)
|
||||||
|
&& (-1. ..=1.).contains(&self.b)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::excessive_precision)]
|
#[allow(clippy::excessive_precision)]
|
||||||
impl From<LinearRgba> for Oklaba {
|
impl From<LinearRgba> for Oklaba {
|
||||||
fn from(value: LinearRgba) -> Self {
|
fn from(value: LinearRgba) -> Self {
|
||||||
|
@ -326,4 +344,21 @@ mod tests {
|
||||||
assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
|
assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
|
||||||
assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
|
assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Oklaba::lab(-1., 2., -2.);
|
||||||
|
let color_2 = Oklaba::lab(1., 0.42, -0.4);
|
||||||
|
let mut color_3 = Oklaba::lab(-0.4, 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Oklaba::lab(0., 1., -1.));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Oklaba::lab(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hwba, Laba, Lcha, LinearRgba,
|
color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hwba, Laba, Lcha,
|
||||||
Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
||||||
};
|
};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -209,6 +209,24 @@ impl From<Oklcha> for Oklaba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Oklcha {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
lightness: self.lightness.clamp(0., 1.),
|
||||||
|
chroma: self.chroma.clamp(0., 1.),
|
||||||
|
hue: self.hue.rem_euclid(360.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.).contains(&self.lightness)
|
||||||
|
&& (0. ..=1.).contains(&self.chroma)
|
||||||
|
&& (0. ..=360.).contains(&self.hue)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Derived Conversions
|
// Derived Conversions
|
||||||
|
|
||||||
impl From<Hsla> for Oklcha {
|
impl From<Hsla> for Oklcha {
|
||||||
|
@ -355,4 +373,21 @@ mod tests {
|
||||||
assert_approx_eq!(oklcha.hue, oklcha2.hue, 0.001);
|
assert_approx_eq!(oklcha.hue, oklcha2.hue, 0.001);
|
||||||
assert_approx_eq!(oklcha.alpha, oklcha2.alpha, 0.001);
|
assert_approx_eq!(oklcha.alpha, oklcha2.alpha, 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Oklcha::lch(-1., 2., 400.);
|
||||||
|
let color_2 = Oklcha::lch(1., 1., 249.54);
|
||||||
|
let mut color_3 = Oklcha::lch(-0.4, 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Oklcha::lch(0., 1., 40.));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Oklcha::lch(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ops::{Div, Mul};
|
use std::ops::{Div, Mul};
|
||||||
|
|
||||||
use crate::color_difference::EuclideanDistance;
|
use crate::color_difference::EuclideanDistance;
|
||||||
use crate::{Alpha, LinearRgba, Luminance, Mix, StandardColor, Xyza};
|
use crate::{Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor, Xyza};
|
||||||
use bevy_math::Vec4;
|
use bevy_math::Vec4;
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -307,6 +307,24 @@ impl EuclideanDistance for Srgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Srgba {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
red: self.red.clamp(0., 1.),
|
||||||
|
green: self.green.clamp(0., 1.),
|
||||||
|
blue: self.blue.clamp(0., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.).contains(&self.red)
|
||||||
|
&& (0. ..=1.).contains(&self.green)
|
||||||
|
&& (0. ..=1.).contains(&self.blue)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LinearRgba> for Srgba {
|
impl From<LinearRgba> for Srgba {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value: LinearRgba) -> Self {
|
fn from(value: LinearRgba) -> Self {
|
||||||
|
@ -490,4 +508,21 @@ mod tests {
|
||||||
assert!(matches!(Srgba::hex("yyy"), Err(HexColorError::Parse(_))));
|
assert!(matches!(Srgba::hex("yyy"), Err(HexColorError::Parse(_))));
|
||||||
assert!(matches!(Srgba::hex("##fff"), Err(HexColorError::Parse(_))));
|
assert!(matches!(Srgba::hex("##fff"), Err(HexColorError::Parse(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Srgba::rgb(2., -1., 0.4);
|
||||||
|
let color_2 = Srgba::rgb(0.031, 0.749, 1.);
|
||||||
|
let mut color_3 = Srgba::rgb(-1., 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Srgba::rgb(1., 0., 0.4));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Srgba::rgb(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Alpha, LinearRgba, Luminance, Mix, StandardColor};
|
use crate::{Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -134,6 +134,24 @@ impl Mix for Xyza {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClampColor for Xyza {
|
||||||
|
fn clamped(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x.clamp(0., 1.),
|
||||||
|
y: self.y.clamp(0., 1.),
|
||||||
|
z: self.z.clamp(0., 1.),
|
||||||
|
alpha: self.alpha.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_within_bounds(&self) -> bool {
|
||||||
|
(0. ..=1.).contains(&self.x)
|
||||||
|
&& (0. ..=1.).contains(&self.y)
|
||||||
|
&& (0. ..=1.).contains(&self.z)
|
||||||
|
&& (0. ..=1.).contains(&self.alpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LinearRgba> for Xyza {
|
impl From<LinearRgba> for Xyza {
|
||||||
fn from(
|
fn from(
|
||||||
LinearRgba {
|
LinearRgba {
|
||||||
|
@ -208,4 +226,21 @@ mod tests {
|
||||||
assert_approx_eq!(color.xyz.alpha, xyz2.alpha, 0.001);
|
assert_approx_eq!(color.xyz.alpha, xyz2.alpha, 0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp() {
|
||||||
|
let color_1 = Xyza::xyz(2., -1., 0.4);
|
||||||
|
let color_2 = Xyza::xyz(0.031, 0.749, 1.);
|
||||||
|
let mut color_3 = Xyza::xyz(-1., 1., 1.);
|
||||||
|
|
||||||
|
assert!(!color_1.is_within_bounds());
|
||||||
|
assert_eq!(color_1.clamped(), Xyza::xyz(1., 0., 0.4));
|
||||||
|
|
||||||
|
assert!(color_2.is_within_bounds());
|
||||||
|
assert_eq!(color_2, color_2.clamped());
|
||||||
|
|
||||||
|
color_3.clamp();
|
||||||
|
assert!(color_3.is_within_bounds());
|
||||||
|
assert_eq!(color_3, Xyza::xyz(0., 1., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue