mirror of
https://github.com/danbulant/cushy
synced 2026-05-24 12:28:23 +00:00
contrast_between is now smarter
This commit is contained in:
parent
0610466964
commit
eca2b21e6a
2 changed files with 93 additions and 19 deletions
|
|
@ -800,6 +800,12 @@ impl ZeroToOne {
|
|||
Self(value.clamp(0., 1.))
|
||||
}
|
||||
|
||||
/// Returns the difference between `self` and `other` as a positive number.
|
||||
#[must_use]
|
||||
pub fn difference_between(self, other: Self) -> Self {
|
||||
Self((self.0 - other.0).abs())
|
||||
}
|
||||
|
||||
/// Returns the contained floating point value.
|
||||
#[must_use]
|
||||
pub fn into_f32(self) -> f32 {
|
||||
|
|
|
|||
106
src/styles.rs
106
src/styles.rs
|
|
@ -1150,6 +1150,32 @@ impl ColorSource {
|
|||
Okhsl::new(self.hue, *self.saturation, *lightness.into_lightness()).into_color();
|
||||
Color::new_f32(rgb.red, rgb.blue, rgb.green, 1.0)
|
||||
}
|
||||
|
||||
/// Calculates an approximate ratio between 0.0 and 1.0 of how contrasting
|
||||
/// these colors are, with perfect constrast being two clors that are
|
||||
/// opposite of each other on the hue circle and one fully desaturated and
|
||||
/// the other fully saturated.
|
||||
#[must_use]
|
||||
pub fn contrast_between(self, other: Self) -> ZeroToOne {
|
||||
let saturation_delta = self.saturation.difference_between(other.saturation);
|
||||
let self_hue = self.hue.into_positive_degrees();
|
||||
let other_hue = other.hue.into_positive_degrees();
|
||||
// Calculate the shortest distance between the hues, taking into account
|
||||
// that 0 and 359 are one degree apart.
|
||||
let hue_delta = ZeroToOne::new(
|
||||
if self_hue < other_hue {
|
||||
let hue_delta_a = other_hue - self_hue;
|
||||
let hue_delta_b = self_hue + 360. - other_hue;
|
||||
hue_delta_a.min(hue_delta_b)
|
||||
} else {
|
||||
let hue_delta_a = self_hue - other_hue;
|
||||
let hue_delta_b = other_hue + 360. - self_hue;
|
||||
hue_delta_a.min(hue_delta_b)
|
||||
} / 180.,
|
||||
);
|
||||
|
||||
saturation_delta * hue_delta
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can represent the lightness of a color.
|
||||
|
|
@ -1197,29 +1223,31 @@ pub trait ColorExt: Copy {
|
|||
self.into_source_and_lightness().1
|
||||
}
|
||||
|
||||
/// Returns the contrast between this color and the components provided.
|
||||
///
|
||||
/// To achieve a contrast of 1.0:
|
||||
///
|
||||
/// - `self`'s hue and `check_source.hue` must be 180 degrees apart.
|
||||
/// - `self`'s saturation and `check_source.saturation` must be different by
|
||||
/// 1.0.
|
||||
/// - `self`'s lightness and `check_lightness` must be different by 1.0.
|
||||
/// - `self`'s alpha and `check_alpha` must be different by 1.0.
|
||||
///
|
||||
/// The algorithm currently used is purposely left undocumented as it will
|
||||
/// likely change. It should be a reasonable heuristic until someone smarter
|
||||
/// than @ecton comes along.
|
||||
fn contrast_between(
|
||||
self,
|
||||
check_source: ColorSource,
|
||||
check_lightness: ZeroToOne,
|
||||
check_alpha: ZeroToOne,
|
||||
) -> ZeroToOne;
|
||||
|
||||
/// Returns the color in `others` that contrasts the most from `self`.
|
||||
#[must_use]
|
||||
fn most_contrasting(self, others: &[Self]) -> Self
|
||||
where
|
||||
Self: Copy,
|
||||
{
|
||||
// TODO this currently only checks lightness. We should probably factor
|
||||
// in hue/saturation changes too.
|
||||
let check = self.lightness();
|
||||
|
||||
let mut others = others.iter().copied();
|
||||
let mut most_contrasting = others.next().expect("at least one comparison");
|
||||
let mut most_contrast_amount = (*most_contrasting.lightness() - *check).abs();
|
||||
for other in others {
|
||||
let contrast_amount = (*other.lightness() - *check).abs();
|
||||
if contrast_amount > most_contrast_amount {
|
||||
most_contrasting = other;
|
||||
most_contrast_amount = contrast_amount;
|
||||
}
|
||||
}
|
||||
|
||||
most_contrasting
|
||||
}
|
||||
Self: Copy;
|
||||
}
|
||||
|
||||
impl ColorExt for Color {
|
||||
|
|
@ -1234,4 +1262,44 @@ impl ColorExt for Color {
|
|||
ZeroToOne::new(hsl.lightness * self.alpha_f32()),
|
||||
)
|
||||
}
|
||||
|
||||
fn contrast_between(
|
||||
self,
|
||||
check_source: ColorSource,
|
||||
check_lightness: ZeroToOne,
|
||||
check_alpha: ZeroToOne,
|
||||
) -> ZeroToOne {
|
||||
let (other_source, other_lightness) = self.into_source_and_lightness();
|
||||
let lightness_delta = other_lightness.difference_between(check_lightness);
|
||||
|
||||
let source_change = check_source.contrast_between(other_source);
|
||||
|
||||
let other_alpha = ZeroToOne::new(self.alpha_f32());
|
||||
let alpha_delta = check_alpha.difference_between(other_alpha);
|
||||
|
||||
lightness_delta * source_change * alpha_delta
|
||||
}
|
||||
|
||||
fn most_contrasting(self, others: &[Self]) -> Self
|
||||
where
|
||||
Self: Copy,
|
||||
{
|
||||
let (check_source, check_lightness) = self.into_source_and_lightness();
|
||||
let check_alpha = ZeroToOne::new(self.alpha_f32());
|
||||
|
||||
let mut others = others.iter().copied();
|
||||
let mut most_contrasting = others.next().expect("at least one comparison");
|
||||
let mut most_contrast_amount =
|
||||
most_contrasting.contrast_between(check_source, check_lightness, check_alpha);
|
||||
for other in others {
|
||||
let contrast_amount =
|
||||
other.contrast_between(check_source, check_lightness, check_alpha);
|
||||
if contrast_amount > most_contrast_amount {
|
||||
most_contrasting = other;
|
||||
most_contrast_amount = contrast_amount;
|
||||
}
|
||||
}
|
||||
|
||||
most_contrasting
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue