Open cdoublev opened 2 months ago
I agree that coercing values to infinity is going to cause no end of problems with color math; most color spaces are not designed to cope with such extreme values.
@tabatkins why are we doing this, again?
Clarification on how to handle infinite values has already been asked in #8629, which was resolved with no further change because if you put an infinite calculation into an
rgba()
, the behavior is well-defined: clamp to the allowed range.
For color spaces are clamped, yes. Most are not, including color(srgb ...)
and oklab()
and so on.
I just re-opened #8629 because the WG discussion apparently consisted of @tabatkins asserting that it was fine (for rgba()
) and thus fine everywhere else.
Not sure if this is applicable to browser implementations but I found that clamping to a really large value gives good results : https://github.com/csstools/postcss-plugins/blob/20ccaebde3de250e858adbbd3d519b01b7cf5304/packages/css-color-parser/src/functions/hsl-normalize-channel-values.ts#L111
Having a really large but finite value makes the color math work as expected.
Unless otherwise specified, color channel values are not clamped, which applies to values produced by math functions, which can be infinite.
Infinite color channel values produce
NaN
in most conversion functions.Specifically...
- `color(srgb-linear) -> color(xyz-d65)` - when one or more channels are infinite and one or more are the opposite infinite - `color(xyz-d50) -> color(xyz-d65)` - `color(xyz-d65) -> color(srgb-linear)` - when `x` and `y` are the same infinite - when `x`/`y` and `z` are infinite - `color(xyz-d50) -> lab()` - when `x` and `y` are the same infinite - when `y` and `z` are the same infinite - `color(xyz-d65) -> color(xyz-d50)` - when `x` and `y` are infinite - when `y` and `z` are infinite - when `x` and `z` are the same infinite - `color(xyz-d65) -> oklab()` - `oklab() -> color(xyz-d65)` - `rgb() -> hsl()` - when one or more channel is infinite - `hsl() -> rgb()` - when `s` is infinite and `l` is 0/100 - when `l` is infinite - `hwb() -> rgb()` - when `w` is infinite - when `b` is -Infinity - `lab() -> color(xyz-d50)` - when `l` is infinite and `a` is the opposite infinite - when `l` and `b` are the same infinite - `lch() -> lab()` - `oklch() -> oklab()` - when `c` is infinite and `h` is 0 This [CodePen](https://codepen.io/creativewave/pen/eYaKyVX) allows to play with conversion functions, which have comments for `NaN` cases.In
colorjs.io@0.5.1
,NaN
seems to be replaced bynone
, which seems incorrect:Clarification on how to handle infinite values has already been asked in #8629, which was resolved with no further change because if you put an infinite calculation into an rgba(), the behavior is well-defined: clamp to the allowed range.
At least, this is not true for some channels of other color functions than
rgb()
, and for relative colors.Following this comment, I tried to guess the color resulting from a color function specified with one infinite channel value. Since I am personally only interested by serialized values, I only did it for
hsl() -> rgb()
andhwb() -> rgb()
, but it should presumably be done for all conversions producingNaN
.When converting
- when `l === 0`, `r`, `g`, `b`, are `0` - when `l < 0`, same as when `l > 0` but with inverted infinite signs - when `l > 0`: | `h` | `r` | `g` | `b` | --------------- | -------------- | -------------- | -------------- | `330 < h < 30` | `+Infinity` | `-Infinity` | `-Infinity` | `h === 30` | `+Infinity` | depends on `l` | `-Infinity` | `30 < h < 90` | `+Infinity` | `+Infinity` | `-Infinity` | `h === 90` | depends on `l` | `+Infinity` | `-Infinity` | `90 < h < 150` | `-Infinity` | `+Infinity` | `-Infinity` | `h === 150` | `-Infinity` | `+Infinity` | depends on `l` | `150 < h < 210` | `-Infinity` | `+Infinity` | `+Infinity` | `h === 210` | `-Infinity` | depends on `l` | `+Infinity` | `210 < h < 270` | `-Infinity` | `-Infinity` | `+Infinity` | `h === 270` | depends on `l` | `-Infinity` | `+Infinity` | `270 < h < 330` | `+Infinity` | `-Infinity` | `+Infinity` | `h === 330` | `+Infinity` | `-Infinity` | depends on `l` When converting `hsl()` to `rgb()` and `l === +Infinity`: - when `-100 <= saturation <= 100`, `r`, `g`, `b`, are `+Infinity` - when `saturation < -100`, same as `saturation > 100` but with inverted infinite signs - when `saturation > 100`, `r`, `g`, `b`, are `+Infinity`/`-Infinity` depending on `saturation` and `h` - there are 6 intervals defined by `h` - the exact `h` interval values depend on `saturation` - for each sibling intervals, one of `r`/`g`/`b` has a different sign When converting `hsl()` to `rgb()` and `l === -Infinity`: - when `-100 <= saturation <= 100`, `r`, `g`, `b`, are `-Infinity` - when `saturation < -100` or `saturation > 100`, same as when `lightness === +Infinity` but with different intervalshsl()
torgb()
andsaturation
is+Infinity
or-Infinity
: [...]Let's skip
hwb() -> rgb()
because it becomes more complicated when more than one channel value is infinite.For example (
lab() -> color(xyz)
), whena === 0
,z
tends towards+Infinity
whenl
tends towards+Infinity
, butz
tends towards-Infinity
whenb
also tends towards+Infinity
. Shouldz
be0
whenl
andb
are+Infinity
? Should some "precedence" between channel values be defined (eg.l
overa
andb
)?Note that channel values do not always tend linearly towards an infinite value:
rgb(from hsl(0 -99 calc(infinity)) r g b)
would resolve tocolor(srgb calc(infinity) calc(infinity) calc(infinity))
rgb(from hsl(0 -101 calc(infinity)) r g b)
would resolve tocolor(srgb calc(infinity) calc(-infinity) calc(-infinity))
Am I missing something?