From 27178c62bc56b8915583827af0ff7be93956c7ec Mon Sep 17 00:00:00 2001 From: okaneco <47607823+okaneco@users.noreply.github.com> Date: Sat, 21 Mar 2020 18:35:00 -0400 Subject: [PATCH] Add feature "random" for random color generation using `rand` crate Implement Distribution for Standard from rand for all color types, LabHue, and RgbHue Implement SampleUniform and UniformSampler for all color types, LabHue, and RgbHue Color sampling is supported for `gen`, `gen_range`, and `Uniform`. Equations for cone sampling are placed in their own module and include unit tests. Hwb is implemented in terms of Hsv and wraps around the Hsv sampler and implementation. Co-authored-by: Erik Hedvall --- palette/Cargo.toml | 6 ++ palette/src/alpha.rs | 83 +++++++++++++++++ palette/src/hsl.rs | 98 ++++++++++++++++++++ palette/src/hsv.rs | 112 +++++++++++++++++++++- palette/src/hues.rs | 138 ++++++++++++++++++++++++++++ palette/src/hwb.rs | 81 ++++++++++++++++ palette/src/lab.rs | 102 +++++++++++++++++++- palette/src/lch.rs | 104 ++++++++++++++++++++- palette/src/lib.rs | 3 + palette/src/luma/luma.rs | 90 +++++++++++++++++- palette/src/random_sampling/cone.rs | 133 +++++++++++++++++++++++++++ palette/src/random_sampling/mod.rs | 3 + palette/src/rgb/rgb.rs | 99 +++++++++++++++++++- palette/src/xyz.rs | 101 +++++++++++++++++++- palette/src/yxy.rs | 101 +++++++++++++++++++- 15 files changed, 1228 insertions(+), 26 deletions(-) create mode 100644 palette/src/random_sampling/cone.rs create mode 100644 palette/src/random_sampling/mod.rs diff --git a/palette/Cargo.toml b/palette/Cargo.toml index 54988d2ca..3902084fb 100644 --- a/palette/Cargo.toml +++ b/palette/Cargo.toml @@ -18,6 +18,7 @@ build = "build/main.rs" default = ["named_from_str", "std"] named_from_str = ["named", "phf", "phf_codegen", "std"] named = [] +random = ["rand"] serializing = ["serde", "std"] #ignore in feature test @@ -33,6 +34,11 @@ approx = {version = "0.3", default-features = false} version = "0.8" optional = true +[dependencies.rand] +version = "0.7" +default-features = false +optional = true + [dependencies.serde] version = "1" features = ["serde_derive"] diff --git a/palette/src/alpha.rs b/palette/src/alpha.rs index de3c1e054..cae4ea449 100644 --- a/palette/src/alpha.rs +++ b/palette/src/alpha.rs @@ -2,6 +2,12 @@ use core::fmt; use core::ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; use crate::blend::PreAlpha; use crate::encoding::pixel::RawPixel; @@ -435,6 +441,83 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: Component, + Standard: Distribution + Distribution, +{ + fn sample(&self, rng: &mut R) -> Alpha { + Alpha { + color: rng.gen(), + alpha: rng.gen(), + } + } +} + +#[cfg(feature = "random")] +pub struct UniformAlpha +where + T: Component + SampleUniform, + C: SampleUniform, +{ + color: Uniform, + alpha: Uniform, +} + +#[cfg(feature = "random")] +impl SampleUniform for Alpha +where + T: Component + SampleUniform, + C: Copy + SampleUniform, +{ + type Sampler = UniformAlpha; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformAlpha +where + T: Component + SampleUniform, + C: Copy + SampleUniform, +{ + type X = Alpha; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformAlpha { + color: Uniform::new::(low.color, high.color), + alpha: Uniform::new::<_, T>(low.alpha, high.alpha), + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformAlpha { + color: Uniform::new_inclusive::(low.color, high.color), + alpha: Uniform::new_inclusive::<_, T>(low.alpha, high.alpha), + } + } + + fn sample(&self, rng: &mut R) -> Alpha { + Alpha { + color: self.color.sample(rng), + alpha: self.alpha.sample(rng), + } + } +} + #[cfg(test)] mod test { use crate::encoding::Srgb; diff --git a/palette/src/hsl.rs b/palette/src/hsl.rs index 230beabf7..de20d08e0 100644 --- a/palette/src/hsl.rs +++ b/palette/src/hsl.rs @@ -3,6 +3,12 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Sub, SubAssign}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb}; @@ -657,6 +663,98 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + S: RgbSpace, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Hsl { + crate::random_sampling::sample_hsl(rng.gen::>(), rng.gen(), rng.gen()) + } +} + +#[cfg(feature = "random")] +pub struct UniformHsl +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + hue: crate::hues::UniformRgbHue, + u1: Uniform, + u2: Uniform, + space: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Hsl +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + type Sampler = UniformHsl; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformHsl +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + type X = Hsl; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + use crate::random_sampling::invert_hsl_sample; + + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + let (r1_min, r2_min) = invert_hsl_sample(low); + let (r1_max, r2_max) = invert_hsl_sample(high); + + UniformHsl { + hue: crate::hues::UniformRgbHue::new(low.hue, high.hue), + u1: Uniform::new::<_, T>(r1_min, r1_max), + u2: Uniform::new::<_, T>(r2_min, r2_max), + space: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + use crate::random_sampling::invert_hsl_sample; + + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + let (r1_min, r2_min) = invert_hsl_sample(low); + let (r1_max, r2_max) = invert_hsl_sample(high); + + UniformHsl { + hue: crate::hues::UniformRgbHue::new_inclusive(low.hue, high.hue), + u1: Uniform::new_inclusive::<_, T>(r1_min, r1_max), + u2: Uniform::new_inclusive::<_, T>(r2_min, r2_max), + space: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Hsl { + crate::random_sampling::sample_hsl( + self.hue.sample(rng), + self.u1.sample(rng), + self.u2.sample(rng), + ) + } +} + #[cfg(test)] mod test { use super::Hsl; diff --git a/palette/src/hsv.rs b/palette/src/hsv.rs index 1e1df9275..476cd7589 100644 --- a/palette/src/hsv.rs +++ b/palette/src/hsv.rs @@ -3,16 +3,20 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Sub, SubAssign}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb}; use crate::float::Float; use crate::rgb::{Rgb, RgbSpace}; -use crate::{clamp, contrast_ratio, from_f64}; -use crate::{Alpha, Hsl, Hwb, Xyz}; use crate::{ - Component, FloatComponent, FromColor, FromF64, GetHue, Hue, IntoColor, Limited, Mix, Pixel, - RelativeContrast, RgbHue, Saturate, Shade, + clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromColor, FromF64, GetHue, + Hsl, Hue, Hwb, IntoColor, Limited, Mix, Pixel, RelativeContrast, RgbHue, Saturate, Shade, Xyz, }; /// Linear HSV with an alpha component. See the [`Hsva` implementation in @@ -672,6 +676,106 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + S: RgbSpace, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Hsv { + crate::random_sampling::sample_hsv(rng.gen::>(), rng.gen(), rng.gen()) + } +} + +#[cfg(feature = "random")] +pub struct UniformHsv +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + hue: crate::hues::UniformRgbHue, + u1: Uniform, + u2: Uniform, + space: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Hsv +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + type Sampler = UniformHsv; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformHsv +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + type X = Hsv; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + let (r1_min, r2_min) = ( + low.value * low.value * low.value, + low.saturation * low.saturation, + ); + let (r1_max, r2_max) = ( + high.value * high.value * high.value, + high.saturation * high.saturation, + ); + + UniformHsv { + hue: crate::hues::UniformRgbHue::new(low.hue, high.hue), + u1: Uniform::new::<_, T>(r1_min, r1_max), + u2: Uniform::new::<_, T>(r2_min, r2_max), + space: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + let (r1_min, r2_min) = ( + low.value * low.value * low.value, + low.saturation * low.saturation, + ); + let (r1_max, r2_max) = ( + high.value * high.value * high.value, + high.saturation * high.saturation, + ); + + UniformHsv { + hue: crate::hues::UniformRgbHue::new_inclusive(low.hue, high.hue), + u1: Uniform::new_inclusive::<_, T>(r1_min, r1_max), + u2: Uniform::new_inclusive::<_, T>(r2_min, r2_max), + space: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Hsv { + crate::random_sampling::sample_hsv( + self.hue.sample(rng), + self.u1.sample(rng), + self.u2.sample(rng), + ) + } +} + #[cfg(test)] mod test { use super::Hsv; diff --git a/palette/src/hues.rs b/palette/src/hues.rs index d566cfc58..dc2ba3cce 100644 --- a/palette/src/hues.rs +++ b/palette/src/hues.rs @@ -1,6 +1,13 @@ use core::cmp::PartialEq; use core::ops::{Add, AddAssign, Sub, SubAssign}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; + use crate::float::Float; use crate::{from_f64, FromF64}; @@ -239,6 +246,17 @@ macro_rules! make_hues { *self -= other.0; } } + + #[cfg(feature = "random")] + impl Distribution<$name> for Standard + where + T: Float + FromF64, + Standard: Distribution, + { + fn sample(&self, rng: &mut R) -> $name { + $name(rng.gen() * from_f64(360.0)) + } + } )+) } @@ -270,6 +288,126 @@ fn normalize_angle_positive(deg: T) -> T { deg - ((deg / c360).floor() * c360) } +#[cfg(feature = "random")] +pub struct UniformLabHue +where + T: Float + FromF64 + SampleUniform, +{ + hue: Uniform, +} + +#[cfg(feature = "random")] +impl SampleUniform for LabHue +where + T: Float + FromF64 + SampleUniform, +{ + type Sampler = UniformLabHue; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformLabHue +where + T: Float + FromF64 + SampleUniform, +{ + type X = LabHue; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLabHue { + hue: Uniform::new( + LabHue::to_positive_degrees(low), + LabHue::to_positive_degrees(high), + ), + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLabHue { + hue: Uniform::new_inclusive( + LabHue::to_positive_degrees(low), + LabHue::to_positive_degrees(high), + ), + } + } + + fn sample(&self, rng: &mut R) -> LabHue { + LabHue::from(self.hue.sample(rng) * from_f64(360.0)) + } +} + +#[cfg(feature = "random")] +pub struct UniformRgbHue +where + T: Float + FromF64 + SampleUniform, +{ + hue: Uniform, +} + +#[cfg(feature = "random")] +impl SampleUniform for RgbHue +where + T: Float + FromF64 + SampleUniform, +{ + type Sampler = UniformRgbHue; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformRgbHue +where + T: Float + FromF64 + SampleUniform, +{ + type X = RgbHue; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformRgbHue { + hue: Uniform::new( + RgbHue::to_positive_degrees(low), + RgbHue::to_positive_degrees(high), + ), + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformRgbHue { + hue: Uniform::new_inclusive( + RgbHue::to_positive_degrees(low), + RgbHue::to_positive_degrees(high), + ), + } + } + + fn sample(&self, rng: &mut R) -> RgbHue { + RgbHue::from(self.hue.sample(rng) * from_f64(360.0)) + } +} + #[cfg(test)] mod test { use super::{normalize_angle, normalize_angle_positive}; diff --git a/palette/src/hwb.rs b/palette/src/hwb.rs index 4610a8e98..7e6eace23 100644 --- a/palette/src/hwb.rs +++ b/palette/src/hwb.rs @@ -3,6 +3,12 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Sub, SubAssign}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; use crate::encoding::pixel::RawPixel; use crate::encoding::Srgb; @@ -618,6 +624,81 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + S: RgbSpace, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Hwb { + Hwb::from(rng.gen::>()) + } +} + +#[cfg(feature = "random")] +pub struct UniformHwb +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + sampler: crate::hsv::UniformHsv, + space: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Hwb +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + type Sampler = UniformHwb; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformHwb +where + T: FloatComponent + SampleUniform, + S: RgbSpace + SampleUniform, +{ + type X = Hwb; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + let sampler = crate::hsv::UniformHsv::::new(Hsv::from(low), Hsv::from(high)); + + UniformHwb { + sampler, + space: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + let sampler = + crate::hsv::UniformHsv::::new_inclusive(Hsv::from(low), Hsv::from(high)); + + UniformHwb { + sampler, + space: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Hwb { + Hwb::from(self.sampler.sample(rng)) + } +} + #[cfg(test)] mod test { use super::Hwb; diff --git a/palette/src/lab.rs b/palette/src/lab.rs index 4e69f9675..f53da8154 100644 --- a/palette/src/lab.rs +++ b/palette/src/lab.rs @@ -1,15 +1,20 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; + use crate::color_difference::ColorDifference; use crate::color_difference::{get_ciede_difference, LabColorDiff}; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; -use crate::{clamp, contrast_ratio, from_f64}; -use crate::{Alpha, LabHue, Lch, Xyz}; use crate::{ - Component, ComponentWise, FloatComponent, GetHue, IntoColor, Limited, Mix, Pixel, - RelativeContrast, Shade, + clamp, contrast_ratio, from_f64, Alpha, Component, ComponentWise, FloatComponent, GetHue, + IntoColor, LabHue, Lch, Limited, Mix, Pixel, RelativeContrast, Shade, Xyz, }; /// CIE L\*a\*b\* (CIELAB) with an alpha component. See the [`Laba` @@ -671,6 +676,95 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + Wp: WhitePoint, + Standard: Distribution, +{ + // `a` and `b` both range from (-128.0, 127.0) + fn sample(&self, rng: &mut R) -> Lab { + Lab { + l: rng.gen(), + a: rng.gen() * from_f64(255.0) - from_f64(128.0), + b: rng.gen() * from_f64(255.0) - from_f64(128.0), + white_point: PhantomData, + } + } +} + +#[cfg(feature = "random")] +pub struct UniformLab +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + l: Uniform, + a: Uniform, + b: Uniform, + white_point: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Lab +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type Sampler = UniformLab; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformLab +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type X = Lab; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLab { + l: Uniform::new::<_, T>(low.l, high.l), + a: Uniform::new::<_, T>(low.a, high.a), + b: Uniform::new::<_, T>(low.b, high.b), + white_point: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLab { + l: Uniform::new_inclusive::<_, T>(low.l, high.l), + a: Uniform::new_inclusive::<_, T>(low.a, high.a), + b: Uniform::new_inclusive::<_, T>(low.b, high.b), + white_point: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Lab { + Lab { + l: self.l.sample(rng), + a: self.a.sample(rng), + b: self.b.sample(rng), + white_point: PhantomData, + } + } +} + #[cfg(test)] mod test { use super::Lab; diff --git a/palette/src/lch.rs b/palette/src/lch.rs index afbf4726c..980934fef 100644 --- a/palette/src/lch.rs +++ b/palette/src/lch.rs @@ -1,15 +1,20 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Sub, SubAssign}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; + use crate::color_difference::ColorDifference; use crate::color_difference::{get_ciede_difference, LabColorDiff}; use crate::encoding::pixel::RawPixel; use crate::white_point::{WhitePoint, D65}; -use crate::{clamp, contrast_ratio, from_f64}; -use crate::{Alpha, Hue, Lab, LabHue, Xyz}; use crate::{ - Component, FloatComponent, FromColor, GetHue, IntoColor, Limited, Mix, Pixel, RelativeContrast, - Saturate, Shade, + clamp, contrast_ratio, from_f64, Alpha, Component, FloatComponent, FromColor, GetHue, Hue, + IntoColor, Lab, LabHue, Limited, Mix, Pixel, RelativeContrast, Saturate, Shade, Xyz, }; /// CIE L\*C\*h° with an alpha component. See the [`Lcha` implementation in @@ -562,6 +567,97 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + Wp: WhitePoint, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Lch { + Lch { + l: rng.gen(), + chroma: crate::Float::sqrt(rng.gen()) * from_f64(128.0), + hue: rng.gen::>(), + white_point: PhantomData, + } + } +} + +#[cfg(feature = "random")] +pub struct UniformLch +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + l: Uniform, + chroma: Uniform, + hue: crate::hues::UniformLabHue, + white_point: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Lch +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type Sampler = UniformLch; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformLch +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type X = Lch; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLch { + l: Uniform::new::<_, T>(low.l, high.l), + chroma: Uniform::new::<_, T>(low.chroma * low.chroma, high.chroma * high.chroma), + hue: crate::hues::UniformLabHue::new(low.hue, high.hue), + white_point: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLch { + l: Uniform::new_inclusive::<_, T>(low.l, high.l), + chroma: Uniform::new_inclusive::<_, T>( + low.chroma * low.chroma, + high.chroma * high.chroma, + ), + hue: crate::hues::UniformLabHue::new_inclusive(low.hue, high.hue), + white_point: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Lch { + Lch { + l: self.l.sample(rng), + chroma: crate::Float::sqrt(self.chroma.sample(rng)), + hue: self.hue.sample(rng), + white_point: PhantomData, + } + } +} + #[cfg(test)] mod test { use crate::white_point::D65; diff --git a/palette/src/lib.rs b/palette/src/lib.rs index 442f0ec9c..c2fe50bcd 100644 --- a/palette/src/lib.rs +++ b/palette/src/lib.rs @@ -372,6 +372,9 @@ pub mod gradient; #[cfg(feature = "named")] pub mod named; +#[cfg(feature = "random")] +mod random_sampling; + mod alpha; mod hsl; mod hsv; diff --git a/palette/src/luma/luma.rs b/palette/src/luma/luma.rs index 378326bc1..14c0cdb0d 100644 --- a/palette/src/luma/luma.rs +++ b/palette/src/luma/luma.rs @@ -3,6 +3,12 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; use crate::blend::PreAlpha; use crate::encoding::linear::LinearFn; @@ -10,11 +16,9 @@ use crate::encoding::pixel::RawPixel; use crate::encoding::{Linear, Srgb, TransferFn}; use crate::luma::LumaStandard; use crate::white_point::WhitePoint; -use crate::{clamp, contrast_ratio}; -use crate::{Alpha, Xyz, Yxy}; use crate::{ - Blend, Component, ComponentWise, FloatComponent, FromColor, FromComponent, IntoColor, Limited, - Mix, Pixel, RelativeContrast, Shade, + clamp, contrast_ratio, Alpha, Blend, Component, ComponentWise, FloatComponent, FromColor, + FromComponent, IntoColor, Limited, Mix, Pixel, RelativeContrast, Shade, Xyz, Yxy, }; /// Luminance with an alpha component. See the [`Lumaa` implementation @@ -750,6 +754,84 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: Component, + S: LumaStandard, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Luma { + Luma { + luma: rng.gen(), + standard: PhantomData, + } + } +} + +#[cfg(feature = "random")] +pub struct UniformLuma +where + T: Component + SampleUniform, + S: LumaStandard + SampleUniform, +{ + luma: Uniform, + standard: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Luma +where + T: Component + SampleUniform, + S: LumaStandard + SampleUniform, +{ + type Sampler = UniformLuma; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformLuma +where + T: Component + SampleUniform, + S: LumaStandard + SampleUniform, +{ + type X = Luma; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLuma { + luma: Uniform::new::<_, T>(low.luma, high.luma), + standard: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformLuma { + luma: Uniform::new_inclusive::<_, T>(low.luma, high.luma), + standard: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Luma { + Luma { + luma: self.luma.sample(rng), + standard: PhantomData, + } + } +} + #[cfg(test)] mod test { use crate::encoding::Srgb; diff --git a/palette/src/random_sampling/cone.rs b/palette/src/random_sampling/cone.rs new file mode 100644 index 000000000..35e365625 --- /dev/null +++ b/palette/src/random_sampling/cone.rs @@ -0,0 +1,133 @@ +use core::marker::PhantomData; + +use crate::float::Float; +use crate::hues::RgbHue; +use crate::rgb::RgbSpace; +use crate::{from_f64, FloatComponent, Hsl, Hsv}; + +// Based on https://stackoverflow.com/q/4778147 and https://math.stackexchange.com/q/18686, +// picking A = (0, 0), B = (0, 1), C = (1, 1) gives us: +// +// ( sqrt(r1) * r2 , sqrt(r1) * (1 - r2) + sqrt(r1) * r2 ) = +// ( sqrt(r1) * r2 , sqrt(r1) - sqrt(r1) * r2 + sqrt(r1) * r2 ) = +// ( sqrt(r1) * r2 , sqrt(r1) ) +// +// `sqrt(r1)` gives us the scale of the triangle, `r2` the radius. +// Substituting, we get `x = scale * radius, y = scale` and thus for cone +// sampling: `scale = powf(r1, 1.0/3.0)` and `radius = sqrt(r2)`. + +pub fn sample_hsv(hue: RgbHue, r1: T, r2: T) -> Hsv +where + T: FloatComponent, + S: RgbSpace, +{ + let (value, saturation) = (Float::cbrt(r1), Float::sqrt(r2)); + + Hsv { + hue, + saturation, + value, + space: PhantomData, + } +} + +pub fn sample_hsl(hue: RgbHue, r1: T, r2: T) -> Hsl +where + T: FloatComponent, + S: RgbSpace, +{ + let (saturation, lightness) = if r1 <= from_f64::(0.5) { + // Scale it up to [0, 1] + let r1 = r1 * from_f64::(2.0); + let h = Float::cbrt(r1); + let r = Float::sqrt(r2); + // Scale the lightness back to [0, 0.5] + (r, h * from_f64::(0.5)) + } else { + // Scale and shift it to [0, 1). + let r1 = (from_f64::(1.0) - r1) * from_f64::(2.0); + let h = Float::cbrt(r1); + let r = Float::sqrt(r2); + // Turn the cone upside-down and scale the lightness back to (0.5, 1.0] + (r, (from_f64::(2.0) - h) * from_f64::(0.5)) + }; + + Hsl { + hue, + saturation, + lightness, + space: PhantomData, + } +} + +pub fn invert_hsl_sample(color: Hsl) -> (T, T) +where + T: FloatComponent, + S: RgbSpace, +{ + let r1 = if color.lightness <= from_f64::(0.5) { + // ((x * 2)^3) / 2 = x^3 * 4. + // lightness is multiplied by 2 to scale it up to [0, 1], becoming h. + // h is cubed to make it r1. r1 is divided by 2 to take it back to [0, 0.5]. + color.lightness * color.lightness * color.lightness * from_f64::(4.0) + } else { + let x = color.lightness - from_f64::(1.0); + x * x * x * from_f64::(4.0) + from_f64::(1.0) + }; + + // saturation is first multiplied, then divided by h before squaring. + // h can be completely eliminated, leaving only the saturation. + let r2 = color.saturation * color.saturation; + + (r1, r2) +} + +#[cfg(test)] +mod test { + use super::{invert_hsl_sample, sample_hsl, sample_hsv}; + use crate::encoding::Srgb; + use crate::hues::RgbHue; + use crate::{Hsl, Hsv}; + + #[cfg(feature = "random")] + #[test] + fn sample_max_min() { + let a = sample_hsv(RgbHue::from(0.0), 0.0, 0.0); + let b = sample_hsv(RgbHue::from(360.0), 1.0, 1.0); + assert_relative_eq!(Hsv::new(0.0, 0.0, 0.0), a); + assert_relative_eq!(Hsv::new(360.0, 1.0, 1.0), b); + let a = sample_hsl(RgbHue::from(0.0), 0.0, 0.0); + let b = sample_hsl(RgbHue::from(360.0), 1.0, 1.0); + assert_relative_eq!(Hsl::new(0.0, 0.0, 0.0), a); + assert_relative_eq!(Hsl::new(360.0, 1.0, 1.0), b); + } + + #[cfg(feature = "random")] + #[test] + fn hsl_sampling() { + // Sanity check that sampling and inverting from sample are equivalent + macro_rules! test_hsl { + ( $x:expr, $y:expr ) => {{ + let a = invert_hsl_sample::(sample_hsl(RgbHue::from(0.0), $x, $y)); + assert_relative_eq!(a.0, $x); + assert_relative_eq!(a.1, $y); + }}; + } + + test_hsl!(0.8464721407, 0.8271899200); + test_hsl!(0.8797234442, 0.4924621591); + test_hsl!(0.9179406120, 0.8771350605); + test_hsl!(0.5458023108, 0.1154283005); + test_hsl!(0.2691241774, 0.7881780600); + test_hsl!(0.2085030453, 0.9975406626); + test_hsl!(0.8483632811, 0.4955013942); + test_hsl!(0.0857919040, 0.0652214785); + test_hsl!(0.7152662838, 0.2788421565); + test_hsl!(0.2973598808, 0.5585230243); + test_hsl!(0.0936619602, 0.7289450731); + test_hsl!(0.4364395449, 0.9362269009); + test_hsl!(0.9802381158, 0.9742974964); + test_hsl!(0.1666129293, 0.4396910574); + test_hsl!(0.6190216210, 0.7175675180); + } +} diff --git a/palette/src/random_sampling/mod.rs b/palette/src/random_sampling/mod.rs new file mode 100644 index 000000000..61f0e2b12 --- /dev/null +++ b/palette/src/random_sampling/mod.rs @@ -0,0 +1,3 @@ +mod cone; + +pub use self::cone::*; diff --git a/palette/src/rgb/rgb.rs b/palette/src/rgb/rgb.rs index 0b845f78f..d05a2e3f7 100644 --- a/palette/src/rgb/rgb.rs +++ b/palette/src/rgb/rgb.rs @@ -6,6 +6,12 @@ use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use core::str::FromStr; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; use crate::alpha::Alpha; use crate::blend::PreAlpha; @@ -17,10 +23,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::white_point::WhitePoint; -use crate::{clamp, contrast_ratio, from_f64}; use crate::{ - Blend, Component, ComponentWise, FloatComponent, FromComponent, GetHue, Limited, Mix, Pixel, - RelativeContrast, Shade, + clamp, contrast_ratio, from_f64, Blend, Component, ComponentWise, FloatComponent, + FromComponent, GetHue, Limited, Mix, Pixel, RelativeContrast, Shade, }; use crate::{Hsl, Hsv, Hwb, Lab, Lch, Luma, RgbHue, Xyz, Yxy}; @@ -1144,6 +1149,94 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: Component, + S: RgbStandard, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Rgb { + Rgb { + red: rng.gen(), + green: rng.gen(), + blue: rng.gen(), + standard: PhantomData, + } + } +} + +#[cfg(feature = "random")] +pub struct UniformRgb +where + T: Component + SampleUniform, + S: RgbStandard + SampleUniform, +{ + red: Uniform, + green: Uniform, + blue: Uniform, + standard: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Rgb +where + T: Component + SampleUniform, + S: RgbStandard + SampleUniform, +{ + type Sampler = UniformRgb; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformRgb +where + T: Component + SampleUniform, + S: RgbStandard + SampleUniform, +{ + type X = Rgb; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformRgb { + red: Uniform::new::<_, T>(low.red, high.red), + green: Uniform::new::<_, T>(low.green, high.green), + blue: Uniform::new::<_, T>(low.blue, high.blue), + standard: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformRgb { + red: Uniform::new_inclusive::<_, T>(low.red, high.red), + green: Uniform::new_inclusive::<_, T>(low.green, high.green), + blue: Uniform::new_inclusive::<_, T>(low.blue, high.blue), + standard: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Rgb { + Rgb { + red: self.red.sample(rng), + green: self.green.sample(rng), + blue: self.blue.sample(rng), + standard: PhantomData, + } + } +} + #[cfg(test)] mod test { use core::str::FromStr; diff --git a/palette/src/xyz.rs b/palette/src/xyz.rs index 1db89dd5b..88a00cde3 100644 --- a/palette/src/xyz.rs +++ b/palette/src/xyz.rs @@ -1,15 +1,21 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; + use crate::encoding::pixel::RawPixel; use crate::luma::LumaStandard; use crate::matrix::{multiply_rgb_to_xyz, rgb_to_xyz_matrix}; use crate::rgb::{Rgb, RgbSpace, RgbStandard}; use crate::white_point::{WhitePoint, D65}; -use crate::{clamp, contrast_ratio, from_f64}; -use crate::{Alpha, Lab, Luma, Yxy}; use crate::{ - Component, ComponentWise, FloatComponent, Limited, Mix, Pixel, RelativeContrast, Shade, + clamp, contrast_ratio, from_f64, Alpha, Component, ComponentWise, FloatComponent, Lab, Limited, + Luma, Mix, Pixel, RelativeContrast, Shade, Yxy, }; /// CIE 1931 XYZ with an alpha component. See the [`Xyza` implementation in @@ -647,6 +653,95 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + Wp: WhitePoint, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Xyz { + let xyz_ref: Xyz = Wp::get_xyz(); + Xyz { + x: rng.gen() * xyz_ref.x, + y: rng.gen() * xyz_ref.y, + z: rng.gen() * xyz_ref.z, + white_point: PhantomData, + } + } +} + +#[cfg(feature = "random")] +pub struct UniformXyz +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + x: Uniform, + y: Uniform, + z: Uniform, + white_point: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Xyz +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type Sampler = UniformXyz; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformXyz +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type X = Xyz; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformXyz { + x: Uniform::new::<_, T>(low.x, high.x), + y: Uniform::new::<_, T>(low.y, high.y), + z: Uniform::new::<_, T>(low.z, high.z), + white_point: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformXyz { + x: Uniform::new_inclusive::<_, T>(low.x, high.x), + y: Uniform::new_inclusive::<_, T>(low.y, high.y), + z: Uniform::new_inclusive::<_, T>(low.z, high.z), + white_point: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Xyz { + Xyz { + x: self.x.sample(rng), + y: self.y.sample(rng), + z: self.z.sample(rng), + white_point: PhantomData, + } + } +} + #[cfg(test)] mod test { use super::Xyz; diff --git a/palette/src/yxy.rs b/palette/src/yxy.rs index bebf5f776..5fb8a97c7 100644 --- a/palette/src/yxy.rs +++ b/palette/src/yxy.rs @@ -1,14 +1,19 @@ use core::marker::PhantomData; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +#[cfg(feature = "random")] +use rand::distributions::uniform::{SampleBorrow, SampleUniform, Uniform, UniformSampler}; +#[cfg(feature = "random")] +use rand::distributions::{Distribution, Standard}; +#[cfg(feature = "random")] +use rand::Rng; + use crate::encoding::pixel::RawPixel; use crate::luma::LumaStandard; use crate::white_point::{WhitePoint, D65}; -use crate::{clamp, contrast_ratio}; -use crate::{Alpha, Luma, Xyz}; use crate::{ - Component, ComponentWise, FloatComponent, IntoColor, Limited, Mix, Pixel, RelativeContrast, - Shade, + clamp, contrast_ratio, Alpha, Component, ComponentWise, FloatComponent, IntoColor, Limited, + Luma, Mix, Pixel, RelativeContrast, Shade, Xyz, }; /// CIE 1931 Yxy (xyY) with an alpha component. See the [`Yxya` implementation @@ -611,6 +616,94 @@ where } } +#[cfg(feature = "random")] +impl Distribution> for Standard +where + T: FloatComponent, + Wp: WhitePoint, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> Yxy { + Yxy { + x: rng.gen(), + y: rng.gen(), + luma: rng.gen(), + white_point: PhantomData, + } + } +} + +#[cfg(feature = "random")] +pub struct UniformYxy +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + x: Uniform, + y: Uniform, + luma: Uniform, + white_point: PhantomData, +} + +#[cfg(feature = "random")] +impl SampleUniform for Yxy +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type Sampler = UniformYxy; +} + +#[cfg(feature = "random")] +impl UniformSampler for UniformYxy +where + T: FloatComponent + SampleUniform, + Wp: WhitePoint + SampleUniform, +{ + type X = Yxy; + + fn new(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformYxy { + x: Uniform::new::<_, T>(low.x, high.x), + y: Uniform::new::<_, T>(low.y, high.y), + luma: Uniform::new::<_, T>(low.luma, high.luma), + white_point: PhantomData, + } + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + + UniformYxy { + x: Uniform::new_inclusive::<_, T>(low.x, high.x), + y: Uniform::new_inclusive::<_, T>(low.y, high.y), + luma: Uniform::new_inclusive::<_, T>(low.luma, high.luma), + white_point: PhantomData, + } + } + + fn sample(&self, rng: &mut R) -> Yxy { + Yxy { + x: self.x.sample(rng), + y: self.y.sample(rng), + luma: self.luma.sample(rng), + white_point: PhantomData, + } + } +} + #[cfg(test)] mod test { use super::Yxy;