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] Clarification on how channel keywords with multiple specified types work #7876

Closed
weinig opened this issue Oct 12, 2022 · 28 comments

Comments

@weinig
Copy link

weinig commented Oct 12, 2022

In the relative color syntax section of CSS Color 5, some of the channel keywords specify more than one type. For instance, the channel color l for the lab() relative color syntax states:

(http://w3c.github.io/csswg-drafts/css-color-5/##valdef-lab-l)

I am unclear on what this means with respect to using l in a calc() expression. For instance, in the following contrived case:

lab(from purple calc(l + 2) a b / 100%)

should I be interpreting l as a <percentage> or a <number>? (or am I missing something fundamental about calc that makes this not an issue?)

Edit: linked to stable drafts, and corrected to CSS Color 5, not 4

@svgeesus
Copy link
Contributor

I think this is a case of the editors not appreciating the impact of other changes. At the time it was introduced to CSS, CIE L was a <number> in [0..100]. Then it became a mandatory <percentage> for a long time, and relatively recently became <percentage> | <number>. You are right that this means RCS no longer defines what you actually calculate with for calc() inside RCS.

Following the advice that @LeaVerou gives in her CSS Variables talks:

converting a number to a value with units is easy, calc(var(--foo) * 100%)
converting a value with units to a number requires things like calc(var(--foo) / 1%) which don't exist yet
so make your custom properties be pure data with no units

Then I suggest resolving this such that <percentage> gets resolved to <number> because that will be the most convenient thing to manipulate in calc()

So

--base: oklab(50% 0.2 -12%);
--darker: oklab(from var(--base) calc(l * 0.8) a b);

Using the percent reference ranges for oklab the value of --base becomes oklab(0.5 0.2 -0.048) and the value of --darker becomes oklab(0.4 0.2 -0.048)'

Converting the <percentage> values to <number> is also consistent with how those values are serialized

@svgeesus svgeesus added the css-color-5 Color modification label Oct 13, 2022
@svgeesus svgeesus changed the title [css-color-4] Clarification on how channel keywords with multiple specified types work [css-color-5] Clarification on how channel keywords with multiple specified types work Oct 13, 2022
@LeaVerou
Copy link
Member

LeaVerou commented Oct 13, 2022

I think this is a case of the editors not appreciating the impact of other changes. At the time it was introduced to CSS, CIE L was a <number> in [0..100]. Then it became a mandatory <percentage> for a long time, and relatively recently became <percentage> | <number>. You are right that this means RCS no longer defines what you actually calculate with for calc() inside RCS.

Yeah, this appears to have been some sort of mass change.

Following the advice that @LeaVerou gives in her CSS Variables talks:

converting a number to a value with units is easy, calc(var(--foo) * 100%)
converting a value with units to a number requires things like calc(var(--foo) / 1%) which don't exist yet
so make your custom properties be pure data with no units

Then I suggest resolving this such that <percentage> gets resolved to <number> because that will be the most convenient thing to manipulate in calc()

As a general principle, we should not be designing the language long-term based on short-term UA limitations.
However, I don't see anything wrong with erring on <number> whenever possible, to limit the number of conversions needed. That could be a general principle for RCS.

@svgeesus
Copy link
Contributor

Another point is that forcing users to convert from percentage form themselves requires them to memorize or look up the percentage reference ranges for each component. 100% maps to 100 for CIE L, 1.0 for Oklab L, 125 for CIE a, 0.4 for Oklab a. Having the implementation do this will reduce errors.

@LeaVerou
Copy link
Member

Another point is that forcing users to convert from percentage form themselves requires them to memorize or look up the percentage reference ranges for each component. 100% maps to 100 for CIE L, 1.0 for Oklab L, 125 for CIE a, 0.4 for Oklab a. Having the implementation do this will reduce errors.

Correct, though it may make operations easier, e.g. calc(l + 20%) becomes more meaningful across color spaces.

@svgeesus
Copy link
Contributor

calc(l * 1.2) seems easy enough

@LeaVerou
Copy link
Member

Yes, but they do different things. Both are useful.

@LeaVerou
Copy link
Member

Perhaps we can make it <number-percentage> so that math with percentages still works? @tabatkins is that a thing? Can it be?

@svgeesus
Copy link
Contributor

There is no more <number-percentage>, it was removed.

@tabatkins
Copy link
Member

Yeah, number-percentage doesn't exist (and can't - it breaks the typing system). We do need to decide on one or the other.

I'm fine with making it number.

@svgeesus
Copy link
Contributor

So:

No-one has argued for <percentage> only, and it is agreed that the channel keywords need to resolve to a single type.

