diff --git a/src/doc/docIntro/doc/ChangeLog.fandoc b/src/doc/docIntro/doc/ChangeLog.fandoc index 8bfa004c6..99aa26419 100644 --- a/src/doc/docIntro/doc/ChangeLog.fandoc +++ b/src/doc/docIntro/doc/ChangeLog.fandoc @@ -19,6 +19,7 @@ - Fixes for Crypto EC keypair support - Fix Time.minus 24hr boundary checks - Allow mixins to have non-abstract fields if no storage required +- Fix Color HSL implementation to match browser behavoir *Build 1.0.79 (17 Jul 2023)* - New yaml API diff --git a/src/graphics/fan/Color.fan b/src/graphics/fan/Color.fan index 5eb0c2688..4bd6ca359 100644 --- a/src/graphics/fan/Color.fan +++ b/src/graphics/fan/Color.fan @@ -55,29 +55,21 @@ const final class Color : Paint ** Also see `h`, `s`, `l`. static Color makeHsl(Float h, Float s, Float l, Float a := 1.0f) { - r := l; g := l; b := l - if (s != 0f) - { - if (h == 360f) h = 0f - h /= 60f - i := h.floor - f := h - i - p := l * (1f - s) - q := l * (1f - s * f) - t := l * (1f - (s*(1f-f))) - switch (i.toInt) - { - case 0: r=l; g=t; b=p - case 1: r=q; g=l; b=p - case 2: r=p; g=l; b=t - case 3: r=p; g=q; b=l - case 4: r=t; g=p; b=l - case 5: r=l; g=p; b=q - } - } - return make((r * 255f).toInt.shiftl(16) - .or((g * 255f).toInt.shiftl(8)) - .or((b * 255f).toInt), a) + c := (1f - (2f * l - 1f).abs) * s + x := c * (1f - ((h / 60f) % 2f - 1f).abs) + m := l - c / 2f + r := 0f + g := 0f + b := 0f + if (h < 60f) { r=c; g=x; b=0f } + else if (h >= 60f && h < 120f) { r=x; g=c; b=0f } + else if (h >= 120f && h < 180f) { r=0f; g=c; b=x } + else if (h >= 180f && h < 240f) { r=0f; g=x; b=c } + else if (h >= 240f && h < 300f) { r=x; g=0f; b=c } + else if (h >= 300f && h < 360f) { r=c; g=0f; b=x } + return make(((r+m) * 255f).round.toInt.shiftl(16) + .or(((g+m) * 255f).round.toInt.shiftl(8)) + .or(((b+m) * 255f).round.toInt), a) } ////////////////////////////////////////////////////////////////////////// @@ -271,14 +263,18 @@ const final class Color : Paint { min := r.min(b.min(g)).toFloat max := r.max(b.max(g)).toFloat - return max == 0f ? 0f : (max-min) / max + c := max - min + if (c == 0f) return 0f + return c / (1f - (2f * l - 1f).abs) / 255f } ** Lightness (brightness) as a float between 0.0 and 1.0 of the HSL ** model (hue, saturation, lightness). Also see `makeHsl`, `h`, `s`. Float l() { - r.max(b.max(g)).toFloat / 255f + max := r.max(b.max(g)).toFloat + min := r.min(b.min(g)).toFloat + return (max + min) * 0.5f / 255f } ////////////////////////////////////////////////////////////////////////// diff --git a/src/graphics/test/ColorTest.fan b/src/graphics/test/ColorTest.fan index c524a07c7..ed786b3c2 100644 --- a/src/graphics/test/ColorTest.fan +++ b/src/graphics/test/ColorTest.fan @@ -150,25 +150,38 @@ class ColorTest : Test Void testHsl() { - verifyHsl(0x000000, 0f, 0f, 0f) - verifyHsl(0xffffff, 0f, 0f, 1f) - verifyHsl(0xff0000, 0f, 1f, 1f) - verifyHsl(0x00ff00, 120f, 1f, 1f) - verifyHsl(0x0000ff, 240f, 1f, 1f) - verifyHsl(0xffff00, 60f, 1f, 1f) - verifyHsl(0x00ffff, 180f, 1f, 1f) - verifyHsl(0xff00ff, 300f, 1f, 1f) - verifyHsl(0x6496c8, 210f, 0.5f, 0.78f) - verifyHsl(0x32c850, 132f, 0.75f, 0.78f) - verifyHsl(Color("hsl(240 0.4 1)"), 240f, 0.4f, 1f) + verifyHsl(0x000000, 0f, 0f, 0f) // black + verifyHsl(0xffffff, 0f, 0f, 1f) // white + verifyHsl(0xff0000, 0f, 1f, 0.5f) // red + verifyHsl(0x00ff00, 120f, 1f, 0.5f) // lime + verifyHsl(0x0000ff, 240f, 1f, 0.5f) // blue + verifyHsl(0xffff00, 60f, 1f, 0.5f) // yellow + verifyHsl(0x00ffff, 180f, 1f, 0.5f) // cyan + verifyHsl(0xff00ff, 300f, 1f, 0.5f) // magenta + verifyHsl(0xbfbfbf, 0f, 0f, 0.749f) // silver (191,191,191) + verifyHsl(0x808080, 0f, 0f, 0.501f) // gray (128,128,128) + verifyHsl(0x800000, 0f, 1f, 0.25f) // maroon (128,0,0) + verifyHsl(0x808000, 60f, 1f, 0.25f) // olive (128,128,0) + verifyHsl(0x008000, 120f, 1f, 0.25f) // green (0,128,0) + verifyHsl(0x800080, 300f, 1f, 0.25f) // purple (128,0,128) + verifyHsl(0x008080, 180f, 1f, 0.25f) // teal (0,128,128) + verifyHsl(0x000080, 240f, 1f, 0.25f) // navy (0,0,128) + + verifyHsl(0x32c850, 132f, 0.60f, 0.490f) // (50,200,80) + verifyHsl(0x6496c8, 210f, 0.476f, 0.588f) // (100,150,200) + verifyHsl(0x7e22ce, 272.093f, 0.716f, 0.470f) // (126, 34, 206) + verifyHsl(0xfcd34d, 45.942f, 0.967f, 0.645f) // (252, 211, 77) + + // l=100% is always #fff and so we lose hue/sat on roundtrip to rgb + verifyHsl(Color("hsl(240 0.4 1)"), 0f, 0f, 1f) } Void verifyHsl(Obj obj, Float h, Float s, Float l) { c := obj as Color ?: Color.make(obj) - verify(c.h.approx(h, 0.1f)) - verify(c.s.approx(s, 0.01f)) - verify(c.l.approx(l, 0.01f)) + verify(c.h.approx(h, 0.001f)) + verify(c.s.approx(s, 0.001f)) + verify(c.l.approx(l, 0.001f)) verifyEq(c, Color.makeHsl(c.h, c.s, c.l)) }