-
Notifications
You must be signed in to change notification settings - Fork 682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[css-color-4] Achromatic colors converted to hue-ish spaces should treat hue as "missing", not NaN #6107
Comments
and
What I'm not seeing is a) an explanation of why that spec text is a good and useful thing (divide by zero produces Several color APIs use NaN here (d3.js for example, which also uses it for missing data in general, colorio and color.js too. It seems natural that the Color API would do this too. So first we need to understand why CSS V&U wants to map I see @LeaVerou asked a similar question earlier
A quick search for |
It should be noted that there is a lot of prior art in Color libraries for using NaN for achromatic interpolation.
That ...seems like hugely problematic behavior that we should, at the very least, get group consensus on before we accept it as something to design other specs around. |
Because NaN is a meaningless error state. Rather than putting the onus on every single function and property that accepts numeric values to define how they handle a NaN, censoring it immediately gives us a well-defined behavior. And, since you only produce NaN via writing erroneous code, the actual behavior doesn't much matter anyway; it just needs to be well-defined, and ideally it makes it somewhat obvious that something is wrong.
That's quite a different question from what the spec is currently concerning itself with! Right now, this "missing hue" situation is solely caused by interpolation in a hue-ful space, when an achromatic color is originally defined in a hue-less space. It never shows up in a way that actually gets exposed to users, since the intermediate interpolation has a well-defined hue at all times. If you want this state to be explicitly specifiable by userland code, we can do that; the hue parameter of the relevant color functions would have to take some appropriate keyword to indicate it's hue-less, and we'd reflect that in OM and TypedOM as well. But this isn't directly relevant to the issue at hand. Right now, this "missing hue" state is not directly reflected in the serialization or specifiable by a user (we can't write
Sure, a lot of programming languages (JS and C++, in particular) just use a However, that's ultimately a hack due to the fact that these langs have weak/non-ergonomic type systems, and so smuggling in extra data via sentinel values is a reasonable compromise of usability vs semantics/type safety. In languages that have a better type system, like Rust, the standard practice is to actually specify the secondary values, like with an
This has been the specified behavior of NaNs since 2014, and was discussed in https://lists.w3.org/Archives/Public/www-style/2014Apr/0101.html. Note the convo there reflects what I said above, too - NaN is produced solely as a result of an authoring mistake, so it doesn't matter what it turns into, but ideally it turns into something that makes the mistake likely to be noticed. Given the several subsequent discussions we've had over this chapter in CSSWG calls/meetings over the years, I think it's safe to say the group treats it as uncontroversial. If you'd like to revisit it, okay, but I think you'd need to give a pretty good reason why we actually want |
Oh yeah, as suggested by the mailing list link in the preceding comment, that's because the specification for this predates our use of GitHub Issues by a year or two. ^_^ Check that message thread for the reasoning. |
No, that is incorrect.
It really does matter, as the cases cited demonstrate.
No, it is cased by colorspace conversion regardless of whether you plan to interpolate it
The correct term here is cylindrical polar color representation btw
Right, when an achromatic color is converted to a CPCR
It sure does
Happens outside of interpolation. |
Yeah, we are going to need that. Well, we (and several color libraries) have that already, but if we conclude that censoring |
If that is your axiom then, since it is untrue, we need to revisit this decision. Sure, |
"NaN used as flag for smuggling an extra boolean into an attribute defined as a double" is different than "NaN produced as result of erroneous math". Like I said, languages with weaker or less ergonomic type systems do the former to avoid having to burden their APIs with carrying around an extra boolean, and are okay with accepting that it unfortunately conflates with the latter case (and thus some erroneous code is instead interpreted as correct code that does something different). CSS does not need to do that. Are you really wanting to argue that
Sure, but all such conversions are censored away into a color with a hue before authors see it, right? That's the point I'm making there - this is a spec-internal concept that is only exposed to authors by way of the effect it has on other processes; it never shows up in a serialization, and authors can't write it directly on their own. (Tho fwiw, as far as I can tell your general statement is not true; colorspace conversions don't produce NaN. The only example in the spec that describes converting from a hue-less to hue-ful representation is converting Lab to LCH, where H is calculated as |
After Tab's replies, I can now see both sides of this, so I'm going to mostly step back and observe the rest of the discussion. I still think that handling NaN as Infinity is completely inconsistent with any other language that uses NaN, as well as with IEEE 754, but if we don't use it here, perhaps the effects of that will be more limited. But if we do what Tab is proposing, how would this "missing" value serialize in Typed OM and the like? It would need to be yet another object, which complicates the object model even more. And eventually we are going to have a |
No. That is your model of "the only way let gray = new Color("Lab" [50, 0.00001, 0.00002]);
let angle = gray.to("LCH").hue;
As I already said:
and as I (meant to) continue
On the observability of the value
You keep stating this. No. Why would it? The spec is already very clear that some cylindrical polar color representations have an undefined hue. You seem to believe that it is censored away into +infinity which then becomes, I dunno, 360? |
The non-normative, deliberately simplified, sample code can omit this subtlety but you are right, the "Converting" sections should state this clearly. lab (Lab) {
// Convert to polar form
let [L, a, b] = Lab;
let hue;
const ε = 0.02;
if (Math.abs(a) < ε && Math.abs(b) < ε) {
hue = NaN;
}
else {
hue = Math.atan2(b, a) * 180 / Math.PI;
}
return [
L, // L is still L
Math.sqrt(a ** 2 + b ** 2), // Chroma
angles.constrain(hue) // Hue, in degrees [0 to 360)
];
} |
Other computer languages are allowed to throw errors when you pass in bad input, and so that's usually what happens when someone accidentally produces a NaN. CSS can't do that, so we work with the abilities we have: making it probably obvious that there's an error, by causing something to likely blow up with an infinity. (It's uncommon, but we do similar things in other spots in CSS when a value is parsed as valid, but ends up being meaningless or an error in context; we choose an arbitrary answer this is easy to define and implement, and hopefully makes it obvious to the author that something has gone wrong. See, for example, referring to a non-existent grid line name in
Assuming we did allow a "missing hue" color to be exposed to the author (rather than just being a spec-internal concept that produces a normal hued color in the values that are author-exposed), then it would just mean that the (Note that the TypedOM repr gives the hue as a TypedOM numeric object, because it's an angle which can have multiple units, and might be a math function. This gives us a lot of flexibility in the value, versus more JS-only APIs that would just use
I apologize, I might not have made it clear enough why I was asking the specific question. This isn't a gotcha, or rhetorical, I was really wanting an answer from you, because it's important to how this question gets resolved. If the So, given that, I re-ask my question: is
I apologize, but I don't know what you're trying to say here. Your terseness is really making it hard to figure out the points you're trying to make. :(
Again, I asked that question deliberately. In what situation, precisely, is a color with undefined hue exposed to the author, in such a way that they can actually see the "undefinedness" represented in the value? I pointed to all the places where undefined hue seems to be capable of occurring, and as far as I can tell, by the time a value actually gets out to the author, that value always has a defined hue. In the spec, undefined hue appears to happen solely due to an achromatic hueless color being converted into a hueful space for the purpose of interpolating or mixing, and then the interpolation rules cause it to be treated as having a particular hue (that of the other color) at all points. Am I wrong? Does undefined hue happen at other times, in a way that would be exposed to the author by the OM? If so, where, and can you give me some sample code that would show it? |
I agree that treating
Chris already gave you an example where the missing hue needs to be exposed: Converting any achromatic Lab color (e.g. Regardless, I think the ability of authors being able to define color components as missing to have them automatically take the other color's value is quite useful. |
It's an error case, so it doesn't really matter so long as it's well-defined. (But per https://drafts.csswg.org/css-values/#numeric-types, it's defined to clamp to the nearest supported multiple of
I was asking for sample code because it wasn't clear to me how this would actually happen in a way that's author-exposed. Y'all just keep saying "when you convert a color", but as far as I can tell Color 4 doesn't have any way to do so that doesn't immediately fill in a hue anyway. I guess via Color 5's relative color syntax?
Sure, no complaints here; the d3 example seems quite compelling. |
Most sample code for converting to polar coordinates will typically produce a random hue (typically 0) for achromatic colors instead of declaring it as undefined or missing, but it would be more correct to produce a missing hue. Technically, even converting a gray sRGB color (e.g.
Yes, or |
I meant some CSS that would actually cause a conversion and expose its results directly to the author, not color conversion code in JS or whatever. The various Color 5 examples suffice, thanks. (Afaict I was right that with Color 4 abilities only a missing hue is never author-exposed, tho.) Does this suggest that we can push the "author-exposed missing hue" value to Color 5? Or, since it does at least have effects that can be triggered by interpolation, maybe we do want to add it to the color functions' grammars here in Color 4? If we do, I think we should add a missing saturation/chroma as well, right? d3 defaults to treating black/white as missing a chroma, which makes sense to me. And if we do that, should we allow a missing lightness to round out the set? No color meaningfully starts with a missing lightness (except maybe |
If we add a "missing value" keyword, I think it should be added in css-values, and referenced from Color 4. |
Nah, if it's color-specific it doesn't belong in V&U. If/when we end up using the same pattern elsewhere we can generalize and move it to V&U, but for now it's a color-specific value and should be defined in Color, imo. |
I think the concept you are missing, in general about CSS Color 4 (explicitly) and all colors back to CSS 1 (implicitly) is that they have a colorimetric basis. They are tied to scientific, rigorous, measurable color sensations. CSS Color 4 tries to explain that, with a balance between completeness and readability. This means that I see "a bunch of different ways to specify a color" with great care taken to say exactly what color that is. While you, I think, focus far more on "here are a bunch of different syntaxes" and what measured color that actually is, is sort of a by product. Which explains the disconnect over the color part of Typed OM. You see it as deeply tied to syntax, while I see it as a way to set, query and manipulate color. And thus of course if the author sets a color in syntax A they need to be able to get that color in syntax B or C or Z because the crucial thing is getting the color and indeed getting the right color. So yes, CSS Color 4 does not give a way to get a color in any given colorspace because it is not an object model. But it provides all the math and description to enable that conversion between colorspaces. |
No, it isn't an error case. The color |
On the off chance that implementations will help, here are the links to sRGB to HSL: and Lab to LCH: (We are probably getting lucky and the implementation of atan2() we are using seems to return 0 when both arguments are 0, but the c spec seems to say that it is a domain error, so we should probably add an explicit check). in WebKit. |
When they are exactly 0, yes. The issue is when a and b are supposed to both be zero but are actually like 0.00000005 and -0.0000003 and you get a meaningless, effectively random hue angle of -80.537 |
If both > new Color("#777").to('lab')
Color$1 {
_spaceId: 'lab',
toString: [Function: toString],
coords: [ 50.03434402595761, -0.0021727557177886325, -0.008043723319994811 ],
alpha: 1
} If they aren't rounded off, you get some wild values for hue as But even if they yielded zero reliably, zero is still a hue (red), but an achromatic color doesn't have a hue, so What I think is trying to be done is setting the stage for proper mixing by providing a way to make clear when a hue value is meaningless. How that happens is the argument, I guess. I'm just interested to see how it all plays out 🙂. EDIT: Looks like it was answered before I hit send. |
Oh, sorry, I didn't mean anything by that other than a note to myself that I should probably make sure we are not running into C undefined behavior issues in WebKit. |
Chris, you've spent this issue thread repeatedly misinterpreting me and my question/arguments in the worst possible light, and at this point I can't help but assume it's intentional and malicious. I'm not going to further engage you in this thread since you've repeatedly acted like I have zero understanding of how colors or colorspaces work (despite working with you on Color and color-adjacent topics for years in this WG), and you've repeatedly taken every reference I make to an explicitly-produced NaN from a calculation and pretended that I'm instead talking about grays. This isn't a productive use of my time. Lea, I'm happy to continue talking to you about this topic. |
@tabatkins I can assure you that @svgeesus is not being malicious. You're just both just talking cross-purposes. |
I see that |
- Allow none keyword - Allow interpolation result channel to return with a undefined channel if both channels under interpolation during interpolation are none - Allow printing CSS with none keywords (disabled by default) Ref: w3c/csswg-drafts#6107
- Allow none keyword - Allow interpolation result channel to return with a undefined channel if both channels under interpolation during interpolation are none - Allow printing CSS with none keywords (disabled by default) Ref: w3c/csswg-drafts#6107
In theory they can be any color in an HDR context, whose lightness is greater than media white. In practice people rarely use Lab for HDR, outside academic studies.
Yes, making it warmer or cooler or (more usually) a deeper black than just K=100% would give you. This is called TAC (Total Area Coverage) and can go up to 300%, or even more on heavier papers.
Right. |
The defaults here aren't intended for usage, they're just here to give us an answer for what to do when
Yup, marking a
If a color with a This requires a little work to invoke manually, like Again, this is purely an error case, tho. An author should never use
Note that this isn't behavior for when alpha is omitted (that works as you expect -
So should I leave it as-is (lab lightness >= 100% does not render the hue/chroma powerless), then? Or is this a theoretical ability only, and in practice we should go ahead and define that lightness >= 100% is some variety of "pure white", and leave "colored HDR white" as a something that another color function might handle? |
I'm tending more towards that one. |
Okay, that sounds like in this case hue would not become "powerless' until
Right, I believe this was solely from an interpolation perspective, not |
Cool, I'll make the edits.
Yes, exactly. |
I guess my hesitancy with going with zero-everywhere is that it does kind of mostly work in practice, and that it's not clear that using
If |
On the other hand,
If anything, making it less convenient is better, because |
Right. I would even argue that usage of As it stands now, a color using
I don't have the context necessary to think through the implications, but how would this work in practice for transitions, etc? Taking a use-case from the spec: p {
color: lch(none 0% none);
transition: color 1s;
}
p:hover {
color: lch(50% 100 40);
} On mouseover: jumping from black to gray, then smoothly towards red? Some types of interpolations (where the other color the value is relative to is not known beforehand) would only work smoothly in the cases where So does it make sense to limit the scope of none to only be valid in powerless slots? I appreciate this would be dialing it back a lot. Or, conversely, accept the glitchiness of some contexts? |
And
That would be reasonable (RGB components are not powerless, for example) but is sketchy in edge cases. Is hue powerless in
That could also be a reasonable outcome ("not all uses of |
The big point is, the entire reason we're adding This means that limiting Any and all usage of |
@tabatkins given your most recent comment, and the edits to add |
Thanks @tabatkins @svgeesus for indulging me in working out a mental model on this. I think one small leftover is updating the |
Yes, indeed |
Oh that was entirely my mistake in missing it, dunno how it slipped my mind. I'll take care of that. |
Hmm looks like we both did that? |
Yes, I didn't see your edit and did it myself, and then merged in the conflict (you hadn't applied it to the alpha). |
In https://drafts.csswg.org/css-color-4/#hue-syntax it's stated that:
Then this is later referenced in interpolation:
The intent of this spec text is good and correct; it means interpolating from gray to green in LCH just becomes greener, rather than the hue swinging from red to green or something.
However, the exact mechanics of this aren't workable.
NaN
is limited to existing within the bounds of a calculation; it gets censored toinfinity
if it would escape (andinfinity
then gets clamped to the actual largest value the impl supports). So we can't have aNaN
value just hanging out in normal CSS values. I think instead this should be phrased as achromatic colors having a "missing" hue, and then interpolation can key off of whether the hue is "missing".Even if
NaN
did persist into normal CSS values, I don't think we actually want to merge "calculation for the angle yields NaN" with "achromatic color interpolates with chromatic color"; the latter is a common non-error case with an obvious solution, while the former is explicitly the result of a bad or nonsensical calculation with no obvious answer. So we really shouldn't be keying off ofNaN
anyway; a "real"NaN
should be treated the same as aninfinity
(which it luckily censors itself into already), where it's an error case that just has an arbitrary answer (per #6105, effectively0deg
).The text was updated successfully, but these errors were encountered: