Skip to content
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] HTML needs an html-compatible, hex serialization of 8 bit/component sRGB colors #10550

Closed
svgeesus opened this issue Jul 10, 2024 · 21 comments

Comments

@svgeesus
Copy link
Contributor

Originally posted by @annevk in whatwg/html#8917 (comment)

From HTML's perspective we have these requirements:

  • 2D canvas: stores a CSS color and when that color happens to use 8-bit per component it needs to be serialized as #... (when opaque) or rgba(...) (when not opaque). I think it would make sense if we could set a boolean named parameter called "HTMLCompatible" or some such when serializing to enable that.
  • <input type=color>: stores a CSS color, but when serializing that color a) needs to be converted to a color space according to the colorspace attribute b) when that is Limited sRGB that color b1) needs to be rounded to 8-bits per component and b2) use "HTMLCompatible" just like 2D canvas

I think for <input type=color>:

  • HTML should probably do the color space conversion upon the CSS color.
  • HTML should probably do the rounding as well, although I could also see this being an additional named parameter, but I don't see much utility in that unless there's multiple callers needing it.

So here is what this would mean for the CSS Color specification for maximum clarity:

  • It needs to define a "serialize a CSS color" operation that takes a CSS color and outputs a string (doh).
  • It needs to define a "HTMLCompatible" named parameter for that operation that influences the serialization when the passed CSS color is a) in the 'srgb' color space and b) uses 8-bits per component. In particular for colors meeting those requirements it will use https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color (which it defines itself so that definition can be removed from HTML).

Does that make sense? Once that's in place I can update the <input type=color> PR and perhaps also create a separate PR to ensure 2D canvas is properly defined.

@svgeesus svgeesus added the css-color-4 Current Work label Jul 10, 2024
@svgeesus
Copy link
Contributor Author

svgeesus commented Jul 10, 2024

CSS Color 4 has a concept of legacy color syntax. Essentially, pre-css color 4 colors (rgb, rgba, hsl, hsla) get serialized as they always have. Non-legacy colors are serialized as described in csswg.sesse.net/css-color-4.

The simplest solution would just be to do exactly what CSS is doing, while preserving "opaque colors are hex" quirk from https://html.spec.whatwg.org/#serialisation-of-a-color.

ctx.fillStyle = "rgb(255, 0, 255)";
ctx.fillStyle;   // '#ff00ff'
ctx.fillStyle = "rgb(255, 0, 255, 0.5)"
ctx.fillStyle;   // 'rgb(255, 0, 255, 0.5)'
ctx.fillStyle = "color(dIsPlAy-P3  0.964  0.763  0.787)"
ctx.fillStyle;   // 'color(display-p3 0.96 0.76 0.79)'

Could we even link to https://csswg.sesse.net/css-color-4/#serializing-color-function-values?
We obviously need some tests in wpt to cover this.

Originally posted by @mysteryDate in whatwg/html#8917 (comment)

@svgeesus
Copy link
Contributor Author

This is to allow the HTML spec "serialization of a color" to be replaced by a reference to CSS Color; while also updating it to cover display-p3 because Canvas has the option of either sRGB or display-p3 now.

The serialization of a color for a color value is a string, computed as follows: if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character (U+0023 NUMBER SIGN), with the first two digits representing the red component, the next two digits representing the green component, and the last two digits representing the blue component, the digits being ASCII lower hex digits. Otherwise, the color value has alpha less than 1.0, and the string is the color value in the CSS rgba() functional-notation format: "rgba" (U+0072 U+0067 U+0062 U+0061) followed by a U+0028 LEFT PARENTHESIS, a base-ten integer in the range 0-255 representing the red component (using ASCII digits in the shortest form possible), a literal U+002C COMMA and U+0020 SPACE, an integer for the green component, a comma and a space, an integer for the blue component, another comma and space, a U+0030 DIGIT ZERO, if the alpha value is greater than zero then a U+002E FULL STOP (representing the decimal point), if the alpha value is greater than zero then one or more ASCII digits representing the fractional part of the alpha, and finally a U+0029 RIGHT PARENTHESIS. User agents must express the fractional part of the alpha value, if any, with the level of precision necessary for the alpha value, when reparsed, to be interpreted as the same alpha value.

@annevk
Copy link
Member

