Skip to content

Commit 73620c2

Browse files
committed
Fixes HCL color interpolation
1 parent 6cb4011 commit 73620c2

5 files changed

Lines changed: 83 additions & 17 deletions

File tree

elm.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"elmcraft/core-extra": "1.0.0 <= v < 3.0.0",
3131
"folkertdev/one-true-path-experiment": "5.0.0 <= v < 7.0.0",
3232
"gampleman/elm-rosetree": "1.0.0 <= v < 2.0.0",
33-
"ianmackenzie/elm-geometry": "3.6.0 <= v < 4.0.0",
33+
"ianmackenzie/elm-geometry": "3.6.0 <= v < 5.0.0",
3434
"ianmackenzie/elm-units-prefixed": "2.0.0 <= v < 3.0.0",
3535
"justinmimbs/time-extra": "1.0.1 <= v < 2.0.0",
3636
"rtfeldman/elm-hex": "1.0.0 <= v < 2.0.0",

src/Color/Lab.elm

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,19 @@ toHcl color =
197197
toLab color
198198
in
199199
if a == 0 && b == 0 then
200+
-- A grey has an undefined hue (you cannot take the angle of a
201+
-- zero-length vector). Midtone greys have a well-defined chroma of 0;
202+
-- pure black and white have an undefined chroma as well. This matches
203+
-- d3-color's `hclConvert`. (A previous version of this code had the
204+
-- chroma condition inverted, which turned any gradient touching a grey
205+
-- entirely grey — see issue #151.)
200206
{ hue = nan
201207
, chroma =
202208
if 0 < l && l < 100 then
203-
nan
209+
0
204210

205211
else
206-
0
212+
nan
207213
, luminance = l
208214
, alpha = alpha
209215
}

src/Interpolation.elm

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ hcl =
328328
-}
329329
hclLong : Color -> Color -> Interpolator Color
330330
hclLong =
331-
hclImpl float
331+
hclImpl labChannel
332332

333333

334334
{-| We do not want negative values in an rgb color
@@ -354,25 +354,51 @@ hclImpl hueInt from to =
354354
in
355355
map4 (\h c l alpha -> Lab.fromHcl { hue = h, chroma = c, luminance = l, alpha = alpha })
356356
(hueInt start.hue end.hue)
357-
(float start.chroma end.chroma)
358-
(float start.luminance end.luminance)
359-
(float start.alpha end.alpha)
357+
(labChannel start.chroma end.chroma)
358+
(labChannel start.luminance end.luminance)
359+
(labChannel start.alpha end.alpha)
360360
>> forcePositive
361361

362362

363+
{-| Interpolates a single Lab/Hcl color channel. Mirrors d3-interpolate's
364+
color-channel interpolator (`nogamma`): if either endpoint is undefined (`NaN` —
365+
as happens for the hue of any grey, or the chroma of pure black/white), the
366+
channel holds the defined endpoint constant rather than letting the `NaN`
367+
propagate across the whole gradient. Without this, a single grey endpoint turns
368+
an entire HCL gradient grey (issue #151).
369+
-}
370+
labChannel : Float -> Float -> Interpolator Float
371+
labChannel from to =
372+
if isNaN from then
373+
always to
374+
375+
else if isNaN to then
376+
always from
377+
378+
else
379+
float from to
380+
381+
363382
hue : Float -> Float -> Interpolator Float
364383
hue from to =
365-
let
366-
d =
367-
to - from
368-
in
369-
float from
370-
(if d > 0.5 || d < -0.5 then
371-
from + (d - toFloat (round d))
384+
if isNaN from then
385+
always to
386+
387+
else if isNaN to then
388+
always from
389+
390+
else
391+
let
392+
d =
393+
to - from
394+
in
395+
float from
396+
(if d > 0.5 || d < -0.5 then
397+
from + (d - toFloat (round d))
372398

373-
else
374-
to
375-
)
399+
else
400+
to
401+
)
376402

377403

378404
hue360 : Float -> Float -> Interpolator Float

tests/Color/LabTests.elm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@ suite =
5151
Color.rgb255 170 187 204
5252
|> Lab.toHcl
5353
|> expectHclEqual { hue = 252.37145234745182, chroma = 11.223567114593477, luminance = 74.96879980931759, alpha = 1 }
54+
, test "a midtone grey has chroma 0 and an undefined (NaN) hue (#151)" <|
55+
\() ->
56+
Color.rgb255 145 145 145
57+
|> Lab.toHcl
58+
|> expectHclEqual { hue = nan, chroma = 0, luminance = 60.17214765137845, alpha = 1 }
59+
, test "pure black has an undefined chroma and hue" <|
60+
\() ->
61+
Color.rgb255 0 0 0
62+
|> Lab.toHcl
63+
|> expectHclEqual { hue = nan, chroma = nan, luminance = 0, alpha = 1 }
64+
, test "pure white has an undefined chroma and hue" <|
65+
\() ->
66+
Color.rgb255 255 255 255
67+
|> Lab.toHcl
68+
|> expectHclEqual { hue = nan, chroma = nan, luminance = 100, alpha = 1 }
5469
, test "hcl converts to proper RGB" <|
5570
\() ->
5671
Lab.fromHcl { hue = 120, chroma = 30, luminance = 50, alpha = 0.4 }
@@ -105,3 +120,8 @@ areHclCoordsEqual expected actual =
105120
areRgbCoordsEqual : Float -> Float -> Bool
106121
areRgbCoordsEqual expected actual =
107122
round (expected * 255) == round (actual * 255)
123+
124+
125+
nan : Float
126+
nan =
127+
0 / 0

tests/InterpolationTests.elm

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ suite =
116116
\() ->
117117
interpolateColorTest Interpolation.hcl
118118
{ red = 106, green = 121, blue = 206 }
119+
, test "a grey endpoint fades chroma while holding the other hue, instead of greying out the whole gradient (#151)" <|
120+
\() ->
121+
-- #00ff00 -> #919191. A grey has an undefined hue, so the
122+
-- green should smoothly desaturate to grey. Expected values
123+
-- are taken from d3-interpolate's interpolateHcl, which this
124+
-- module ports, and match it exactly.
125+
Interpolation.hcl (Color.rgb255 0 255 0) (Color.rgb255 145 145 145)
126+
|> equalsSamples equalsColor
127+
[ Color.rgb255 0 255 0
128+
, Color.rgb255 92 228 73
129+
, Color.rgb255 121 201 103
130+
, Color.rgb255 136 173 126
131+
, Color.rgb255 145 145 145
132+
]
119133
]
120134
, describe "hclLong" <|
121135
[ test "interpolates in hcl" <|

0 commit comments

Comments
 (0)