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-5] Should colors nested in a parent color function (RCS, color-mix, light-dark, color-contrast) serialize in their most precision saving form #10328

Open
weinig opened this issue May 13, 2024 · 9 comments
Labels
css-color-5 Color modification

Comments

@weinig
Copy link
Contributor

weinig commented May 13, 2024

In CSS Color 5, the serialization of relative colors is defined with the following:

The serialization of the declared value of a relative color function is a string identifying the color function in all-lowercase, followed by "(from ", followed by the serialization of the declared value of the origin color, followed by a single space, followed by a singly-space-separated list of the arguments to the color function, followed by ")".

(emphasis added)

If taken at face value, this would mean that relative colors do not round trip faithfully when the origin color has values outside the reference range, as the processing model for relative colors states:

When relative color syntax is used, color channel values, whether directly specified or arising from color space conversion, are not clamped to the reference ranges but are retained as-is. This preserves out of gamut values, if the destination color space is capable of representing them.

So, in an example like:

hsl(from hsl(127.9 302% 25.33%) h s l)

my reading is that the declared value would serialize as: hsl(from rgb(0, 255, 0) h s l), but the computed value would be color(srgb -0.511666 1.018266 -0.310225)

I would like to propose that nested colors, both for RCS and the other color functions that take color arguments, the nested colors serialize in a way that preserves the behavior of the parent. The most straightforward way I can think to have that work would be to serialize the declared value according to normal (not encumbered by the legacy of color serialization) CSS serialization rules for declared values - aka minimal needed, calc() preserving, etc.

In the case above, that would mean serializing the declared value exactly as written:

hsl(from hsl(127.9 302% 25.33%) h s l)

To expand with another example:

rgb(from hsl(calc(30 + 60) 50% none) r g b)

would serialize the declared value as:

rgb(from hsl(calc(90) 50% none) r g b)
@kbabbitt kbabbitt added the css-color-5 Color modification label May 13, 2024
@weinig weinig changed the title [css-color] Should colors nested in a parent color function (RCS, color-mix, light-dark, color-contrast) serialize in their most precision saving form [css-color-5] Should colors nested in a parent color function (RCS, color-mix, light-dark, color-contrast) serialize in their most precision saving form May 13, 2024
@annevk
Copy link
Member

annevk commented May 21, 2024

cc @svgeesus @LeaVerou

@LeaVerou
Copy link
Member

This makes total sense to me. Agenda+ so we can resolve ASAP.

@cdoublev
Copy link
Collaborator

cdoublev commented May 22, 2024

My reading is that channel values must never be clamped in a relative color. But I agree that it should also apply to colors nested in other color functions. I would still resolve <hue> and <percentage> to <number> though.

@weinig
Copy link
Contributor Author

weinig commented May 22, 2024

The question isn't really about clamping per-se, but rather about serialization of those unclamped values.

@cdoublev
Copy link
Collaborator

Ah yes. I was thinking of converting hsl to rgb without clamping, to be consistent with how top-level absolute color functions serialize, and you propose to serialize the list of component values (with simplified math functions, omitted optional values, etc).

Your proposition is also my preference but, and to expand on this #10327 (comment), I do not know/understand why all absolute color functions are partially or entirely resolved when serializing them as components of a declared value, which seems to also be your point in #10305. Therefore my reasoning is confused (sorry).

@svgeesus
Copy link
Contributor

@weinig could you propose replacement spec text for the part starting

The serialization of the declared value of a relative color function

I think we would come to consensus on a call more rapidly if there is specific proposed text, rather than a general point whose wording then needs to be discussed and agreed upon.

@weinig
Copy link
Contributor Author

weinig commented May 28, 2024

@svgeesus I can give it a shot, but I'm not entirely confident. I spent a while looking at specs to find rules or guidance (CSSOM, CSS Values and Units, CSS Cascade) but it left me a bit more unclear than I would have liked. That said, here is my, likely overly verbose, take:


I think we should probably will want to have a sub-definition of the serialization of a color used as the origin color in another color. That might look like this:

The serialization of a the declared value of a color used as the origin color inside of another color function (color-mix, RCS, color-contrast) is:

