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

Serialization of color names #26

Open
raphlinus opened this issue Nov 9, 2024 · 5 comments
Open

Serialization of color names #26

raphlinus opened this issue Nov 9, 2024 · 5 comments

Comments

@raphlinus
Copy link
Contributor

There's a basic question that needs a decision (though it doesn't have to happen right now), and some more subtle considerations.

The serialization section of the CSS Color 4 spec requires that a parse of "red" serializes as "red", not "rgb(255, 0, 0)" or "#ff0000". I can see this as potentially useful in applications that process CSS for output to the web, improving human readability. We could do it, but it has some cost and complexity.

The outline of how I'd do it is reserve another bit in Missing (which I'd probably rename "Flags" or somesuch) indicating that it's a named color. Then, when serializing, if the bit is set, the color would be rounded to rgba8, the named color table would be searched, and, if there's a match, the name output. If there's not a match, then it would be serialized as if the bit were not set. The search could be implemented at least 3 ways: linear scan (slow-ish but simple and with no additional data burden), phf (tricky, estimated 284 bytes), or binary search using an additional 142 byte permutation table representing the sort by u32.

But this touches a deeper problem. There is some potentially some divergence between what we want for string output, and strict CSS serialization. Aside from the named colors, we need to serialize out-of-gamut srgb using the color function and srgb colorspace, rather than the rgb function, as the latter clips. Also, if we have color spaces not covered by CSS (#14, #15, possibly others), then those won't serialize to a valid CSS string.

One solution is that we do our own thing for Display on our color types that is not necessarily CSS compatible, and have another way to do strictly CSS compatible serialization. I'd lean toward doing that as Display impl on a CssFormat wrapper type, and also have that feature gated, but am open to other ideas.

@DJMcNab
Copy link
Member

DJMcNab commented Nov 11, 2024

Do we not have enough space available to just store which named colour it was inline? From a quick check, we seem to have 16 bits of padding in DynamicColor without even thinking of packing things in more tightly, which would allow 65000 named colours. Obviously you have to be careful about when to discard those, but you have to discard the named bit in the same circumstances anyway.

@tomcur
Copy link
Member

tomcur commented Nov 13, 2024

Aside from the named colors, we need to serialize out-of-gamut srgb using the color function and srgb colorspace, rather than the rgb function, as the latter clips.

I may be mistaken, but I think that is the approach the spec takes. The spec says colors generated from the values specified in § 15.2 should be serialized to rgb or rgba, but all those are either already in-gamut or are specified to do parse-time clamping.

Other colors are serialized to color as in § 15.5.

In other words, I believe the spec says color(sRGB 0.0 0.0 0.0) and color(sRGB -0.1 0.0 0.0) should be serialized as color(srgb 0 0 0) and color(srgb -0.1 0 0) respectively.

That does mean Flags would need an additional bit to track whether the color was generated from a color space function (rgb(), oklab(), ...) or from the color() function. (And one bit for RGB specifically to distinguish whether it was rgb() or one of the other sRGB-based functions, or a named color, but that could also be done with Daniel's suggestion and using e.g. 0x00 as meaning "not from a named color", basically using a 1-based index into the name array).

tomcur added a commit to tomcur/color that referenced this issue Nov 13, 2024
A rough implementation of the ideas in
linebender#26 (comment).

The ergonomics require some tweaking, and perhaps the performance too,
though it should be easy to write this in a form where the compiler can
easily optimize the bitwise operations. I haven't verified what the
compiler does with the current form.

An alternative is to keep `Missing` unchanged, and add something similar
to `Flags` as a new field on `DynamicColor`. But adding a new field
makes constructing `DynamicColor` a bit less ergonomic.
@tomcur
Copy link
Member

tomcur commented Nov 13, 2024

Opened draft #39 for consideration.

tomcur added a commit to tomcur/color that referenced this issue Nov 13, 2024
A rough implementation of the ideas in
linebender#26 (comment).

The ergonomics require some tweaking, and perhaps the performance too,
though it should be easy to write this in a form where the compiler can
easily optimize the bitwise operations. I haven't verified what the
compiler does with the current form.

An alternative is to keep `Missing` unchanged, and add something similar
to `Flags` as a new field on `DynamicColor`. But adding a new field
makes constructing `DynamicColor` a bit less ergonomic.
tomcur added a commit to tomcur/color that referenced this issue Nov 13, 2024
A rough implementation of the ideas in
linebender#26 (comment).

The ergonomics require some tweaking, and perhaps the performance too,
though it should be easy to write this in a form where the compiler can
easily optimize the bitwise operations. I haven't verified what the
compiler does with the current form.

An alternative is to keep `Missing` unchanged, and add something similar
to `Flags` as a new field on `DynamicColor`. But adding a new field
makes constructing `DynamicColor` a bit less ergonomic.
tomcur added a commit to tomcur/color that referenced this issue Nov 16, 2024
A rough implementation of the ideas in
linebender#26 (comment).

The ergonomics require some tweaking, and perhaps the performance too,
though it should be easy to write this in a form where the compiler can
easily optimize the bitwise operations. I haven't verified what the
compiler does with the current form.

An alternative is to keep `Missing` unchanged, and add something similar
to `Flags` as a new field on `DynamicColor`. But adding a new field
makes constructing `DynamicColor` a bit less ergonomic.
@danburzo
Copy link

danburzo commented Nov 17, 2024

The css-color-4 editor’s draft contains some updated language for serializing CSS colors. The spec makes a distinction between serialized declared value and serialized computed value, and neither of them are what we would expect from a “toString()” method in a library.

  • the declared value of named colors and the transparent keyword is serialized as-is, normalized to all-lowercase.
  • the declared and computed values of hsl() and other sRGB-derived color spaces serialize to rgb().
  • the declared and computed values of sRGB colors are serialized as rgb() or color(), matching how they were declared.

Two places where these serialized values appear on the web platform:

  • When serializing the content of a CSS stylesheet (i.e. with the cssText property), the serialized declared value is used.
  • When obtaining the computed style of a DOM element with getComputedStyle(), the serialized computed value is used.

The distinction between the serialization of declared and computed values is clearer in css-color-5, which introduces relative colors. The serialized declared value keeps the color relative, while the computed value resolves it to an absolute color (in most cases).

So to match the CSS spec on the serialization of declared and/or computed values, it does seem the color object needs to store some info on how it was declared.

But the library will probably benefit from a CSS-compatible serialization uncoupled from these rules, so that hsl() remains hsl(), and all formats of sRGB produce a single form of color(srgb) or rgb().

The css-color-5 also offers a mechanism to serialize custom color spaces. Albeit this currently refers to ICC-profiled color spaces, it’s an accepted convention to serialize non-CSS color spaces with an --ident.

tomcur added a commit to tomcur/color that referenced this issue Nov 18, 2024
A rough implementation of the ideas in
linebender#26 (comment).

The ergonomics require some tweaking, and perhaps the performance too,
though it should be easy to write this in a form where the compiler can
easily optimize the bitwise operations. I haven't verified what the
compiler does with the current form.

An alternative is to keep `Missing` unchanged, and add something similar
to `Flags` as a new field on `DynamicColor`. But adding a new field
makes constructing `DynamicColor` a bit less ergonomic.

---------

Co-authored-by: Daniel McNab <[email protected]>
@tomcur
Copy link
Member

tomcur commented Nov 18, 2024

Thank you for the helpful summary! It's interesting the language is updated from "specified" to "declared". For level 4, aside from the language change, I believe the current editor's draft agrees with the current candidate recommendation draft about serializing.

In level 5, relative color makes things a bit more complicated, but the section on serializing custom color spaces is a potential answer to a question we were having.

So to match the CSS spec on the serialization of declared and/or computed values, it does seem the color object needs to store some info on how it was declared.

Indeed, that appears to be necessary.

tomcur added a commit to tomcur/color that referenced this issue Dec 12, 2024
A rough implementation of the ideas in
linebender#26 (comment).

The ergonomics require some tweaking, and perhaps the performance too,
though it should be easy to write this in a form where the compiler can
easily optimize the bitwise operations. I haven't verified what the
compiler does with the current form.

An alternative is to keep `Missing` unchanged, and add something similar
to `Flags` as a new field on `DynamicColor`. But adding a new field
makes constructing `DynamicColor` a bit less ergonomic.

Squashed:

hsl() and hwb() serialize to rgb()

Naming consistency

Check size only during test

Test all named colors

Mixed case test

Separate into two `u8`s

consistency

Debug impl

Clean up

Clippy

Use Missing directly

Reduce api surface

Module name

Simplify Debug

Module header

Inline more

Docs

---------

Co-authored-by: Daniel McNab <[email protected]>
github-merge-queue bot pushed a commit that referenced this issue Dec 12, 2024
To perform correct CSS Color 4 serialization of colors, some details of
how a color was declared have to be preserved. Specifically, [CSS Color
Module Level 4 §
15.2](https://www.w3.org/TR/css-color-4/#serializing-sRGB-values) and
the two sections on Lab and Oklab following it specify that
- a color declared as a named CSS/X11 color, should serialize to that
(normalized) color name; and
- a color declared as one of the named color space functions – i.e.,
_not_ the `color()` function – should serialize to that function.
(Exceptions being `hsl()` and `hwb()`, those serialize to `rgb()`.)

This implementation uses the padding currently available on
`DynamicColor` to pack in the color name the `DynamicColor` was created
from, or whether a named color space function was used; an
implementation of the ideas in
#26 (comment).

`DynamicColor` now contains `Flags`, and the existing `Missing` color
components are moved into `Flags`. In this implementation, only
`parse_color` sets the color name or color space function name.

---------

Co-authored-by: Daniel McNab <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants