Skip to content

Commit

Permalink
graphics: Fix Color HSL implementation to match browser behavoir
Browse files Browse the repository at this point in the history
  • Loading branch information
afrankvt committed Mar 19, 2024
1 parent e9c6c56 commit 369018a
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 39 deletions.
1 change: 1 addition & 0 deletions src/doc/docIntro/doc/ChangeLog.fandoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 21 additions & 25 deletions src/graphics/fan/Color.fan
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

//////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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
}

//////////////////////////////////////////////////////////////////////////
Expand Down
41 changes: 27 additions & 14 deletions src/graphics/test/ColorTest.fan
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down

0 comments on commit 369018a

Please sign in to comment.