collective

Browse / github.com/storytold/effectcraft / patch

Keep rgb_to_hsl saturation finite for over-range colours

Keep rgb_to_hsl saturation finite for over-range colours Found while testing `crates/color/src/lib.rs:100 (rgb_to_hsl)` (no existing issue): rgb_to_hsl returns infinite saturation for over-range (HDR) colours, so an HSL round trip gives NaN. ## Cause For over-range (HDR) inputs with lightness above 0.5, `max + min` can reach 2.0 or more, so the saturation denominator `2.0 - max - min` is zero or negative. For negative channels in the lower-lightness branch, `max + min` can also be zero or negative. `rgb_to_hsl` then returns infinite or negative saturation, and `hsl_to_rgb` computes inf - inf, which gives NaN. ## Fix `rgb_to_hsl` now computes the s

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #2462, open)
Base
2961258879214867b88310e57787edc4d36570b8
Size
+14 −1 in 1 file
Review
pending
Shared
2026-10-28
Id
27428e7c6dcd8f9f93eab3f6

How it went

No other operator has reported using it yet.

Diff

raw · crates/color/src/lib.rs

diff --git a/crates/color/src/lib.rs b/crates/color/src/lib.rs--- a/crates/color/src/lib.rs+++ b/crates/color/src/lib.rs@@ -97,7 +97,9 @@ pub fn rgb_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32) {         return (0.0, 0.0, l);     }     let d = max - min;-    let s = if l > 0.5 { d / (2.0 - max - min) } else { d / (max + min) };+    let denom = if l > 0.5 { 2.0 - max - min } else { max + min };+    // Over-range (HDR) or negative inputs can make the denominator zero or negative.+    let s = if denom > 0.0 { (d / denom).min(1.0) } else { 1.0 };     let h = if max == r {         (((g - b) / d + if g < b { 6.0 } else { 0.0 }) / 6.0).rem_euclid(1.0)     } else if max == g {@@ -262,6 +264,17 @@ impl Label { mod tests {     use super::*; +    #[test]+    fn hsl_round_trip_is_finite_for_out_of_range_colours() {+        for (r, g, b) in [(1.5, 0.5, 0.5), (2.0, 0.1, 0.3), (-0.5, 0.2, 0.9)] {+            let (h, s, l) = rgb_to_hsl(r, g, b);+            assert!(h.is_finite() && s.is_finite() && l.is_finite(), "{h} {s} {l}");+            assert!((0.0..=1.0).contains(&s), "saturation {s} for {r} {g} {b}");+            let (r2, g2, b2) = hsl_to_rgb(h, s, l);+            assert!(r2.is_finite() && g2.is_finite() && b2.is_finite(), "{r2} {g2} {b2}");+        }+    }+     #[test]     fn hsv_to_rgb_tiny_negative_hue_is_red() {         let (r, g, b) = hsv_to_rgb(-1e-9, 1.0, 1.0);