Interpolating hues should use rem_euclid. (#12641)

Also, added additional tests for the hue interpolation.

Fixes #12632
Fixes #12631
This commit is contained in:
Talin 2024-03-22 12:53:10 -07:00 committed by GitHub
parent 6910ca3e8a
commit 7133d51331
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 31 additions and 28 deletions

View file

@ -97,10 +97,18 @@ pub trait ClampColor: Sized {
fn is_within_bounds(&self) -> bool; fn is_within_bounds(&self) -> bool;
} }
/// Utility function for interpolating hue values. This ensures that the interpolation
/// takes the shortest path around the color wheel, and that the result is always between
/// 0 and 360.
pub(crate) fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
let diff = (b - a + 180.0).rem_euclid(360.) - 180.;
(a + diff * t).rem_euclid(360.0)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::Hsla; use crate::{testing::assert_approx_eq, Hsla};
#[test] #[test]
fn test_rotate_hue() { fn test_rotate_hue() {
@ -113,4 +121,23 @@ mod tests {
assert_eq!(hsla.rotate_hue(360.0), hsla); assert_eq!(hsla.rotate_hue(360.0), hsla);
assert_eq!(hsla.rotate_hue(-360.0), hsla); assert_eq!(hsla.rotate_hue(-360.0), hsla);
} }
#[test]
fn test_hue_wrap() {
assert_approx_eq!(lerp_hue(10., 20., 0.25), 12.5, 0.001);
assert_approx_eq!(lerp_hue(10., 20., 0.5), 15., 0.001);
assert_approx_eq!(lerp_hue(10., 20., 0.75), 17.5, 0.001);
assert_approx_eq!(lerp_hue(20., 10., 0.25), 17.5, 0.001);
assert_approx_eq!(lerp_hue(20., 10., 0.5), 15., 0.001);
assert_approx_eq!(lerp_hue(20., 10., 0.75), 12.5, 0.001);
assert_approx_eq!(lerp_hue(10., 350., 0.25), 5., 0.001);
assert_approx_eq!(lerp_hue(10., 350., 0.5), 0., 0.001);
assert_approx_eq!(lerp_hue(10., 350., 0.75), 355., 0.001);
assert_approx_eq!(lerp_hue(350., 10., 0.25), 355., 0.001);
assert_approx_eq!(lerp_hue(350., 10., 0.5), 0., 0.001);
assert_approx_eq!(lerp_hue(350., 10., 0.75), 5., 0.001);
}
} }

View file

@ -105,16 +105,8 @@ impl Mix for Hsla {
#[inline] #[inline]
fn mix(&self, other: &Self, factor: f32) -> Self { fn mix(&self, other: &Self, factor: f32) -> Self {
let n_factor = 1.0 - factor; let n_factor = 1.0 - factor;
// TODO: Refactor this into EuclideanModulo::lerp_modulo
let shortest_angle = ((((other.hue - self.hue) % 360.) + 540.) % 360.) - 180.;
let mut hue = self.hue + shortest_angle * factor;
if hue < 0. {
hue += 360.;
} else if hue >= 360. {
hue -= 360.;
}
Self { Self {
hue, hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
saturation: self.saturation * n_factor + other.saturation * factor, saturation: self.saturation * n_factor + other.saturation * factor,
lightness: self.lightness * n_factor + other.lightness * factor, lightness: self.lightness * n_factor + other.lightness * factor,
alpha: self.alpha * n_factor + other.alpha * factor, alpha: self.alpha * n_factor + other.alpha * factor,

View file

@ -73,16 +73,8 @@ impl Mix for Hsva {
#[inline] #[inline]
fn mix(&self, other: &Self, factor: f32) -> Self { fn mix(&self, other: &Self, factor: f32) -> Self {
let n_factor = 1.0 - factor; let n_factor = 1.0 - factor;
// TODO: Refactor this into EuclideanModulo::lerp_modulo
let shortest_angle = ((((other.hue - self.hue) % 360.) + 540.) % 360.) - 180.;
let mut hue = self.hue + shortest_angle * factor;
if hue < 0. {
hue += 360.;
} else if hue >= 360. {
hue -= 360.;
}
Self { Self {
hue, hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
saturation: self.saturation * n_factor + other.saturation * factor, saturation: self.saturation * n_factor + other.saturation * factor,
value: self.value * n_factor + other.value * factor, value: self.value * n_factor + other.value * factor,
alpha: self.alpha * n_factor + other.alpha * factor, alpha: self.alpha * n_factor + other.alpha * factor,

View file

@ -77,16 +77,8 @@ impl Mix for Hwba {
#[inline] #[inline]
fn mix(&self, other: &Self, factor: f32) -> Self { fn mix(&self, other: &Self, factor: f32) -> Self {
let n_factor = 1.0 - factor; let n_factor = 1.0 - factor;
// TODO: Refactor this into EuclideanModulo::lerp_modulo
let shortest_angle = ((((other.hue - self.hue) % 360.) + 540.) % 360.) - 180.;
let mut hue = self.hue + shortest_angle * factor;
if hue < 0. {
hue += 360.;
} else if hue >= 360. {
hue -= 360.;
}
Self { Self {
hue, hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
whiteness: self.whiteness * n_factor + other.whiteness * factor, whiteness: self.whiteness * n_factor + other.whiteness * factor,
blackness: self.blackness * n_factor + other.blackness * factor, blackness: self.blackness * n_factor + other.blackness * factor,
alpha: self.alpha * n_factor + other.alpha * factor, alpha: self.alpha * n_factor + other.alpha * factor,