For rgb(),rgba(),hsl(),hsla() - the string identifying the canonical color function, "rgb" for "rgb()" and "rgba()", "hsl" for "hsl()" and hsla()", in all-lowercase, followed by "(", followed by a space separated list of the non-alpha components as specified (numbers serializing as numbers, percentages serializing as percentages, angles serializing as canonicalized angles in degrees, calc() serializing in its simplified form) with no clamping applied, followed by " / " and the alpha component as specified (using the same rules as the color components) if an alpha component is present, followed by ")". (NOTE: the same serialization is used regardless of whether the modern or legacy syntax was used).

For hwb(),lab(),lch(),oklab(),oklch() - the string identifying the color function in all-lowercase, followed by "(", followed by a space separated list of the non-alpha components as specified (numbers serializing as numbers, percentages serializing as percentages, angles serializing as canonicalized angles in degrees, calc() serializing in its simplified form) with no clamping applied, followed by " / " and the alpha component as specified (using the same rules as the color components) if an alpha component is present, followed by ")".

For color() - the string "color(" followed by the canonical colorspace ("xyz-d65" for "xyz") in all-lowercase followed by a space, followed by a space separated list of the non-alpha components as specified (numbers serializing as numbers, percentages serializing as percentages, angles serializing as canonicalized angles in degrees, calc() serializing in its simplified form) with no clamping applied, followed by " / " and the alpha component as specified (using the same rules as the color components) if an alpha component is present, followed by ")".

Then, for relative colors, we could have:

The serialization of the declared value of a relative color is:

For rgb(),rgba(),hsl(),hsla() - the string identifying the canonical color function, "rgb" for "rgb()" and "rgba()", "hsl" for "hsl()" and hsla()", in all-lowercase, followed by "(from ", followed by the serialization of the origin color using the rules for serializing nested origin colors, followed by a single space, followed by space separated list of the non-alpha channel arguments as specified (identifiers serializing as identifiers, numbers serializing as numbers, percentages serializing as percentages, angles serializing as canonicalized angles in degrees, calc() serializing in its simplified form), followed by " / " and the alpha component as specified (using the same rules as the color channel arguments) if an alpha component is present, followed by ")".

For hwb(),lab(),lch(),oklab(),oklch() - the string identifying the color function in all-lowercase, followed by "(from ", followed by the serialization of the origin color using the rules for serializing nested origin colors, followed by a single space, followed by space separated list of the non-alpha channel arguments as specified (identifiers serializing as identifiers, numbers serializing as numbers, percentages serializing as percentages, angles serializing as canonicalized angles in degrees, calc() serializing in its simplified form), followed by " / " and the alpha component as specified (using the same rules as the color channel arguments) if an alpha component is present, followed by ")".

For color() - the string "color(from ", followed by the serialization of the origin color using the rules for serializing nested origin colors, followed by a single space, followed by the canonical colorspace ("xyz-d65" for "xyz") in all-lowercase, followed by a single space, followed by space separated list of the non-alpha channel arguments as specified (identifiers serializing as identifiers, numbers serializing as numbers, percentages serializing as percentages, angles serializing as canonicalized angles in degrees, calc() serializing in its simplified form), followed by " / " and the alpha component as specified (using the same rules as the color channel arguments) if an alpha component is present, followed by ")".

No doubt there is some factoring out that can be done to reduce redundancy, but for my initial take, being verbose seemed like the right choice.

In addition to the text, I have a set of examples I was trying to work from that I am including in a details section here

Details

Tablestakes. Given the following HTML snippet:

<div id="example" style="background-color: red;"></div>

The output of following is said to be the "declared value serialization".

document.getElementById("example").style["background-color"]

And the output of following is said to be the "computed value serialization".

window.getComputedStyle(document.getElementById("example"))["background-color"]

Example 1: Basics

<div id="example" style="background-color: lab(from red l a b);"></div>

Result "declared value serialization"

lab(from red l a b)

Result "computed value serialization"

lab(54.290539 80.804947 69.890961)

Notes: Declared value retains written form. Computed value serializes the resolved value.


Example 2: Spelling, whitespace and capitalization

<div id="example" style="background-color: rgBA( fROm red R   g 1 );"></div>

Result "declared value serialization"

rgb(from red r g 1)

Result "computed value serialization"

color(srgb 1 0 1)

Notes: Declared value retains structure, but uses canonicalized spelling and capitalization (I would propose "rgba" -> "rgb", "hsla" -> "hsl", and "xyz" -> "xyz-d65") and one space between items. Computed value serializes the resolved value.


Example 3: color(...)

<div id="example" style="background-color: color(from red srgb r g b);"></div>

Result "declared value serialization"

color(from red srgb r g b)

Result "computed value serialization"

color(srgb 1 0 0)

Notes: Declared value serializes colorspace after origin color like the grammar. Computed value serializes the resolved value.


Example 4: Percentages and angles

<div id="example" style="background-color: oklch(from white 10% c 40deg);"></div>

Result "declared value serialization"

oklch(from white 10% c 40deg)

Result "computed value serialization"

oklch(0.1 0 40)

Notes: Declared value serializes retains use of percentages and angles. Computed value serializes the resolved value, which always canonicalizes to numbers.


Example 5: calc()

<div id="example" style="background-color: rgb(from red calc(r / 2) g calc(30%));"></div>

Result "declared value serialization"

rgb(from red calc(0.5 * r) g calc(30%))

Result "computed value serialization"

color(srgb 0.5 0 0.3)

Notes: Declared value serializes calc() usages in their simplified form. Computed value serializes the resolved value.


Example 6: none

<div id="example" style="background-color: rgb(from red none g b);"></div>

Result "declared value serialization"

rgb(from red none g b)

Result "computed value serialization"

color(srgb none 0 0)

Notes: Declared value serializes retains use of none in channels. Computed value serializes the resolved value, which also serializes the none values.


Example 7: Nested none

<div id="example" style="background-color: hsl(from hsl(none 10% 50%) h s l);"></div>

Result "declared value serialization"

hsl(from hsl(none 10% 50%) h s l)

Result "computed value serialization"

color(srgb 0.55 0.45 0.45)

Notes: Declared value serializes the origin at full fedelity, no conversion to "rgb(140, 115, 115)". Computed value serializes the resolved value.


Example 8: Nested out-of-gamut rgb/hsl

<div id="example" style="background-color: hsl(from hsl(127.9 302% 25.33%) h s l);"></div>

Result "declared value serialization"

hsl(from hsl(127.9 302% 25.33%) h s l)

Result "computed value serialization"

color(srgb -0.511666 1.018266 -0.310225)

Notes: Declared value serializes unclamped origin, no-clamping values to range, no conversion to "rgb(0, 255, 0)". Computed value serializes the resolved value.


Example 9: alpha

<div id="example" style="background-color: rgb(from red r g b / 100%);"></div>

Result "declared value serialization"

rgb(from red r g b / 50%)

Result "computed value serialization"

color(srgb 1 0 0)

Notes: Declared value serializes alpha only if it is specified explicitly in a channel. Computed value serializes the resolved value.


Example 10: currentcolor (NOTE: extra "color" declaration in HTML snippet)

<div id="example" style="background-color: rgb(from currentcolor r g calc(b * 2)); color: blue;"></div>

Result "declared value serialization"

rgb(from currentcolor r g calc(b * 2))

Result "computed value serialization"

color(srgb 0 0 0.5)

Notes: Declared value serializes the same as elsewhere. Computed value serializes the used value, same as if "currentcolor" had been used on its own.

@svgeesus
Copy link
Contributor

Wow, thanks for the very thorough response!

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-color-5] Should colors nested in a parent color function (RCS, color-mix, light-dark, color-contrast) serialize in their most precision saving form, and agreed to the following:

  • RESOLVED: Adopt the proposed text
The full IRC log of that discussion <fantasai> scribenick: fantasai
<fantasai> ChrisL: The nesting of things with color-mix()/light-dark()/ etc. was not fully specified
<fantasai> ChrisL: We discussed some, and we have some proposed replacement text that looks extremely detailed and accurate to me
<fantasai> ChrisL: So I'd like to adopt into the spec
<ChrisL> https://github.com//issues/10328#issuecomment-2135948165
<fantasai> RESOLVED: Adopt the proposed text

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-color-5 Color modification
Projects
Status: Wednesday morning
Development

No branches or pull requests

7 participants