annevk commented Jul 15, 2024

HTML PRs are now up:

CSS Color defining serialize a CSS <color> value (to complement equivalently named parse operation) with a htmlCompatible named parameter would let me land those two PRs. Happy to help review.

@svgeesus svgeesus added the Async Resolution: Proposed Candidate for auto-resolve with stated time limit label Jul 15, 2024
@svgeesus
Copy link
Contributor Author

Proposed resolution: CSS WG will work together with WhatWG to add an HTML-compatible, hex serialization of 8 bit/component color, for ease of referencing from HTML LS

@astearns

@astearns
Copy link
Member

The CSSWG will automatically accept this resolution one week from now if no objections are raised here. Anyone can add an emoji to this comment to express support. If you do not support this resolution, please add a new comment.

Proposed Resolution: CSS WG will work together with WhatWG to add an HTML-compatible, hex serialization of 8 bit/component color, for ease of referencing from HTML LS

@astearns astearns added Async Resolution: Call For Consensus Resolution will be called after time limit expires and removed Async Resolution: Proposed Candidate for auto-resolve with stated time limit labels Jul 16, 2024
@astearns
Copy link
Member

RESOLVED: CSS WG will work together with WhatWG to add an HTML-compatible, hex serialization of 8 bit/component color, for ease of referencing from HTML LS

@annevk
Copy link
Member

annevk commented Aug 13, 2024

Great to see this resolved! @svgeesus when do you plan on adding this?

@svgeesus
Copy link
Contributor Author

Sorry I have been busy. Will get to it. If you have concrete wording suggestions please drop them into this thread, of course.

@annevk
Copy link
Member

annevk commented Aug 14, 2024

Thanks @svgeesus for adding https://drafts.csswg.org/css-color-4/#serializing-sRGB-values. Per feedback from Domenic "htmlCompatible" would be better. I was also hoping for an actual algorithm, which the current setup doesn't entirely lean itself to. But perhaps we can pretend it is one for now.

I also noticed these other things:

  • "The color space is sRGB" this seems to be implied as this only cares about sRGB values anyway.
  • The examples need changes because as I noted this is really about fillStyle and friends. Not about the data format getImageData() uses.
@annevk
Copy link
Member

annevk commented Aug 15, 2024

With respect to "actual algorithm", we currently invoke https://drafts.csswg.org/css-color/#parse-a-css-color-value from HTML for parsing. Having a serialization counterpart to that would be ideal, but for now we can pretend these serialization sections represent that I suppose.

@svgeesus
Copy link
Contributor Author

In terms of an actual algorithm, the procedure I described in prose is consistent with the rest of the CSS Color 4 serialization section. It is also, by the way, consistent with (the first part of) HTML LS serialization of a color:

the string is a lowercase six-digit hex value, prefixed with a "#" character (U+0023 NUMBER SIGN), with the first two digits representing the red component, the next two digits representing the green component, and the last two digits representing the blue component, the digits being ASCII lower hex digits.

If there is really a preference for using a more algorithmic approach I can do that, but assembling a seven-character string from "#" plus three, two digit, lower-ASCII hex strings hardly seems to warrant it.

@annevk
Copy link
Member

annevk commented Aug 15, 2024

It's mostly that how to invoke it from HTML is a bit ambiguous, but if the way I did it in whatwg/html#10481 works for everyone I'm okay.


I found something else when testing rgb(300 none 400) in implementations. This becomes #ff00ff in Chrome and Firefox, and rgb(255, 0, 255) in Safari. Apparently dropping the precision for this syntax is endorsed:

Also for compatibility, the component values are serialized in base 10, with a range of [0-255], regardless of the bit depth with which they are stored.

Maybe we should standardize on what Chrome and Firefox do here? It seems more useful if they all get serialized using the same form.

This sRGB section also doesn't address mapping "none" to 0, but it probably should?

cc @weinig

@svgeesus
Copy link
Contributor Author

This sRGB section also doesn't address mapping "none" to 0, but it probably should?

Because it is not specific to sRGB. It is defined in 4.4. “Missing” Color Components and the none Keyword:

If a color with a missing component is serialized or otherwise presented directly to an author, then for legacy color syntax it represents that component as a zero value; otherwise, it represents that component as being the none keyword.

Hex colors are a legacy color syntax. color(whatever r g b / alpha) is a modern syntax.

But then, 5.2.2. CSS serialization of sRGB values does reiterate it:

During serialization, any missing values are converted to 0.

So maybe the htmlCompatible serialization should also say that, for sRGB values.

@annevk
Copy link
Member

annevk commented Aug 15, 2024

rgb(300 none 400) is not legacy syntax, is it? But you're right that the sRGB serialization section appears to cover it, but it should be noted higher up indeed as it should apply to both HTML sRGB serialization and normal sRGB serialization.

The additional point about out-of-range values should probably move up as well then to apply to both HTML sRGB and normal sRGB.

@svgeesus
Copy link
Contributor Author

rgb(300 none 400) is not legacy syntax, is it?

Correct, it is not

The additional point about out-of-range values should probably move up as well then to apply to both HTML sRGB and normal sRGB.

Yup

But you're right that the sRGB serialization section appears to cover it, but it should be noted higher up indeed as it should apply to both HTML sRGB serialization and normal sRGB serialization.

Good idea

@domenic
Copy link
Collaborator

domenic commented Aug 16, 2024

It's mostly that how to invoke it from HTML is a bit ambiguous, but if the way I did it in whatwg/html#10481 works for everyone I'm okay.

I agree that how it's invoked from HTML is too ambiguous.

It would have been nice to have a full algorithm for converting a well-defined "CSS color" data structure into a string, with a named htmlCompatible argument. I can understand if culturally that is not how the CSSWG does serialization though. That is, apparently there is no "CSS color" data structure but instead a <color> grammar concept, and it's traditional to give serialization rules as a series of "This applies to this case" sections and "If"s within those sections, instead of one algorithm you could correspond directly with the implementation.

To bridge these worlds, I think the minimum we'd need would be a well-defined linkable <dfn> for "HTML-compatible serialization is requested". I think a named parameter htmlCompatible doesn't make sense given that we don't have an actual serialization algorithm <dfn>, so I'd suggest modifying https://drafts.csswg.org/css-color/#HTML-compatible-serialization-of-srgb step 5 from

5. HTMLCompatible serialization is requested

to

5. <dfn export for="color serialization">HTML-compatible serialization is requested</dfn>

and then we can update the HTML side to say something like with <a href="...">HTML-compatible serialization requested</a>.

@annevk
Copy link
Member

annevk commented Sep 4, 2024

@svgeesus @tabatkins it'd be great to get @domenic's comment resolved as that's currently blocking a bunch of work on the HTML side, including <input type=color colorspace>.

@svgeesus
Copy link
Contributor Author

svgeesus commented Sep 4, 2024

That is, apparently there is no "CSS color" data structure but instead a <color> grammar concept, and it's traditional to give serialization rules as a series of "This applies to this case" sections and "If"s within those sections, instead of one algorithm you could correspond directly with the implementation.

That is correct. Interoperability of CSS implementations is defined over the CSS content that they parse as input; the precise details of internal representation are intentionally not described, they can and do vary between implementations.

I added the <dfn> as requested.

svgeesus added a commit that referenced this issue Sep 4, 2024
…s to both HTML-compatible and CSS serializations #10550
@svgeesus
Copy link
Contributor Author

svgeesus commented Sep 4, 2024

The additional point about out-of-range values should probably move up as well then to apply to both HTML sRGB and normal sRGB.

Agreed and also done

@annevk you said the examples of where HTML-compatible serialization would be used are wrong because they only apply to things like fill and stroke, not reading colors back from canvas. Can you suggest a better example that I could use instead?

svgeesus added a commit that referenced this issue Sep 4, 2024
…alization, they will already have been converted to zero #10550
annevk added a commit to web-platform-tests/wpt that referenced this issue Sep 17, 2024
@annevk
Copy link
Member

annevk commented Sep 17, 2024

Thanks!

Some examples (mainly borrowed from the tests I wrote):

context.fillStyle = "#ff00ff00";
console.log(context.fillStyle); // "rgba(255, 0, 255, 0)"

context.fillStyle = "lab(29% 39 20)";
console.log(context.fillStyle); // "lab(29 39 20)"

context.fillStyle = "rgb(255, 0, 255)"
console.log(context.fillStyle); // "#ff00ff"
svgeesus added a commit that referenced this issue Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment