Skip to content

Commit 481d20d

Browse files
bors[bot]Ogeon
andauthored
Merge #196
196: Add uniformity tests for random colors and fix mistakes r=Ogeon a=Ogeon Adds tests that makes sure randomly generated colors are uniform within their "main" color space. I realized a Chi-squared goodness-of-fit test would do the trick, so I put this together over the past couple of days. Also found a few mistakes. I'm considering replacing the RNG with a fake RNG to avoid requiring as many samples, but that can wait. The only space I didn't find a good test method for is `Lch`, which covers a circle. My attempts showed a slight bias towards the center, but I can't explain it. I ended up not looking further into it for now, since the test method I used (sampling only a central square) rejects too many samples anyway. Co-authored-by: Erik Hedvall <erikwhedvall@gmail.com>
2 parents 92215de + 739bb76 commit 481d20d

File tree

14 files changed

+453
-54
lines changed

14 files changed

+453
-54
lines changed

palette/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ version = "0.8"
3838
optional = true
3939

4040
[dependencies.rand]
41-
version = "0.7"
41+
version = "0.8"
4242
default-features = false
4343
optional = true
4444

@@ -67,6 +67,11 @@ version = "0.23"
6767
default-features = false
6868
features = ["png"]
6969

70+
[dev-dependencies.rand_mt]
71+
version = "4"
72+
default-features = false
73+
features = ["rand-traits"]
74+
7075
[build-dependencies.phf_codegen]
7176
version = "0.8"
7277
optional = true

palette/src/alpha/alpha.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,4 +678,16 @@ mod test {
678678

679679
assert_eq!(deserialized, Rgba::<Srgb>::new(0.3, 0.8, 0.1, 0.5));
680680
}
681+
682+
#[cfg(feature = "random")]
683+
test_uniform_distribution! {
684+
Rgba<Srgb, f32> {
685+
red: (0.0, 1.0),
686+
green: (0.0, 1.0),
687+
blue: (0.0, 1.0),
688+
alpha: (0.0, 1.0)
689+
},
690+
min: Rgba::new(0.0f32, 0.0, 0.0, 0.0),
691+
max: Rgba::new(1.0, 1.0, 1.0, 1.0)
692+
}
681693
}

palette/src/hsl.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ where
682682
pub struct UniformHsl<S, T>
683683
where
684684
T: FloatComponent + SampleUniform,
685-
S: RgbStandard + SampleUniform,
685+
S: RgbStandard,
686686
{
687687
hue: crate::hues::UniformRgbHue<T>,
688688
u1: Uniform<T>,
@@ -694,7 +694,7 @@ where
694694
impl<S, T> SampleUniform for Hsl<S, T>
695695
where
696696
T: FloatComponent + SampleUniform,
697-
S: RgbStandard + SampleUniform,
697+
S: RgbStandard,
698698
{
699699
type Sampler = UniformHsl<S, T>;
700700
}
@@ -703,7 +703,7 @@ where
703703
impl<S, T> UniformSampler for UniformHsl<S, T>
704704
where
705705
T: FloatComponent + SampleUniform,
706-
S: RgbStandard + SampleUniform,
706+
S: RgbStandard,
707707
{
708708
type X = Hsl<S, T>;
709709

@@ -860,4 +860,49 @@ mod test {
860860

861861
assert_eq!(deserialized, Hsl::new(0.3, 0.8, 0.1));
862862
}
863+
864+
#[cfg(feature = "random")]
865+
test_uniform_distribution! {
866+
Hsl<crate::encoding::Srgb, f32> as crate::rgb::Rgb {
867+
red: (0.0, 1.0),
868+
green: (0.0, 1.0),
869+
blue: (0.0, 1.0)
870+
},
871+
min: Hsl::new(0.0f32, 0.0, 0.0),
872+
max: Hsl::new(360.0, 1.0, 1.0)
873+
}
874+
875+
/// Sanity check to make sure the test doesn't start accepting known
876+
/// non-uniform distributions.
877+
#[cfg(feature = "random")]
878+
#[test]
879+
#[should_panic(expected = "is not uniform enough")]
880+
fn uniform_distribution_fail() {
881+
use rand::Rng;
882+
883+
const BINS: usize = crate::random_sampling::test_utils::BINS;
884+
const SAMPLES: usize = crate::random_sampling::test_utils::SAMPLES;
885+
886+
let mut red = [0; BINS];
887+
let mut green = [0; BINS];
888+
let mut blue = [0; BINS];
889+
890+
let mut rng = rand_mt::Mt::new(1234); // We want the same seed on every run to avoid random fails
891+
892+
for _ in 0..SAMPLES {
893+
let color = Hsl::<crate::encoding::Srgb, f32>::new(
894+
rng.gen::<f32>() * 360.0,
895+
rng.gen(),
896+
rng.gen(),
897+
);
898+
let color: crate::rgb::Rgb = crate::IntoColor::into_color(color);
899+
red[((color.red * BINS as f32) as usize).min(9)] += 1;
900+
green[((color.green * BINS as f32) as usize).min(9)] += 1;
901+
blue[((color.blue * BINS as f32) as usize).min(9)] += 1;
902+
}
903+
904+
assert_uniform_distribution!(red);
905+
assert_uniform_distribution!(green);
906+
assert_uniform_distribution!(blue);
907+
}
863908
}

palette/src/hsv.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ where
689689
pub struct UniformHsv<S, T>
690690
where
691691
T: FloatComponent + SampleUniform,
692-
S: RgbStandard + SampleUniform,
692+
S: RgbStandard,
693693
{
694694
hue: crate::hues::UniformRgbHue<T>,
695695
u1: Uniform<T>,
@@ -701,7 +701,7 @@ where
701701
impl<S, T> SampleUniform for Hsv<S, T>
702702
where
703703
T: FloatComponent + SampleUniform,
704-
S: RgbStandard + SampleUniform,
704+
S: RgbStandard,
705705
{
706706
type Sampler = UniformHsv<S, T>;
707707
}
@@ -710,7 +710,7 @@ where
710710
impl<S, T> UniformSampler for UniformHsv<S, T>
711711
where
712712
T: FloatComponent + SampleUniform,
713-
S: RgbStandard + SampleUniform,
713+
S: RgbStandard,
714714
{
715715
type X = Hsv<S, T>;
716716

@@ -872,4 +872,15 @@ mod test {
872872

873873
assert_eq!(deserialized, Hsv::new(0.3, 0.8, 0.1));
874874
}
875+
876+
#[cfg(feature = "random")]
877+
test_uniform_distribution! {
878+
Hsv<crate::encoding::Srgb, f32> as crate::rgb::Rgb {
879+
red: (0.0, 1.0),
880+
green: (0.0, 1.0),
881+
blue: (0.0, 1.0)
882+
},
883+
min: Hsv::new(0.0f32, 0.0, 0.0),
884+
max: Hsv::new(360.0, 1.0, 1.0)
885+
}
875886
}

palette/src/hues.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,18 @@ where
317317
B2: SampleBorrow<Self::X> + Sized,
318318
{
319319
let low = *low_b.borrow();
320+
let normalized_low = LabHue::to_positive_degrees(low);
320321
let high = *high_b.borrow();
322+
let normalized_high = LabHue::to_positive_degrees(high);
323+
324+
let normalized_high = if normalized_low >= normalized_high && low.0 < high.0 {
325+
normalized_high + from_f64(360.0)
326+
} else {
327+
normalized_high
328+
};
321329

322330
UniformLabHue {
323-
hue: Uniform::new(
324-
LabHue::to_positive_degrees(low),
325-
LabHue::to_positive_degrees(high),
326-
),
331+
hue: Uniform::new(normalized_low, normalized_high),
327332
}
328333
}
329334

@@ -333,13 +338,18 @@ where
333338
B2: SampleBorrow<Self::X> + Sized,
334339
{
335340
let low = *low_b.borrow();
341+
let normalized_low = LabHue::to_positive_degrees(low);
336342
let high = *high_b.borrow();
343+
let normalized_high = LabHue::to_positive_degrees(high);
344+
345+
let normalized_high = if normalized_low >= normalized_high && low.0 < high.0 {
346+
normalized_high + from_f64(360.0)
347+
} else {
348+
normalized_high
349+
};
337350

338351
UniformLabHue {
339-
hue: Uniform::new_inclusive(
340-
LabHue::to_positive_degrees(low),
341-
LabHue::to_positive_degrees(high),
342-
),
352+
hue: Uniform::new_inclusive(normalized_low, normalized_high),
343353
}
344354
}
345355

@@ -377,13 +387,18 @@ where
377387
B2: SampleBorrow<Self::X> + Sized,
378388
{
379389
let low = *low_b.borrow();
390+
let normalized_low = RgbHue::to_positive_degrees(low);
380391
let high = *high_b.borrow();
392+
let normalized_high = RgbHue::to_positive_degrees(high);
393+
394+
let normalized_high = if normalized_low >= normalized_high && low.0 < high.0 {
395+
normalized_high + from_f64(360.0)
396+
} else {
397+
normalized_high
398+
};
381399

382400
UniformRgbHue {
383-
hue: Uniform::new(
384-
RgbHue::to_positive_degrees(low),
385-
RgbHue::to_positive_degrees(high),
386-
),
401+
hue: Uniform::new(normalized_low, normalized_high),
387402
}
388403
}
389404

@@ -393,13 +408,18 @@ where
393408
B2: SampleBorrow<Self::X> + Sized,
394409
{
395410
let low = *low_b.borrow();
411+
let normalized_low = RgbHue::to_positive_degrees(low);
396412
let high = *high_b.borrow();
413+
let normalized_high = RgbHue::to_positive_degrees(high);
414+
415+
let normalized_high = if normalized_low >= normalized_high && low.0 < high.0 {
416+
normalized_high + from_f64(360.0)
417+
} else {
418+
normalized_high
419+
};
397420

398421
UniformRgbHue {
399-
hue: Uniform::new_inclusive(
400-
RgbHue::to_positive_degrees(low),
401-
RgbHue::to_positive_degrees(high),
402-
),
422+
hue: Uniform::new_inclusive(normalized_low, normalized_high),
403423
}
404424
}
405425

palette/src/hwb.rs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ where
641641
pub struct UniformHwb<S, T>
642642
where
643643
T: FloatComponent + SampleUniform,
644-
S: RgbStandard + SampleUniform,
644+
S: RgbStandard,
645645
{
646646
sampler: crate::hsv::UniformHsv<S, T>,
647647
space: PhantomData<S>,
@@ -651,7 +651,7 @@ where
651651
impl<S, T> SampleUniform for Hwb<S, T>
652652
where
653653
T: FloatComponent + SampleUniform,
654-
S: RgbStandard + SampleUniform,
654+
S: RgbStandard,
655655
{
656656
type Sampler = UniformHwb<S, T>;
657657
}
@@ -660,7 +660,7 @@ where
660660
impl<S, T> UniformSampler for UniformHwb<S, T>
661661
where
662662
T: FloatComponent + SampleUniform,
663-
S: RgbStandard + SampleUniform,
663+
S: RgbStandard,
664664
{
665665
type X = Hwb<S, T>;
666666

@@ -669,12 +669,20 @@ where
669669
B1: SampleBorrow<Self::X> + Sized,
670670
B2: SampleBorrow<Self::X> + Sized,
671671
{
672-
let low = *low_b.borrow();
673-
let high = *high_b.borrow();
674-
let sampler = crate::hsv::UniformHsv::<S, _>::new(
675-
Hsv::from_color_unclamped(low),
676-
Hsv::from_color_unclamped(high),
672+
let low_input = Hsv::from_color_unclamped(*low_b.borrow());
673+
let high_input = Hsv::from_color_unclamped(*high_b.borrow());
674+
675+
let low = Hsv::with_wp(
676+
low_input.hue,
677+
low_input.saturation.min(high_input.saturation),
678+
low_input.value.min(high_input.value),
679+
);
680+
let high = Hsv::with_wp(
681+
high_input.hue,
682+
low_input.saturation.max(high_input.saturation),
683+
low_input.value.max(high_input.value),
677684
);
685+
let sampler = crate::hsv::UniformHsv::<S, _>::new(low, high);
678686

679687
UniformHwb {
680688
sampler,
@@ -687,13 +695,22 @@ where
687695
B1: SampleBorrow<Self::X> + Sized,
688696
B2: SampleBorrow<Self::X> + Sized,
689697
{
690-
let low = *low_b.borrow();
691-
let high = *high_b.borrow();
692-
let sampler = crate::hsv::UniformHsv::<S, _>::new_inclusive(
693-
Hsv::from_color_unclamped(low),
694-
Hsv::from_color_unclamped(high),
698+
let low_input = Hsv::from_color_unclamped(*low_b.borrow());
699+
let high_input = Hsv::from_color_unclamped(*high_b.borrow());
700+
701+
let low = Hsv::with_wp(
702+
low_input.hue,
703+
low_input.saturation.min(high_input.saturation),
704+
low_input.value.min(high_input.value),
705+
);
706+
let high = Hsv::with_wp(
707+
high_input.hue,
708+
low_input.saturation.max(high_input.saturation),
709+
low_input.value.max(high_input.value),
695710
);
696711

712+
let sampler = crate::hsv::UniformHsv::<S, _>::new_inclusive(low, high);
713+
697714
UniformHwb {
698715
sampler,
699716
space: PhantomData,
@@ -808,4 +825,15 @@ mod test {
808825

809826
assert_eq!(deserialized, Hwb::new(0.3, 0.8, 0.1));
810827
}
828+
829+
#[cfg(feature = "random")]
830+
test_uniform_distribution! {
831+
Hwb<crate::encoding::Srgb, f32> as crate::rgb::Rgb {
832+
red: (0.0, 1.0),
833+
green: (0.0, 1.0),
834+
blue: (0.0, 1.0)
835+
},
836+
min: Hwb::new(0.0f32, 0.0, 0.0),
837+
max: Hwb::new(360.0, 1.0, 1.0)
838+
}
811839
}

palette/src/lab.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ where
713713
pub struct UniformLab<Wp, T>
714714
where
715715
T: FloatComponent + SampleUniform,
716-
Wp: WhitePoint + SampleUniform,
716+
Wp: WhitePoint,
717717
{
718718
l: Uniform<T>,
719719
a: Uniform<T>,
@@ -725,7 +725,7 @@ where
725725
impl<Wp, T> SampleUniform for Lab<Wp, T>
726726
where
727727
T: FloatComponent + SampleUniform,
728-
Wp: WhitePoint + SampleUniform,
728+
Wp: WhitePoint,
729729
{
730730
type Sampler = UniformLab<Wp, T>;
731731
}
@@ -734,7 +734,7 @@ where
734734
impl<Wp, T> UniformSampler for UniformLab<Wp, T>
735735
where
736736
T: FloatComponent + SampleUniform,
737-
Wp: WhitePoint + SampleUniform,
737+
Wp: WhitePoint,
738738
{
739739
type X = Lab<Wp, T>;
740740

@@ -849,4 +849,15 @@ mod test {
849849

850850
assert_eq!(deserialized, Lab::new(0.3, 0.8, 0.1));
851851
}
852+
853+
#[cfg(feature = "random")]
854+
test_uniform_distribution! {
855+
Lab<D65, f32> {
856+
l: (0.0, 100.0),
857+
a: (-128.0, 127.0),
858+
b: (-128.0, 127.0)
859+
},
860+
min: Lab::new(0.0f32, -128.0, -128.0),
861+
max: Lab::new(100.0, 127.0, 127.0)
862+
}
852863
}

0 commit comments

Comments
 (0)