diff --git a/palette/README.md b/palette/README.md index e8e14fba7..a95b40963 100644 --- a/palette/README.md +++ b/palette/README.md @@ -306,17 +306,8 @@ where } } -// Add the required clamping and validation. +// Add the required clamping. impl Clamp for Color { - fn is_within_bounds(&self) -> bool { - let zero_to_one = 0.0..=1.0; - - zero_to_one.contains(&self.r) - && zero_to_one.contains(&self.g) - && zero_to_one.contains(&self.b) - && zero_to_one.contains(&self.a) - } - fn clamp(self) -> Self { Color { r: self.r.min(1.0).max(0.0), diff --git a/palette/src/alpha/alpha.rs b/palette/src/alpha/alpha.rs index b6c4fd32d..cd834d373 100644 --- a/palette/src/alpha/alpha.rs +++ b/palette/src/alpha/alpha.rs @@ -14,8 +14,8 @@ use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::float::Float; use crate::{ - clamp, Blend, Clamp, Component, ComponentWise, GetHue, Hue, Mix, Pixel, Saturate, Shade, - WithAlpha, + clamp, clamp_assign, Blend, Clamp, ClampAssign, Component, ComponentWise, GetHue, Hue, + IsWithinBounds, Mix, Pixel, Saturate, Shade, WithAlpha, }; /// An alpha component wrapper for colors. @@ -186,21 +186,33 @@ impl Saturate for Alpha { } } -impl Clamp for Alpha { +impl IsWithinBounds for Alpha { #[inline] fn is_within_bounds(&self) -> bool { - self.color.is_within_bounds() && self.alpha >= T::zero() && self.alpha <= T::max_intensity() + self.color.is_within_bounds() + && self.alpha >= Self::min_alpha() + && self.alpha <= Self::max_alpha() } +} +impl Clamp for Alpha { #[inline] fn clamp(self) -> Self { Alpha { color: self.color.clamp(), - alpha: clamp(self.alpha, T::zero(), T::max_intensity()), + alpha: clamp(self.alpha, Self::min_alpha(), Self::max_alpha()), } } } +impl ClampAssign for Alpha { + #[inline] + fn clamp_assign(&mut self) { + self.color.clamp_assign(); + clamp_assign(&mut self.alpha, Self::min_alpha(), Self::max_alpha()); + } +} + impl Blend for Alpha where C::Color: ComponentWise, diff --git a/palette/src/convert.rs b/palette/src/convert.rs index 9eb954a9d..c9dfa4a80 100644 --- a/palette/src/convert.rs +++ b/palette/src/convert.rs @@ -281,7 +281,7 @@ use core::fmt::{self, Display, Formatter}; #[doc(hidden)] pub use palette_derive::FromColorUnclamped; -use crate::Clamp; +use crate::{Clamp, IsWithinBounds}; /// The error type for a color conversion that converted a color into a color /// with invalid values. @@ -326,7 +326,7 @@ pub trait IntoColor: Sized { /// Convert into T with values clamped to the color defined bounds /// /// ``` - /// use palette::{Clamp, IntoColor, Lch, Srgb}; + /// use palette::{IsWithinBounds, IntoColor, Lch, Srgb}; /// /// let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color(); /// assert!(rgb.is_within_bounds()); @@ -345,7 +345,7 @@ pub trait IntoColorUnclamped: Sized { /// /// ``` /// use palette::convert::IntoColorUnclamped; - /// use palette::{Clamp, Lch, Srgb}; + /// use palette::{IsWithinBounds, Lch, Srgb}; /// ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color_unclamped(); ///assert!(!rgb.is_within_bounds()); @@ -382,34 +382,39 @@ pub trait TryIntoColor: Sized { ///A trait for converting one color from another, in a possibly lossy way. /// -/// `U: FromColor` is implemented for every type `U: FromColorUnclamped + Clamp`. +/// `U: FromColor` is implemented for every type `U: FromColorUnclamped + +/// Clamp`. /// -/// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for a lossless version of this trait. -/// See [`TryFromColor`](crate::convert::TryFromColor) for a trait that gives an error when the result -/// is out of bounds. +/// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for a +/// lossless version of this trait. See +/// [`TryFromColor`](crate::convert::TryFromColor) for a trait that gives an +/// error when the result is out of bounds. /// /// # The Difference Between FromColor and From /// -/// The conversion traits, including `FromColor`, were added to gain even more flexibility -/// than what `From` and the other standard library traits can give. There are a few subtle, -/// but important, differences in their semantics: +/// The conversion traits, including `FromColor`, were added to gain even more +/// flexibility than what `From` and the other standard library traits can give. +/// There are a few subtle, but important, differences in their semantics: /// -/// * `FromColor` and `IntoColor` are allowed to be lossy, meaning converting `A -> B -> A` -/// may result in a different value than the original. This applies to `A -> A` as well. -/// * `From` and `Into` are blanket implemented, while `FromColor` and -/// `IntoColor` have to be manually implemented. This allows additional flexibility, -/// such as allowing implementing `FromColor> for Rgb`. -/// * Implementing `FromColorUnclamped` and `Clamp` is enough to get all the other conversion -/// traits, while `From` and `Into` would not be possible to blanket implement in the same way. -/// This also reduces the work that needs to be done by macros. +/// * `FromColor` and `IntoColor` are allowed to be lossy, meaning converting `A +/// -> B -> A` may result in a different value than the original. This applies +/// to `A -> A` as well. +/// * `From` and `Into` are blanket implemented, while +/// `FromColor` and `IntoColor` have to be manually implemented. +/// This allows additional flexibility, such as allowing implementing +/// `FromColor> for Rgb`. +/// * Implementing `FromColorUnclamped`, [`IsWithinBounds`] and [`Clamp`] is +/// enough to get all the other conversion traits, while `From` and `Into` +/// would not be possible to blanket implement in the same way. This also +/// reduces the work that needs to be done by macros. /// -/// See the [`convert`](crate::convert) module for how to implement `FromColorUnclamped` for -/// custom colors. +/// See the [`convert`](crate::convert) module for how to implement +/// `FromColorUnclamped` for custom colors. pub trait FromColor: Sized { /// Convert from T with values clamped to the color defined bounds. /// /// ``` - /// use palette::{Clamp, FromColor, Lch, Srgb}; + /// use palette::{IsWithinBounds, FromColor, Lch, Srgb}; /// /// let rgb = Srgb::from_color(Lch::new(50.0, 100.0, -175.0)); /// assert!(rgb.is_within_bounds()); @@ -431,7 +436,7 @@ pub trait FromColorUnclamped: Sized { /// /// ``` /// use palette::convert::FromColorUnclamped; - /// use palette::{Clamp, Lch, Srgb}; + /// use palette::{IsWithinBounds, Lch, Srgb}; /// /// let rgb = Srgb::from_color_unclamped(Lch::new(50.0, 100.0, -175.0)); /// assert!(!rgb.is_within_bounds()); @@ -476,17 +481,13 @@ where { #[inline] fn from_color(t: T) -> Self { - let mut this = Self::from_color_unclamped(t); - if !this.is_within_bounds() { - this = this.clamp(); - } - this + Self::from_color_unclamped(t).clamp() } } impl TryFromColor for U where - U: FromColorUnclamped + Clamp, + U: FromColorUnclamped + IsWithinBounds, { #[inline] fn try_from_color(t: T) -> Result> { @@ -537,8 +538,9 @@ mod tests { use crate::encoding::linear::Linear; use crate::luma::{Luma, LumaStandard}; use crate::rgb::{Rgb, RgbSpace}; - use crate::{Alpha, Hsl, Hsluv, Hsv, Hwb, Lab, Lch, Luv, Xyz, Yxy}; - use crate::{Clamp, FloatComponent}; + use crate::{ + Alpha, Clamp, FloatComponent, Hsl, Hsluv, Hsv, Hwb, IsWithinBounds, Lab, Lch, Luv, Xyz, Yxy, + }; #[derive(FromColorUnclamped, WithAlpha)] #[palette( @@ -558,11 +560,13 @@ mod tests { impl Copy for WithXyz {} - impl Clamp for WithXyz { + impl IsWithinBounds for WithXyz { fn is_within_bounds(&self) -> bool { true } + } + impl Clamp for WithXyz { fn clamp(self) -> Self { self } @@ -617,11 +621,13 @@ mod tests { )] struct WithoutXyz(PhantomData); - impl Clamp for WithoutXyz { + impl IsWithinBounds for WithoutXyz { fn is_within_bounds(&self) -> bool { true } + } + impl Clamp for WithoutXyz { fn clamp(self) -> Self { self } diff --git a/palette/src/hsl.rs b/palette/src/hsl.rs index ca9eca0cb..98b0a9c85 100644 --- a/palette/src/hsl.rs +++ b/palette/src/hsl.rs @@ -16,8 +16,9 @@ use crate::encoding::pixel::RawPixel; use crate::encoding::Srgb; use crate::rgb::{Rgb, RgbSpace, RgbStandard}; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Clamp, Component, FloatComponent, GetHue, Hsv, Hue, - Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, from_f64, Alpha, Clamp, ClampAssign, Component, + FloatComponent, GetHue, Hsv, Hue, IsWithinBounds, Mix, Pixel, RelativeContrast, RgbHue, + Saturate, Shade, Xyz, }; #[cfg(feature = "random")] use crate::{float::Float, FromF64}; @@ -349,7 +350,7 @@ impl From, A>> for (RgbHue, T, T, A) { } } -impl Clamp for Hsl +impl IsWithinBounds for Hsl where T: Component, { @@ -359,7 +360,12 @@ where self.saturation >= T::zero() && self.saturation <= T::max_intensity() && self.lightness >= T::zero() && self.lightness <= T::max_intensity() } +} +impl Clamp for Hsl +where + T: Component, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -370,6 +376,17 @@ where } } +impl ClampAssign for Hsl +where + T: Component, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.saturation, T::zero(), T::max_intensity()); + clamp_assign(&mut self.lightness, T::zero(), T::max_intensity()); + } +} + impl Mix for Hsl where T: FloatComponent, diff --git a/palette/src/hsluv.rs b/palette/src/hsluv.rs index 6f70338c7..1da1dba51 100644 --- a/palette/src/hsluv.rs +++ b/palette/src/hsluv.rs @@ -20,6 +20,7 @@ use crate::{ Alpha, Clamp, FloatComponent, FromF64, GetHue, Hue, Lchuv, LuvHue, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, }; +use crate::{clamp_assign, ClampAssign, IsWithinBounds}; /// HSLuv with an alpha component. See the [`Hsluva` implementation in /// `Alpha`](crate::Alpha#Hsluva). @@ -214,7 +215,7 @@ impl From, A>> for (LuvHue, T, T, A) { } } -impl Clamp for Hsluv +impl IsWithinBounds for Hsluv where T: Zero + FromF64 + PartialOrd, { @@ -224,7 +225,12 @@ where self.saturation >= Self::min_saturation() && self.saturation <= Self::max_saturation() && self.l >= Self::min_l() && self.l <= Self::max_l() } +} +impl Clamp for Hsluv +where + T: Zero + FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -239,6 +245,21 @@ where } } +impl ClampAssign for Hsluv +where + T: Zero + FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign( + &mut self.saturation, + Self::min_saturation(), + Self::max_saturation(), + ); + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + } +} + impl Mix for Hsluv where T: FloatComponent, diff --git a/palette/src/hsv.rs b/palette/src/hsv.rs index 3418a71e6..77cee4dc2 100644 --- a/palette/src/hsv.rs +++ b/palette/src/hsv.rs @@ -16,8 +16,9 @@ use crate::encoding::pixel::RawPixel; use crate::encoding::Srgb; use crate::rgb::{Rgb, RgbSpace, RgbStandard}; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Clamp, Component, FloatComponent, FromColor, GetHue, - Hsl, Hue, Hwb, Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, from_f64, Alpha, Clamp, ClampAssign, Component, + FloatComponent, FromColor, GetHue, Hsl, Hue, Hwb, IsWithinBounds, Mix, Pixel, RelativeContrast, + RgbHue, Saturate, Shade, Xyz, }; #[cfg(feature = "random")] use crate::{float::Float, FromF64}; @@ -360,7 +361,7 @@ impl From, A>> for (RgbHue, T, T, A) { } } -impl Clamp for Hsv +impl IsWithinBounds for Hsv where T: Component, { @@ -370,7 +371,12 @@ where self.saturation >= Self::min_saturation() && self.saturation <= Self::max_saturation() && self.value >= Self::min_value() && self.value <= Self::max_value() } +} +impl Clamp for Hsv +where + T: Component, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -385,6 +391,21 @@ where } } +impl ClampAssign for Hsv +where + T: Component, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign( + &mut self.saturation, + Self::min_saturation(), + Self::max_saturation(), + ); + clamp_assign(&mut self.value, Self::min_value(), Self::max_value()); + } +} + impl Mix for Hsv where T: FloatComponent, diff --git a/palette/src/hwb.rs b/palette/src/hwb.rs index 9ae0f2858..5f9d94836 100644 --- a/palette/src/hwb.rs +++ b/palette/src/hwb.rs @@ -15,8 +15,9 @@ use crate::encoding::pixel::RawPixel; use crate::encoding::Srgb; use crate::rgb::{RgbSpace, RgbStandard}; use crate::{ - clamp, clamp_min, contrast_ratio, Alpha, Clamp, Component, FloatComponent, GetHue, Hsv, Hue, - Mix, Pixel, RelativeContrast, RgbHue, Shade, Xyz, + clamp, clamp_min, clamp_min_assign, contrast_ratio, Alpha, Clamp, ClampAssign, Component, + FloatComponent, GetHue, Hsv, Hue, IsWithinBounds, Mix, Pixel, RelativeContrast, RgbHue, Shade, + Xyz, }; /// Linear HWB with an alpha component. See the [`Hwba` implementation in @@ -285,9 +286,9 @@ impl From, A>> for (RgbHue, T, T, A) { } } -impl Clamp for Hwb +impl IsWithinBounds for Hwb where - T: Component + DivAssign, + T: Component, { #[rustfmt::skip] #[inline] @@ -296,7 +297,12 @@ where self.whiteness >= Self::min_whiteness() && self.whiteness <= Self::max_blackness() && self.whiteness + self.blackness <= T::max_intensity() } +} +impl Clamp for Hwb +where + T: Component + DivAssign, +{ #[inline] fn clamp(self) -> Self { let mut whiteness = clamp_min(self.whiteness, Self::min_whiteness()); @@ -312,6 +318,23 @@ where } } +impl ClampAssign for Hwb +where + T: Component + DivAssign, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_min_assign(&mut self.whiteness, Self::min_whiteness()); + clamp_min_assign(&mut self.blackness, Self::min_blackness()); + + let sum = self.blackness + self.whiteness; + if sum > T::max_intensity() { + self.whiteness /= sum; + self.blackness /= sum; + } + } +} + impl Mix for Hwb where T: FloatComponent, diff --git a/palette/src/lab.rs b/palette/src/lab.rs index 38ea87367..e3ade5c68 100644 --- a/palette/src/lab.rs +++ b/palette/src/lab.rs @@ -9,14 +9,14 @@ use rand::distributions::{Distribution, Standard}; #[cfg(feature = "random")] use rand::Rng; -use crate::color_difference::get_ciede_difference; -use crate::color_difference::ColorDifference; +use crate::color_difference::{get_ciede_difference, ColorDifference}; use crate::convert::FromColorUnclamped; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, float::Float, from_f64, Alpha, Clamp, ComponentWise, FloatComponent, - FromF64, GetHue, LabHue, Lch, Mix, Pixel, RelativeContrast, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, float::Float, from_f64, Alpha, Clamp, ClampAssign, + ComponentWise, FloatComponent, FromF64, GetHue, IsWithinBounds, LabHue, Lch, Mix, Pixel, + RelativeContrast, Shade, Xyz, }; /// CIE L\*a\*b\* (CIELAB) with an alpha component. See the [`Laba` @@ -236,7 +236,7 @@ impl From, A>> for (T, T, T, A) { } } -impl Clamp for Lab +impl IsWithinBounds for Lab where T: Zero + FromF64 + PartialOrd, { @@ -247,7 +247,12 @@ where self.a >= Self::min_a() && self.a <= Self::max_a() && self.b >= Self::min_b() && self.b <= Self::max_b() } +} +impl Clamp for Lab +where + T: Zero + FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -258,6 +263,18 @@ where } } +impl ClampAssign for Lab +where + T: Zero + FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + clamp_assign(&mut self.a, Self::min_a(), Self::max_a()); + clamp_assign(&mut self.b, Self::min_b(), Self::max_b()); + } +} + impl Mix for Lab where T: FloatComponent, diff --git a/palette/src/lch.rs b/palette/src/lch.rs index 2d812dffb..206eb9e09 100644 --- a/palette/src/lch.rs +++ b/palette/src/lch.rs @@ -15,8 +15,9 @@ use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, clamp_min, contrast_ratio, from_f64, Alpha, Clamp, Float, FloatComponent, FromColor, - FromF64, GetHue, Hue, Lab, LabHue, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, + clamp, clamp_assign, clamp_min, clamp_min_assign, contrast_ratio, from_f64, Alpha, Clamp, + ClampAssign, Float, FloatComponent, FromColor, FromF64, GetHue, Hue, IsWithinBounds, Lab, + LabHue, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, }; /// CIE L\*C\*h° with an alpha component. See the [`Lcha` implementation in @@ -211,7 +212,7 @@ impl From, A>> for (T, T, LabHue, A) { } } -impl Clamp for Lch +impl IsWithinBounds for Lch where T: Zero + FromF64 + PartialOrd, { @@ -219,7 +220,12 @@ where fn is_within_bounds(&self) -> bool { self.l >= Self::min_l() && self.l <= Self::max_l() && self.chroma >= Self::min_chroma() } +} +impl Clamp for Lch +where + T: Zero + FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -230,6 +236,17 @@ where } } +impl ClampAssign for Lch +where + T: Zero + FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + clamp_min_assign(&mut self.chroma, Self::min_chroma()); + } +} + impl Mix for Lch where T: FloatComponent, diff --git a/palette/src/lchuv.rs b/palette/src/lchuv.rs index 487bf7445..b58bea651 100644 --- a/palette/src/lchuv.rs +++ b/palette/src/lchuv.rs @@ -14,8 +14,9 @@ use crate::encoding::pixel::RawPixel; use crate::luv_bounds::LuvBounds; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Clamp, FloatComponent, FromColor, FromF64, GetHue, - Hsluv, Hue, Luv, LuvHue, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, from_f64, Alpha, Clamp, ClampAssign, FloatComponent, + FromColor, FromF64, GetHue, Hsluv, Hue, IsWithinBounds, Luv, LuvHue, Mix, Pixel, + RelativeContrast, Saturate, Shade, Xyz, }; /// CIE L\*C\*uv h°uv with an alpha component. See the [`Lchuva` implementation in @@ -220,7 +221,7 @@ impl From, A>> for (T, T, LuvHue, A) { } } -impl Clamp for Lchuv +impl IsWithinBounds for Lchuv where T: Zero + FromF64 + PartialOrd, { @@ -231,7 +232,12 @@ where && self.chroma >= Self::min_chroma() && self.chroma <= Self::max_chroma() } +} +impl Clamp for Lchuv +where + T: Zero + FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -242,6 +248,17 @@ where } } +impl ClampAssign for Lchuv +where + T: Zero + FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + clamp_assign(&mut self.chroma, Self::min_chroma(), Self::max_chroma()); + } +} + impl Mix for Lchuv where T: FloatComponent, diff --git a/palette/src/lib.rs b/palette/src/lib.rs index 298ddb426..72331f5ca 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -272,7 +272,7 @@ macro_rules! assert_ranges { ) => ( { use core::iter::repeat; - use crate::Clamp; + use crate::{Clamp, IsWithinBounds}; { print!("checking below clamp bounds... "); @@ -468,6 +468,15 @@ fn clamp(value: T, min: T, max: T) -> T { } } +#[inline] +fn clamp_assign(value: &mut T, min: T, max: T) { + if *value < min { + *value = min; + } else if *value > max { + *value = max; + } +} + #[inline] fn clamp_min(value: T, min: T) -> T { if value < min { @@ -477,19 +486,144 @@ fn clamp_min(value: T, min: T) -> T { } } -/// A trait for clamping and checking if colors are within their ranges. -pub trait Clamp { - /// Check if the color's components are within the expected clamped range - /// bounds. - #[must_use] +#[inline] +fn clamp_min_assign(value: &mut T, min: T) { + if *value < min { + *value = min; + } +} + +/// Checks if color components are within their expected range bounds. +/// +/// A color with out-of-bounds components may be clamped with [`Clamp`] or +/// [`ClampAssign`]. +/// +/// ``` +/// use palette::{Srgb, IsWithinBounds}; +/// let a = Srgb::new(0.4, 0.3, 0.8); +/// let b = Srgb::new(1.2, 0.3, 0.8); +/// let c = Srgb::new(-0.6, 0.3, 0.8); +/// +/// assert!(a.is_within_bounds()); +/// assert!(!b.is_within_bounds()); +/// assert!(!c.is_within_bounds()); +/// ``` +/// +/// `IsWithinBounds` is also implemented for `[T]`: +/// +/// ``` +/// use palette::{Srgb, IsWithinBounds}; +/// +/// let my_vec = vec![Srgb::new(0.4, 0.3, 0.8), Srgb::new(0.8, 0.5, 0.1)]; +/// let my_array = [Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.3, 0.5, -3.0)]; +/// let my_slice = &[Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.2, 0.3, 0.8)]; +/// +/// assert!(my_vec.is_within_bounds()); +/// assert!(!my_array.is_within_bounds()); +/// assert!(!my_slice.is_within_bounds()); +/// ``` +pub trait IsWithinBounds { + /// Check if the color's components are within the expected range bounds. + /// + /// ``` + /// use palette::{Srgb, IsWithinBounds}; + /// assert!(Srgb::new(0.8, 0.5, 0.2).is_within_bounds()); + /// assert!(!Srgb::new(1.3, 0.5, -3.0).is_within_bounds()); + /// ``` fn is_within_bounds(&self) -> bool; +} - /// Return a new color where the components have been clamped to the nearest - /// valid values. +impl IsWithinBounds for [T] +where + T: IsWithinBounds, +{ + #[inline] + fn is_within_bounds(&self) -> bool { + self.iter().all(T::is_within_bounds) + } +} + +/// An operator for restricting a color's components to their expected ranges. +/// +/// [`IsWithinBounds`] can be used to check if the components are within their +/// range bounds. See also [`ClampAssign`] for a self-modifying version of +/// `Clamp`. +/// +/// ``` +/// use palette::{Srgb, IsWithinBounds, Clamp}; +/// +/// let unclamped = Srgb::new(1.3, 0.5, -3.0); +/// assert!(!unclamped.is_within_bounds()); +/// +/// let clamped = unclamped.clamp(); +/// assert!(clamped.is_within_bounds()); +/// assert_eq!(clamped, Srgb::new(1.0, 0.5, 0.0)); +/// ``` +pub trait Clamp { + /// Return a new color where out-of-bounds components have been changed to + /// the nearest valid values. + /// + /// ``` + /// use palette::{Srgb, Clamp}; + /// assert_eq!(Srgb::new(1.3, 0.5, -3.0).clamp(), Srgb::new(1.0, 0.5, 0.0)); + /// ``` #[must_use] fn clamp(self) -> Self; } +/// An assigning operator for restricting a color's components to their expected +/// ranges. +/// +/// [`IsWithinBounds`] can be used to check if the components are within their +/// range bounds. See also [`Clamp`] for a moving version of `ClampAssign`. +/// +/// ``` +/// use palette::{Srgb, IsWithinBounds, ClampAssign}; +/// +/// let mut color = Srgb::new(1.3, 0.5, -3.0); +/// assert!(!color.is_within_bounds()); +/// +/// color.clamp_assign(); +/// assert!(color.is_within_bounds()); +/// assert_eq!(color, Srgb::new(1.0, 0.5, 0.0)); +/// ``` +/// +/// `ClampAssign` is also implemented for `[T]`: +/// +/// ``` +/// use palette::{Srgb, ClampAssign}; +/// +/// let mut my_vec = vec![Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.3, 0.5, -3.0)]; +/// let mut my_array = [Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.3, 0.5, -3.0)]; +/// let mut my_slice = &mut [Srgb::new(0.4, 0.3, 0.8), Srgb::new(1.2, 0.3, 0.8)]; +/// +/// my_vec.clamp_assign(); +/// my_array.clamp_assign(); +/// my_slice.clamp_assign(); +/// ``` +pub trait ClampAssign { + /// Changes out-of-bounds components to the nearest valid values. + /// + /// ``` + /// use palette::{Srgb, ClampAssign}; + /// + /// let mut color = Srgb::new(1.3, 0.5, -3.0); + /// color.clamp_assign(); + /// assert_eq!(color, Srgb::new(1.0, 0.5, 0.0)); + /// ``` + fn clamp_assign(&mut self); +} + +impl ClampAssign for [T] +where + T: ClampAssign, +{ + #[inline] + fn clamp_assign(&mut self) { + self.iter_mut().for_each(T::clamp_assign); + } +} + /// A trait for linear color interpolation. /// /// ``` diff --git a/palette/src/luma/luma.rs b/palette/src/luma/luma.rs index dfb30ed53..66d391a48 100644 --- a/palette/src/luma/luma.rs +++ b/palette/src/luma/luma.rs @@ -19,8 +19,9 @@ use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb, TransferFn}; use crate::luma::LumaStandard; use crate::{ - clamp, contrast_ratio, Alpha, Blend, Clamp, Component, ComponentWise, FloatComponent, - FromComponent, Mix, Pixel, RelativeContrast, Shade, Xyz, Yxy, + clamp, clamp_assign, contrast_ratio, Alpha, Blend, Clamp, ClampAssign, Component, + ComponentWise, FloatComponent, FromComponent, IsWithinBounds, Mix, Pixel, RelativeContrast, + Shade, Xyz, Yxy, }; /// Luminance with an alpha component. See the [`Lumaa` implementation @@ -330,7 +331,7 @@ impl From, A>> for (T, A) { } } -impl Clamp for Luma +impl IsWithinBounds for Luma where T: Component, { @@ -338,13 +339,28 @@ where fn is_within_bounds(&self) -> bool { self.luma >= Self::min_luma() && self.luma <= Self::max_luma() } +} +impl Clamp for Luma +where + T: Component, +{ #[inline] fn clamp(self) -> Self { Self::new(clamp(self.luma, Self::min_luma(), Self::max_luma())) } } +impl ClampAssign for Luma +where + T: Component, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.luma, Self::min_luma(), Self::max_luma()); + } +} + impl Mix for Luma where T: FloatComponent, diff --git a/palette/src/luv.rs b/palette/src/luv.rs index 89d35e8e7..1836e3808 100644 --- a/palette/src/luv.rs +++ b/palette/src/luv.rs @@ -13,8 +13,9 @@ use crate::convert::FromColorUnclamped; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Clamp, ComponentWise, FloatComponent, FromF64, GetHue, - Lchuv, LuvHue, Mix, Pixel, RelativeContrast, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, from_f64, Alpha, Clamp, ClampAssign, ComponentWise, + FloatComponent, FromF64, GetHue, IsWithinBounds, Lchuv, LuvHue, Mix, Pixel, RelativeContrast, + Shade, Xyz, }; /// CIE L\*u\*v\* (CIELUV) with an alpha component. See the [`Luva` @@ -238,7 +239,7 @@ impl From, A>> for (T, T, T, A) { } } -impl Clamp for Luv +impl IsWithinBounds for Luv where T: Zero + FromF64 + PartialOrd, { @@ -249,7 +250,12 @@ where self.u >= Self::min_u() && self.u <= Self::max_u() && self.v >= Self::min_v() && self.v <= Self::max_v() } +} +impl Clamp for Luv +where + T: Zero + FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -260,6 +266,18 @@ where } } +impl ClampAssign for Luv +where + T: Zero + FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + clamp_assign(&mut self.u, Self::min_u(), Self::max_u()); + clamp_assign(&mut self.v, Self::min_v(), Self::max_v()); + } +} + impl Mix for Luv where T: FloatComponent, diff --git a/palette/src/oklab.rs b/palette/src/oklab.rs index fe7f9ab38..28cfe5fb3 100644 --- a/palette/src/oklab.rs +++ b/palette/src/oklab.rs @@ -14,8 +14,9 @@ use crate::encoding::pixel::RawPixel; use crate::matrix::multiply_xyz; use crate::white_point::D65; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Clamp, Component, ComponentWise, FloatComponent, - FromF64, GetHue, Mat3, Mix, OklabHue, Oklch, Pixel, RelativeContrast, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, from_f64, Alpha, Clamp, ClampAssign, Component, + ComponentWise, FloatComponent, FromF64, GetHue, IsWithinBounds, Mat3, Mix, OklabHue, Oklch, + Pixel, RelativeContrast, Shade, Xyz, }; #[rustfmt::skip] @@ -287,7 +288,7 @@ impl From, A>> for (T, T, T, A) { } } -impl Clamp for Oklab +impl IsWithinBounds for Oklab where T: FromF64 + PartialOrd, { @@ -298,7 +299,12 @@ where self.a >= Self::min_a() && self.a <= Self::max_a() && self.b >= Self::min_b() && self.b <= Self::max_b() } +} +impl Clamp for Oklab +where + T: FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -309,6 +315,18 @@ where } } +impl ClampAssign for Oklab +where + T: FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + clamp_assign(&mut self.a, Self::min_a(), Self::max_a()); + clamp_assign(&mut self.b, Self::min_b(), Self::max_b()); + } +} + impl Mix for Oklab where T: FloatComponent, diff --git a/palette/src/oklch.rs b/palette/src/oklch.rs index 952383f1e..89c76c6f6 100644 --- a/palette/src/oklch.rs +++ b/palette/src/oklch.rs @@ -13,8 +13,9 @@ use crate::convert::{FromColorUnclamped, IntoColorUnclamped}; use crate::encoding::pixel::RawPixel; use crate::white_point::D65; use crate::{ - clamp, contrast_ratio, from_f64, Alpha, Clamp, FloatComponent, FromColor, FromF64, GetHue, Hue, - Mix, Oklab, OklabHue, Pixel, RelativeContrast, Saturate, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, from_f64, Alpha, Clamp, ClampAssign, FloatComponent, + FromColor, FromF64, GetHue, Hue, IsWithinBounds, Mix, Oklab, OklabHue, Pixel, RelativeContrast, + Saturate, Shade, Xyz, }; /// Oklch with an alpha component. See the [`Oklcha` implementation in @@ -266,7 +267,7 @@ impl From, A>> for (T, T, OklabHue, A) { } } -impl Clamp for Oklch +impl IsWithinBounds for Oklch where T: Zero + FromF64 + PartialOrd, { @@ -277,7 +278,12 @@ where && self.chroma >= Self::min_chroma() && self.chroma <= Self::max_chroma() } +} +impl Clamp for Oklch +where + T: Zero + FromF64 + PartialOrd, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -288,6 +294,17 @@ where } } +impl ClampAssign for Oklch +where + T: Zero + FromF64 + PartialOrd, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.l, Self::min_l(), Self::max_l()); + clamp_assign(&mut self.chroma, Self::min_chroma(), Self::max_chroma()); + } +} + impl Mix for Oklch where T: FloatComponent, diff --git a/palette/src/rgb/rgb.rs b/palette/src/rgb/rgb.rs index dd57647d1..507b907fd 100644 --- a/palette/src/rgb/rgb.rs +++ b/palette/src/rgb/rgb.rs @@ -24,8 +24,9 @@ use crate::luma::LumaStandard; use crate::matrix::{matrix_inverse, multiply_xyz_to_rgb, rgb_to_xyz_matrix}; use crate::rgb::{Packed, RgbChannels, RgbSpace, RgbStandard, TransferFn}; use crate::{ - clamp, contrast_ratio, from_f64, Blend, Clamp, Component, ComponentWise, FloatComponent, - FromComponent, GetHue, Mix, Pixel, RelativeContrast, Shade, + clamp, clamp_assign, contrast_ratio, from_f64, Blend, Clamp, ClampAssign, Component, + ComponentWise, FloatComponent, FromComponent, GetHue, IsWithinBounds, Mix, Pixel, + RelativeContrast, Shade, }; use crate::{Hsl, Hsv, Luma, RgbHue, Xyz}; @@ -515,28 +516,45 @@ where } } -impl Clamp for Rgb +impl IsWithinBounds for Rgb where T: Component, { #[rustfmt::skip] #[inline] fn is_within_bounds(&self) -> bool { - self.red >= T::zero() && self.red <= T::max_intensity() && - self.green >= T::zero() && self.green <= T::max_intensity() && - self.blue >= T::zero() && self.blue <= T::max_intensity() + self.red >= Self::min_red() && self.red <= Self::max_red() && + self.green >= Self::min_green() && self.green <= Self::max_green() && + self.blue >= Self::min_blue() && self.blue <= Self::max_blue() } +} +impl Clamp for Rgb +where + T: Component, +{ #[inline] fn clamp(self) -> Self { Self::new( - clamp(self.red, T::zero(), T::max_intensity()), - clamp(self.green, T::zero(), T::max_intensity()), - clamp(self.blue, T::zero(), T::max_intensity()), + clamp(self.red, Self::min_red(), Self::max_red()), + clamp(self.green, Self::min_green(), Self::max_green()), + clamp(self.blue, Self::min_blue(), Self::max_blue()), ) } } +impl ClampAssign for Rgb +where + T: Component, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.red, Self::min_red(), Self::max_red()); + clamp_assign(&mut self.green, Self::min_green(), Self::max_green()); + clamp_assign(&mut self.blue, Self::min_blue(), Self::max_blue()); + } +} + impl Mix for Rgb where S: RgbStandard, diff --git a/palette/src/xyz.rs b/palette/src/xyz.rs index e42716d77..6c8102d58 100644 --- a/palette/src/xyz.rs +++ b/palette/src/xyz.rs @@ -16,8 +16,9 @@ use crate::matrix::{multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix}; use crate::rgb::{Rgb, RgbSpace, RgbStandard}; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, from_f64, oklab, Alpha, Clamp, ComponentWise, FloatComponent, Lab, Luma, - Luv, Mix, Oklab, Oklch, Pixel, RelativeContrast, Shade, Yxy, + clamp, clamp_assign, contrast_ratio, from_f64, oklab, Alpha, Clamp, ClampAssign, ComponentWise, + FloatComponent, IsWithinBounds, Lab, Luma, Luv, Mix, Oklab, Oklch, Pixel, RelativeContrast, + Shade, Yxy, }; /// CIE 1931 XYZ with an alpha component. See the [`Xyza` implementation in @@ -346,7 +347,7 @@ impl From, A>> for (T, T, T, A) { } } -impl Clamp for Xyz +impl IsWithinBounds for Xyz where T: Zero + PartialOrd, Wp: WhitePoint, @@ -358,7 +359,13 @@ where self.y >= Self::min_y() && self.y <= Self::max_y() && self.z >= Self::min_z() && self.z <= Self::max_z() } +} +impl Clamp for Xyz +where + T: Zero + PartialOrd, + Wp: WhitePoint, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -369,6 +376,19 @@ where } } +impl ClampAssign for Xyz +where + T: Zero + PartialOrd, + Wp: WhitePoint, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.x, Self::min_x(), Self::max_x()); + clamp_assign(&mut self.y, Self::min_y(), Self::max_y()); + clamp_assign(&mut self.z, Self::min_z(), Self::max_z()); + } +} + impl Mix for Xyz where T: FloatComponent, diff --git a/palette/src/yxy.rs b/palette/src/yxy.rs index 4b90dde27..ddfcef7c4 100644 --- a/palette/src/yxy.rs +++ b/palette/src/yxy.rs @@ -13,8 +13,8 @@ use crate::encoding::pixel::RawPixel; use crate::luma::LumaStandard; use crate::white_point::{WhitePoint, D65}; use crate::{ - clamp, contrast_ratio, Alpha, Clamp, Component, ComponentWise, FloatComponent, Luma, Mix, - Pixel, RelativeContrast, Shade, Xyz, + clamp, clamp_assign, contrast_ratio, Alpha, Clamp, ClampAssign, Component, ComponentWise, + FloatComponent, IsWithinBounds, Luma, Mix, Pixel, RelativeContrast, Shade, Xyz, }; /// CIE 1931 Yxy (xyY) with an alpha component. See the [`Yxya` implementation @@ -245,9 +245,9 @@ where } } -impl Clamp for Yxy +impl IsWithinBounds for Yxy where - T: FloatComponent, + T: Component, { #[rustfmt::skip] #[inline] @@ -256,7 +256,12 @@ where self.y >= Self::min_y() && self.y <= Self::max_y() && self.luma >= Self::min_luma() && self.luma <= Self::max_luma() } +} +impl Clamp for Yxy +where + T: Component, +{ #[inline] fn clamp(self) -> Self { Self::new( @@ -267,6 +272,18 @@ where } } +impl ClampAssign for Yxy +where + T: Component, +{ + #[inline] + fn clamp_assign(&mut self) { + clamp_assign(&mut self.x, Self::min_x(), Self::max_x()); + clamp_assign(&mut self.y, Self::min_y(), Self::max_y()); + clamp_assign(&mut self.luma, Self::min_luma(), Self::max_luma()); + } +} + impl Mix for Yxy where T: FloatComponent,