Skip to content

Commit

Permalink
Merge #175
Browse files Browse the repository at this point in the history
175: Add feature "random" for random color generation using `rand` crate r=Ogeon a=okaneco

Implement `Distribution<T> for Standard` from rand for all color types, LabHue, and RgbHue
Implement `SampleUniform ` for all color types, LabHue, and RgbHue
Color sampling is supported for `gen`, `gen_range`, and `Uniform`.

See #174 for design discussion
Color models are sampled based on their geometric space with special consideration for HSV (cone) and HSL (bicone).

Geometry of each color model:
`Hsv` and `Hwb` - Cone. `Hwb` is expressed in terms of `Hsv`.
`Hsl` - Bicone. Sampled as a random selection between two cones.
`Lab`, `Rgb`, `Xyz` and `Yxy` - Cubes and blocks. Each component can be sampled independently.
`Luma` - A line. A single value.
`Alpha` - Samples the contained color and the alpha channel independently.

Closes #174 

Co-authored-by: okaneco <47607823+okaneco@users.noreply.github.com>
  • Loading branch information
bors[bot] and okaneco committed Apr 4, 2020
2 parents acecaee + 27178c6 commit 023b002
Show file tree
Hide file tree
Showing 15 changed files with 1,228 additions and 26 deletions.
6 changes: 6 additions & 0 deletions palette/Cargo.toml
Expand Up @@ -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
Expand All @@ -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"]
Expand Down
83 changes: 83 additions & 0 deletions palette/src/alpha.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -435,6 +441,83 @@ where
}
}

#[cfg(feature = "random")]
impl<C, T> Distribution<Alpha<C, T>> for Standard
where
T: Component,
Standard: Distribution<C> + Distribution<T>,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Alpha<C, T> {
Alpha {
color: rng.gen(),
alpha: rng.gen(),
}
}
}

#[cfg(feature = "random")]
pub struct UniformAlpha<C, T>
where
T: Component + SampleUniform,
C: SampleUniform,
{
color: Uniform<C>,
alpha: Uniform<T>,
}

#[cfg(feature = "random")]
impl<C, T> SampleUniform for Alpha<C, T>
where
T: Component + SampleUniform,
C: Copy + SampleUniform,
{
type Sampler = UniformAlpha<C, T>;
}

#[cfg(feature = "random")]
impl<C, T> UniformSampler for UniformAlpha<C, T>
where
T: Component + SampleUniform,
C: Copy + SampleUniform,
{
type X = Alpha<C, T>;

fn new<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let low = *low_b.borrow();
let high = *high_b.borrow();

UniformAlpha {
color: Uniform::new::<C, _>(low.color, high.color),
alpha: Uniform::new::<_, T>(low.alpha, high.alpha),
}
}

fn new_inclusive<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let low = *low_b.borrow();
let high = *high_b.borrow();

UniformAlpha {
color: Uniform::new_inclusive::<C, _>(low.color, high.color),
alpha: Uniform::new_inclusive::<_, T>(low.alpha, high.alpha),
}
}

fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Alpha<C, T> {
Alpha {
color: self.color.sample(rng),
alpha: self.alpha.sample(rng),
}
}
}

#[cfg(test)]
mod test {
use crate::encoding::Srgb;
Expand Down
98 changes: 98 additions & 0 deletions palette/src/hsl.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -657,6 +663,98 @@ where
}
}

#[cfg(feature = "random")]
impl<S, T> Distribution<Hsl<S, T>> for Standard
where
T: FloatComponent,
S: RgbSpace,
Standard: Distribution<T>,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Hsl<S, T> {
crate::random_sampling::sample_hsl(rng.gen::<RgbHue<T>>(), rng.gen(), rng.gen())
}
}

#[cfg(feature = "random")]
pub struct UniformHsl<S, T>
where
T: FloatComponent + SampleUniform,
S: RgbSpace + SampleUniform,
{
hue: crate::hues::UniformRgbHue<T>,
u1: Uniform<T>,
u2: Uniform<T>,
space: PhantomData<S>,
}

#[cfg(feature = "random")]
impl<S, T> SampleUniform for Hsl<S, T>
where
T: FloatComponent + SampleUniform,
S: RgbSpace + SampleUniform,
{
type Sampler = UniformHsl<S, T>;
}

#[cfg(feature = "random")]
impl<S, T> UniformSampler for UniformHsl<S, T>
where
T: FloatComponent + SampleUniform,
S: RgbSpace + SampleUniform,
{
type X = Hsl<S, T>;

fn new<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + 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<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + 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<R: Rng + ?Sized>(&self, rng: &mut R) -> Hsl<S, T> {
crate::random_sampling::sample_hsl(
self.hue.sample(rng),
self.u1.sample(rng),
self.u2.sample(rng),
)
}
}

#[cfg(test)]
mod test {
use super::Hsl;
Expand Down
112 changes: 108 additions & 4 deletions palette/src/hsv.rs
Expand Up @@ -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
Expand Down Expand Up @@ -672,6 +676,106 @@ where
}
}

#[cfg(feature = "random")]
impl<S, T> Distribution<Hsv<S, T>> for Standard
where
T: FloatComponent,
S: RgbSpace,
Standard: Distribution<T>,
{
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Hsv<S, T> {
crate::random_sampling::sample_hsv(rng.gen::<RgbHue<T>>(), rng.gen(), rng.gen())
}
}

#[cfg(feature = "random")]
pub struct UniformHsv<S, T>
where
T: FloatComponent + SampleUniform,
S: RgbSpace + SampleUniform,
{
hue: crate::hues::UniformRgbHue<T>,
u1: Uniform<T>,
u2: Uniform<T>,
space: PhantomData<S>,
}

#[cfg(feature = "random")]
impl<S, T> SampleUniform for Hsv<S, T>
where
T: FloatComponent + SampleUniform,
S: RgbSpace + SampleUniform,
{
type Sampler = UniformHsv<S, T>;
}

#[cfg(feature = "random")]
impl<S, T> UniformSampler for UniformHsv<S, T>
where
T: FloatComponent + SampleUniform,
S: RgbSpace + SampleUniform,
{
type X = Hsv<S, T>;

fn new<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + 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<B1, B2>(low_b: B1, high_b: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + 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<R: Rng + ?Sized>(&self, rng: &mut R) -> Hsv<S, T> {
crate::random_sampling::sample_hsv(
self.hue.sample(rng),
self.u1.sample(rng),
self.u2.sample(rng),
)
}
}

#[cfg(test)]
mod test {
use super::Hsv;
Expand Down

0 comments on commit 023b002

Please sign in to comment.