In the interest of having concrete text to discuss on the call today, I am going to do the edits to make all channel keywords resolve to <number>.

@svgeesus
Copy link
Contributor

For clarity, this only applies to channel keywords which are currently specified as multiple types, like <percentage> or <number>, as reported by @weinig.

Channel keywords which use other (single) types, like <angle> for hues or <percentage> for the saturation and lightness of HSL, are not affected and they don't change to <number>.

@svgeesus
Copy link
Contributor

svgeesus commented Jan 11, 2023

Ok so now:

  • all channel keywords specify a single type
  • examples updated to consistently use that type
  • added an example of resolving specified percentage values to numbers

In particular, I added an explicit:

Except as specified for individual color functions, (for example, hues return an <angle>), the channel keywords return a <number>; if they were originally specified as a <percentage>, that percentage is resolved to a <number>.

@LeaVerou
Copy link
Member

Channel keywords which use other (single) types, like <angle> for hues

Erm, hue is <number> | <angle> (and <angle> isn't actually used much at all in the wild)

@tabatkins
Copy link
Member

Again, this is not about what types are valid for input, but rather what type the channel keywords are as output - those have to be a single known type or else the type of a calculation is completely undefined.

@tabatkins
Copy link
Member

@svgeesus in hwb() you're still specifying the w and b keywords as percentages. Otherwise the edits look good.

@LeaVerou
Copy link
Member

Again, this is not about what types are valid for input, but rather what type the channel keywords are as output - those have to be a single known type or else the type of a calculation is completely undefined.

I’m aware, but the rule we are discussing is "RCS keywords return <number> if <number> is one of the accepted input types for the component".

@svgeesus
Copy link
Contributor

svgeesus commented Jan 11, 2023

in hwb() you're still specifying the w and b keywords as percentages.

Yes, because in CSS Color 4

The syntax of the hwb() function is:

hwb() = hwb( [<hue> | none] [<percentage> | none] [<percentage> | none] [ / [<alpha-value> | none] ]?

and

The second argument specifies the amount of white to mix in, as a percentage from 0% (no whiteness) to 100% (full whiteness). Similarly, the third argument specifies the amount of black to mix in, also from 0% (no blackness) to 100% (full blackness).

So percentage is required and number is not allowed. Should that be changed?
So numbers are not accepted, only percentages. Same as the non-hue components in HSL.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed channel keywords, and agreed to the following:

  • RESOLVED: keywords with multiple specified types result in number
The full IRC log of that discussion <fantasai> Topic: channel keywords
<fantasai> github: https://github.com//issues/7876
<fantasai> chris: main issue raised was that the keywords could have two possible types, doesn't work
<fantasai> chris: either number or percentage
<fantasai> chris: went with number to be consistent with serialization
<fantasai> TabAtkins: nit, you accidentaly did percentage for wmb, but otherwise it's great
<fantasai> chris: It's because color-4 it only takes a percentage
<fantasai> TabAtkins: Ah, in that case it's completely fine
<fantasai> chris: any other comments?
<fantasai> chris: anyone need more time?
<fantasai> ntim: didn't have a chance to look at it
<fantasai> lea: does that mean for rgb they resolve to 0-255 range?
<fantasai> chris: yes, but remember it's 0.0 to 255.0 so you don't lose precision
<fantasai> lea: but inconsistent with rgb models
<fantasai> chris: because it was invented poorly
<fantasai> lea: I agree but does that mean we don't need relative color syntax for rgb?
<Rossen_> q?
<fantasai> chris: I have a lot of trouble coming up with use cases
<Rossen_> ack ntim
<fantasai> chris: I haven't found a good example
<fantasai> lea: I think it's primarily for ocmpletenes, but maybe we should not do it just for completeness
<fantasai> lea: restrict to color()?
<fantasai> chris: I wouldn't go that far
<fantasai> chris: let's resolve this and deal in other issues?
<fantasai> chris: get consensus on going to number?
<fantasai> Rossen_: So do we have enough consensus?
<lea> s/ocmpletenes, but maybe we should not do it/completeness, but we don't generally do things/
<fantasai> lea: consensus is about every component that's "number or something else" resolves to number?
<fantasai> lea: so hues resolve to numbers?
<fantasai> chris: yeah, all examples treat hues as number
<fantasai> chris: so I think most ppl are treating as numbers
<fantasai> lea: yes, that's what authors do
<fantasai> [...]
<argyle> +1 to number
<fantasai> chris: Proposal, keywords with multiple specified types result in number
<fantasai> Rossen_: Any additional feedback or objections?
<fantasai> jensimmons: this is fine with us from Apple
<fantasai> RESOLVED: keywords with multiple specified types result in number

@romainmenke
Copy link
Member

@tabatkins @svgeesus Maybe this issue should be re-opened.
The WPT tests were never updated to match the spec change and Safari shipped relative color syntax in 16.4 based on an old version of the specification.

@devongovett
Copy link
Contributor

devongovett commented Apr 10, 2023

This is an unfortunate breaking change for folks using pre-processors that already support this syntax. It means that something like lch(from indianred calc(l + 10%) c h) isn't possible anymore. You have to write it as lch(from indianred calc(l + 10) c h) which seems less intuitive, especially since you can normally write the l channel as a percentage (e.g. lch(63.9252% 51.2776 26.8448)).

It's even less intuitive depending on the channel, since each one has a different percentage basis. For example, with oklch you'd have to do calc(l + .1) instead of calc(l + 10) for lch because the number range is [0, 1] instead of [0, 100]. This makes it harder to intuitively "lighten a color by 10%".

Why can't we have it be a number or percentage depending on which one type checks? That's how I had implemented it in Lightning CSS - it first tries the calculation where l is a number, and if that fails, tries it as a percentage.

@svgeesus
Copy link
Contributor

Why can't we have it be a number or percentage depending on which one type checks?

CSS used to have a <number-or-percentage> type but it no longer exists. @tabatkins said that it can't and could explain better than I why that had to be removed.

For example, with oklch you'd have to do calc(l + .1) instead of calc(l + 10) for lch because the number range is [0, 1] instead of [0, 100].

For 10% lighter I would probably do calc(l * 1.1) in both cases.

But I do see your point, and sorry for the breaking change.

@devongovett
Copy link
Contributor

devongovett commented Apr 13, 2023

Another question about this – WPT has this test: https://github.com/web-platform-tests/wpt/blob/7c0ad5dcce2aa7a073b6f09b99213325b0c4574a/css/css-color/parsing/color-computed-relative-color.html#L90

test_computed_value(`color`, `rgb(from rebeccapurple b alpha r / g)`, `rgba(153, 255, 102, 0.2)`);

This implies either that alpha values are stored in 0-255 range rather than 0-1 as specified by <alpha-value>, or that they are somehow converted. Here the alpha becomes the green channel (1 becomes 255), and the green channel becomes the alpha channel (51 becomes 0.2 = 51 / 255).

Is this the intentional behavior or should the output actually be rgba(153, 1, 102, 1)?

@romainmenke
Copy link
Member

romainmenke commented Apr 13, 2023

The WPT tests were never updated after the changes from mixed types to percentages or numbers.

should the output actually be rgba(153, 1, 102, 1)

That should be correct

@devongovett
Copy link
Contributor

This seems quite unintuitive vs treating all channels consistently as percentages, because you have to account for different ranges yourself in any calculations. The behavior of something like lch(from lch(70% 45 30) alpha c h / l) and oklch(from oklch(70% 45 30) alpha c h / l) is very different. With lch, the l channel becomes 1%, but with oklch it is 100% due to the different ranges. Treating channels as percentages in calculations would make this a lot more consistent.

Also given that Safari 16.4 already shipped, and matches WPT as it is now, would it not be a breaking change for them also to switch to this new interpretation?

@romainmenke
Copy link
Member

romainmenke commented Apr 13, 2023

I don't know why <number> was favored over <percentage> but I also find it very hard to work with.

Components as specified can have these types in RCS :

  • <number>
  • <percentage>
  • <angle>

It is much harder to effectively write calc expressions for components because of this.

If <percentage> was favored it could be limited to :

  • <percentage> (everything not hue)
  • <angle> (only hue)

even hue could theoretically be mapped from percentage to degrees [0% 100%] -> [0deg 360deg]

It would also be much more intuitive because + 10% is "a little bit more" in any notation and for any component. Whereas + 1 could be out of gamut or barely noticeable.

@romainmenke
Copy link
Member

romainmenke commented Apr 13, 2023

A downside of percentages is that it limits the possible operations.

For example multiplying channels by alpha becomes impossible because percentages can not be multiplied.

I think that what I actually want is to be able to use numbers but have all number ranges have the same scales, which is just not how the different number notations work.

@devongovett
Copy link
Contributor

also multiplying by 1.1 and adding 10% are different things.

lch(from slateblue calc(l * 1.1) c h) => lch(49.0282% 65.7776 296.794)
lch(from slateblue calc(l + 10) c h) => lch(54.5711% 65.7776 296.794)

The first is 110% of the original l value. The second adds 10% of the overall range of the l channel.

@svgeesus
Copy link
Contributor

The WPT tests were never updated after the changes from mixed types to percentages or numbers.

They have now all been updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants