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] Define the behavior of missing channels in the relative color syntax #7771

Closed
mirisuzanne opened this issue Sep 20, 2022 · 12 comments
Assignees
Labels
Closed Accepted as Obvious Bugfix Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. css-color-5 Color modification

Comments

@mirisuzanne
Copy link
Contributor

The relative color syntax in level 5 allows existing colors to be modified using the color functions. For example, we can start with a base color green, and manipulate it in lch space, by performing calculations on the l, c, and h channels:

/* example from the spec */
html { --color: green; }
.foo {
  --darker-accent: lch(from var(--color) calc(l / 2) c h);
}

This syntax predates the specification of missing color components in level 4, represented by the none keyword. While the addition of none to the function syntax has been ported into level 5, it's not clear how this should interact with the relative color syntax.

What should happen when the base color has a missing component, and the relative syntax performs a calculation on that channel?

html { --color: lch(0.5 0.5 none); }
.foo {
  --complement: lch(from var(--color) l c calc(h + 180deg));
}

I see three basic options:

  1. Invalid operation. This seems like the 'default' if we don't specify something different, since none is clearly not a valid number for calc(). However, I think it's not a very useful behavior, and we should avoid it if possible. In general, it seems bad to say 'some valid colors will simply break if you adjust them' - especially since none can result from internal CSS operations.

  2. Calculate with 0 in place of none. This may be the most obvious choice, since it matches the behavior in other cases where we need a number value for none. But the results aren't necessarily useful, especially when the channel is truly powerless.

  3. The result is always none. This was proposed by @nex3 as it maintains the intent of none to represent powerless or unimportant channels. Rotating a missing/powerless hue results in a hue that is still missing/powerless.

I'd propose that we go with option 3, but I could see arguments for the other approaches. Thoughts?

@mirisuzanne mirisuzanne added the css-color-5 Color modification label Sep 20, 2022
@svgeesus
Copy link
Contributor

(just noting that I will respond on this when I get back to work on Monday)

@tabatkins
Copy link
Member

While this needs to be copied over to L5 for clarity, the intention was option 2:

For all other purposes, a missing component behaves as a zero value, in the appropriate unit for that component: 0, 0%, or 0deg. This includes rendering the color directly, converting it to another color space, performing computations on the color component values, etc.

@LeaVerou
Copy link
Member

LeaVerou commented Sep 21, 2022

While this needs to be copied over to L5 for clarity, the intention was option 2:

For all other purposes, a missing component behaves as a zero value, in the appropriate unit for that component: 0, 0%, or 0deg. This includes rendering the color directly, converting it to another color space, performing computations on the color component values, etc.

This intention refers to general color manipulation. It was never considered directly how none should behave with RCS, which is why I asked Mia to create a new issue.

I see three basic options:

  1. Invalid operation. This seems like the 'default' if we don't specify something different, since none is clearly not a valid number for calc(). However, I think it's not a very useful behavior, and we should avoid it if possible. In general, it seems bad to say 'some valid colors will simply break if you adjust them' - especially since none can result from internal CSS operations.
  2. Calculate with 0 in place of none. This may be the most obvious choice, since it matches the behavior in other cases where we need a number value for none. But the results aren't necessarily useful, especially when the channel is truly powerless.
  3. The result is always none. This was proposed by @nex3 as it maintains the intent of none to represent powerless or unimportant channels. Rotating a missing/powerless hue results in a hue that is still missing/powerless.

I'd propose that we go with option 3, but I could see arguments for the other approaches. Thoughts?

At first glance I agree that option 3 seems the most reasonable and more in line with the principle of least surprise. Option 2 (the current behavior) is not great. E.g. I think it's pretty surprising to end up with some sort of cyan for every complement of a color that has a none hue! Just as weird as treating achromatic colors as red.

@tabatkins
Copy link
Member

I figured that author-level computations on the color channels and UA-level computations on the color channels would operate under the same rules. The only way to implicitly produce missing components is to do color-space conversion and get a result with powerless components, and the only effect of missing components is to change how interpolation works.

Importantly, doing color-space conversion on a color that already has missing components treats those components as zero, rather than specially handling them. This has implications for RCS; if we go with option 3, then if the starting color has missing components, it'll only matter if it's already in the same color-space as the RCS function; if conversion occurs, it'll treat the missing components as zero and produce a color accordingly. (Which might have missing components due to powerlessness, but that's independent of whether the starting color had missing components or not.)

I don't think it's a good idea to have the behavior care about whether the starting color is in the same color-space or not.

@mirisuzanne
Copy link
Contributor Author

mirisuzanne commented Sep 22, 2022

The relative color syntax use-cases are very similar to interpolation use-cases (where we also define a way for none to be maintained across conversions). I don't think it's terribly absurd to think these two features would have a lot in common. That said, I think it's likely that adjusting-none-values will only have accidental use-cases as part of an automated system - and these options may often have similar results: the adjusted value remains powerless whether or not it is still missing.

@weinig
Copy link

weinig commented Sep 22, 2022

We had a previous conversation on this topic in #6920.

@tabatkins
Copy link
Member

Thanks for the pointer! So the text I quoted was explicitly about this very issue. ^_^

@tabatkins
Copy link
Member

@mirisuzanne Note that the effects of missing channels in interpolation (defined in https://drafts.csswg.org/css-color-4/#interpolation-missing) aren't similar to what was suggested in this thread. In interpolation you only get a carried-thru missing channel if both the "to" and "from" colors carry-thru the same missing channel to the interpolation space; otherwise you take the other side's channel value.

That sort of handling can't be done with RCS anyway; if you're mixing two colors by hand with it only one of them is semantically a <color> in the CSS; the other would have to just be literal channel numbers.

(Note that color-mix() uses the same interpolation behavior as transitions/etc, so when mixing colors with the function designed for color mixing, you'll get the desired behavior.)

@svgeesus
Copy link
Contributor

So it sounds like we should

  1. use the same concept of analogous components from Interpolating with Missing Components. This will preserve missing values when the origin color gets converted to the RCS colorspace.
  2. Define the result of calc(none) to be none rather than calc(0).

Does that sound right?

@tabatkins
Copy link
Member

Yes for 1, no for 2. We should do the carry-forward of missing channels to analogous channels in the RCS colorspace, but then treat those missing channels as 0 in the calculation part.

This matches what we do by default - we only carry-forward missing-ness if we know that two components serve essentially identical purposes in both the starting and ending color space. In all other cases (such as a missing hue converting into an RGB space) we just treat it as zero; in RCS we have no way of knowing what the author is doing with the channel values to produce the final color, so we have to be conservative and treat this like the general case.

@svgeesus
Copy link
Contributor

svgeesus commented Feb 1, 2023

@mirisuzanne @tabatkins please have a look at (the end of) 3. Relative Color Syntax where I added explicit handling of missing values (using analogous components), added a worked example including a gradient, and also clarified about calc(none) === calc(0).

@mirisuzanne
Copy link
Contributor Author

@svgeesus That looks great to me.

@svgeesus svgeesus added Closed Accepted as Obvious Bugfix Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. labels Feb 3, 2023
@svgeesus svgeesus closed this as completed Feb 3, 2023
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Nov 18, 2023
https://www.w3.org/TR/css-color-4/#lab-to-lch
"""
For extremely small values of a and b (near-zero Chroma), although the
visual color does not change from being on the neutral axis, small
changes to the values can result in the reported hue angle swinging
about wildly and being essentially random. In CSS, this means the hue is
powerless, and treated as missing when converted into LCH or Oklch
"""

w3c/csswg-drafts#7771

Bug: 1497325
Change-Id: I749d2beb1985c609f454cc47affe612ef72ccf2d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed Accepted as Obvious Bugfix Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. css-color-5 Color modification
Projects
None yet
Development

No branches or pull requests

5 participants