Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ tests/VerifyExamples
.DS_Store
node_modules/
build/
.puppeteer-cache/
*.swp
.idea
2 changes: 1 addition & 1 deletion elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"elmcraft/core-extra": "1.0.0 <= v < 3.0.0",
"folkertdev/one-true-path-experiment": "5.0.0 <= v < 7.0.0",
"gampleman/elm-rosetree": "1.0.0 <= v < 2.0.0",
"ianmackenzie/elm-geometry": "3.6.0 <= v < 4.0.0",
"ianmackenzie/elm-geometry": "3.6.0 <= v < 5.0.0",
"ianmackenzie/elm-units-prefixed": "2.0.0 <= v < 3.0.0",
"justinmimbs/time-extra": "1.0.1 <= v < 2.0.0",
"rtfeldman/elm-hex": "1.0.0 <= v < 2.0.0",
Expand Down
19 changes: 15 additions & 4 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,28 @@
# directory between builds with netlify-plugin-cache.

[build]
command = "npm run build-docs"
# The publisher screenshots examples with puppeteer, which needs a Chrome
# binary. Puppeteer normally downloads it in a postinstall hook, but Netlify
# caches node_modules: when the lockfile is unchanged npm reports "up to date"
# and skips postinstall, so Chrome never lands. We install it explicitly here
# (a no-op once it's in the cached PUPPETEER_CACHE_DIR below) before building.
# The publisher launches with headless: "shell", so chrome-headless-shell is
# the binary it actually needs.
command = "npx puppeteer browsers install chrome-headless-shell && npm run build-docs"
publish = "build"

[build.environment]
# The publisher requires Node 22+ (it also reads .nvmrc).
NODE_VERSION = "22.20.0"
# Put puppeteer's browser download inside the repo so netlify-plugin-cache can
# persist it (the default ~/.cache/puppeteer is outside the cached paths).
PUPPETEER_CACHE_DIR = "/opt/build/repo/.puppeteer-cache"

# Restore build/ before the build and save it afterwards, so the incremental
# cache and prior outputs survive across deploys.
# Restore build/ and the puppeteer browser cache before the build and save them
# afterwards, so the incremental cache, prior outputs, and Chrome all survive
# across deploys.
[[plugins]]
package = "netlify-plugin-cache"

[plugins.inputs]
paths = ["build"]
paths = ["build", ".puppeteer-cache"]
10 changes: 8 additions & 2 deletions src/Color/Lab.elm
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,19 @@ toHcl color =
toLab color
in
if a == 0 && b == 0 then
-- A grey has an undefined hue (you cannot take the angle of a
-- zero-length vector). Midtone greys have a well-defined chroma of 0;
-- pure black and white have an undefined chroma as well. This matches
-- d3-color's `hclConvert`. (A previous version of this code had the
-- chroma condition inverted, which turned any gradient touching a grey
-- entirely grey — see issue #151.)
{ hue = nan
, chroma =
if 0 < l && l < 100 then
nan
0

else
0
nan
, luminance = l
, alpha = alpha
}
Expand Down
54 changes: 40 additions & 14 deletions src/Interpolation.elm
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ hcl =
-}
hclLong : Color -> Color -> Interpolator Color
hclLong =
hclImpl float
hclImpl labChannel


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


{-| Interpolates a single Lab/Hcl color channel. Mirrors d3-interpolate's
color-channel interpolator (`nogamma`): if either endpoint is undefined (`NaN` —
as happens for the hue of any grey, or the chroma of pure black/white), the
channel holds the defined endpoint constant rather than letting the `NaN`
propagate across the whole gradient. Without this, a single grey endpoint turns
an entire HCL gradient grey (issue #151).
-}
labChannel : Float -> Float -> Interpolator Float
labChannel from to =
if isNaN from then
always to

else if isNaN to then
always from

else
float from to


hue : Float -> Float -> Interpolator Float
hue from to =
let
d =
to - from
in
float from
(if d > 0.5 || d < -0.5 then
from + (d - toFloat (round d))
if isNaN from then
always to

else
to
)
else if isNaN to then
always from

else
let
d =
to - from
in
float from
(if d > 0.5 || d < -0.5 then
from + (d - toFloat (round d))

else
to
)


hue360 : Float -> Float -> Interpolator Float
Expand Down
20 changes: 20 additions & 0 deletions tests/Color/LabTests.elm
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ suite =
Color.rgb255 170 187 204
|> Lab.toHcl
|> expectHclEqual { hue = 252.37145234745182, chroma = 11.223567114593477, luminance = 74.96879980931759, alpha = 1 }
, test "a midtone grey has chroma 0 and an undefined (NaN) hue (#151)" <|
\() ->
Color.rgb255 145 145 145
|> Lab.toHcl
|> expectHclEqual { hue = nan, chroma = 0, luminance = 60.17214765137845, alpha = 1 }
, test "pure black has an undefined chroma and hue" <|
\() ->
Color.rgb255 0 0 0
|> Lab.toHcl
|> expectHclEqual { hue = nan, chroma = nan, luminance = 0, alpha = 1 }
, test "pure white has an undefined chroma and hue" <|
\() ->
Color.rgb255 255 255 255
|> Lab.toHcl
|> expectHclEqual { hue = nan, chroma = nan, luminance = 100, alpha = 1 }
, test "hcl converts to proper RGB" <|
\() ->
Lab.fromHcl { hue = 120, chroma = 30, luminance = 50, alpha = 0.4 }
Expand Down Expand Up @@ -105,3 +120,8 @@ areHclCoordsEqual expected actual =
areRgbCoordsEqual : Float -> Float -> Bool
areRgbCoordsEqual expected actual =
round (expected * 255) == round (actual * 255)


nan : Float
nan =
0 / 0
14 changes: 14 additions & 0 deletions tests/InterpolationTests.elm
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ suite =
\() ->
interpolateColorTest Interpolation.hcl
{ red = 106, green = 121, blue = 206 }
, test "a grey endpoint fades chroma while holding the other hue, instead of greying out the whole gradient (#151)" <|
\() ->
-- #00ff00 -> #919191. A grey has an undefined hue, so the
-- green should smoothly desaturate to grey. Expected values
-- are taken from d3-interpolate's interpolateHcl, which this
-- module ports, and match it exactly.
Interpolation.hcl (Color.rgb255 0 255 0) (Color.rgb255 145 145 145)
|> equalsSamples equalsColor
[ Color.rgb255 0 255 0
, Color.rgb255 92 228 73
, Color.rgb255 121 201 103
, Color.rgb255 136 173 126
, Color.rgb255 145 145 145
]
]
, describe "hclLong" <|
[ test "interpolates in hcl" <|
Expand Down
Loading