w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.49k stars 660 forks source link

[css-color-4] Lch limits on hue value are a bit too limiting #4703

Closed faceless2 closed 4 years ago

faceless2 commented 4 years ago

The hue value in Lch is wrapped to the range 0..360° at parse time:

If the provided value is negative, or is greater than or equal to 360deg, it is set to the value modulo 360.

...which seems like a very sensible thing to do, until you try to make a gradient between two colors on either side of 0°.

For example, if I tried to make an Lch gradient from purple (eg hue 335°) through red (hue 0°) to orange (eg hue 60°), I have to split this into two segments - from 335° to 359.999°, and from 0° to 60°. Were it not wrapped, I would be able to transition from hue -25° to 60°.

Hue in HSL is not limited at all, which I think is the best option: a gradient from 0° to 180° is different to one from 0° to 900°, despite the start and end colors being identical. If it is to be limited, it should be at least over a range of 720°

Was previously discussed in https://github.com/w3c/csswg-drafts/issues/289

tabatkins commented 4 years ago

Yes, this should definitely use the same language as hsl(), defining an equivalence class over hue angles rather than changing the angle in any way.

jrus commented 4 years ago

Mapping angles into the range 0..360 (or if you like, –180..180) is fine, but gradients should always take the shortest arc.

If someone specifies a gradient from 340° to 30° or whatever, they almost certainly will expect the angle to be ascending, passing through 360° = 0°. A gradient which goes backwards the long way around the circle is almost never going to be desired.

Someone with the vanishingly rare desire to make a gradient that wraps around the circle multiple times can just add additional gradient stops every 120° or something along the way.

Crissov commented 4 years ago

If youʼd specified the gradient from −20° through 30°, Iʼd agree, but with 340° thatʼs an assumption that is not clearly better than the other way around.

jrus commented 4 years ago

It should be common for an author to get their coordinates (including hue angle) from some graphical tool, and exceedingly rare for someone to want a single gradient segment that goes the long way around the hue circle (even less so for the 3x around the hue circle proposed above). Many authors are not even going to even realize that there are many aliased names for the same angle.

In any practical case I can think of where a polar-coordinate CIELAB gradient goes more than halfway around the hue circle, the author is going to want to add additional stops carefully choosing lightness/chroma anyway, to avoid unpleasant hard clipping when such a gradient inevitably goes out of gamut.

If someone really wants to go the long way around, it’s an easy workaround to add additional gradient stops. But not defaulting to the short arc around the circle is going to lead to a lot of confusion.

In particular imagine the cases where the coordinates come from some named variable. Two nearly identical named colors with a gradient that goes 350° around the hue circle is going to be a serious head-scratcher.

Also imagine the case where a graphical tool is being used to choose the whole gradient. Implementors of such tools are not going to properly handle the necessary fiddly edge cases in their code (need to output gradient stops with the appropriate multiple of 360° added to each stop based on the value of the previous gradient stop, and all of the gradient stops potentially need to be re-adjusted after every change, to guarantee that each gradient segment goes the desired direction), leading to confusing results for end-users of such tools.

Source of the above feedback: like 15 years occasionally trying to pick color schemes for statistical graphics, maps, software, etc., and lots of experience using gradients in raster image editing.

faceless2 commented 4 years ago

What about a gradient from 0 to 180? Or 180° to 0°? If you clamp angles to any range, at some point you are going to run into this situation and will need to make an arbitrary decision on which way to iterate.

Same for the situation where you want to create a continuous color range from 0..360°, which is not an unusual thing to want. Yes, you can split it into two segments, but why should you have to?

If you don't clamp the values, you don't have to do either of these. Given there's also no technical reason this has to be done, and that it's not currently done for HSL which is already being used in the wild, I'm not sure I see the benefit in taking a different approach for Lch.

jrus commented 4 years ago

What about a gradient from 0 to 180? Or 180° to 0°?

Pick either way, doesn’t really matter. People should not generally be making a single gradient segment close to this wide, except for a toy tech demo.

If you really care you can add an optional parameter which specifies whether to go clockwise or anticlockwise around the hue circle, with the default being to take the shorter arc. But I wouldn’t really recommend it; it adds complexity to the spec to slightly simplify an extremely rare use case.

continuous color range from 0..360°, which is not an unusual thing to want

It is not a common thing to want in any practical use on a website or infographic or map ...

It might be desirable in someone’s toy tech demo?

But trying to do this in terms of CIELAB LCh coordinates is generally going to lead to poor results, because the gamut is not big enough to accommodate all hues to the same chroma, so what you’ll end up with is a bunch of artifacts where the gamut clipping boundaries were found.

To get decent looking results you’ll want to pick the chroma of intermediate waypoints to avoid clipping.

I urge you to find a map, data display, element on a webpage that needs a gradient, image editing use case, ...., and actually practically experiment with making gradients in LCh coordinates. If you just idly speculate you’re not going to have a fair idea what practical needs are.

HSL

HSL (a) leads to horrible results in basically all contexts, so is a poor inspiration for anything, because (b) it is one of the simplest possible ways of squishing a cube to fit inside a cylinder, without any reference to human perception, only geometric convenience (here’s a diagram I made a decade ago which shows this). The result of that is that the cylinder is the gamut, so if you interpolate between any two points in cylindrical coordinates you will remain in the gamut.

If you don't clamp the values, you don't have to do either of these.

Yes, but what you will get is extreme confusion among non-technical authors and lots of poor results.

tabatkins commented 4 years ago

Gradients currently don't care about the color angle at all; they convert the color into RGB and then linearly interpolate the RGB components.

When we do allow specifying interpolation in gradients, I expect it'll match how gradients are already defined to interpolate their angles - it interpolates the angles numerically exactly as you specify, so 10deg to 350deg goes the long way around the circle.

jrus commented 4 years ago

Gradients currently don't care about the color angle at all; they convert the color into RGB and then linearly interpolate the RGB components.

Okay fine, but this whole discussion is premised on “if I tried to make an Lch gradient”. If you discard that premise, then there’s no reason to care about whether or when hue angles are canonicalized.

When we do allow specifying interpolation in gradients, I expect it'll match how gradients are already defined to interpolate their angles - it interpolates the angles numerically exactly as you specify, so 10deg to 350deg goes the long way around the circle.

This is not an author-friendly behavior, is what I am saying.

Edit: I am also a bit confused; what do you mean “how gradients are already defined to interpolate their angles”? You just said gradients interpolate linearly in (gamma-encoded?) RGB space. Which is it?

If you are talking about so-called “conic gradients” then that behavior is unrelated and irrelevant to the discussion of interpolating hues. A “conic gradient” necessarily has stops along the way between 0°..360° around the circle, according to the spec conventionally measuring from the negative y axis = 0° with positive angles in the clockwise direction in a left-handed coordinate system. The only choice involved when specifying a conic gradient is where to put intermediate gradient stops in that angle range, but the two ends are permanently fixed. [Arguably this is not always the most convenient user interface for specifying “conic gradients” and someone might prefer some alternative, but that’s off topic here.]

tabatkins commented 4 years ago

Gradients have angles for other reasons than colors. ^_^ linear-gradient(50deg, red, white), for example, which angles the linear gradient to 50deg CW from vertical.

Nobody implements the fancier gradient interpolation yet (they just do a cross-fade between the two endpoints), but when they do, it'll work as I described/linked.

svgeesus commented 4 years ago

Yes, this should definitely use the same language as hsl(), defining an equivalence class over hue angles rather than changing the angle in any way.

On my to-do list

jrus commented 4 years ago

Gradients have angles for other reasons than colors. ^_^ linear-gradient(50deg, red, white), for example, which angles the linear gradient to 50deg CW from vertical.

But this is a single angle, not a way of interpolating between angles ...


This is a bit off topic here, but I wouldn’t know where to raise it..

If you really want to make a more capable gradient capability, the most useful addition in terms of raw power/flexibility isn’t changing the color space where interpolation happens, but instead adding some facility for non-linear interpolation on each segment. For instance, allowing arbitrary cubic (or even just quadratic) polynomial segments would give the tool dramatically more flexibility for a given number of specified colors, comparable to the difference between polylines vs. Bézier splines for specifying 2D shapes.

svgeesus commented 4 years ago

This is a bit off topic here, but I wouldn’t know where to raise it

It will get lost here. Raised for you https://github.com/w3c/csswg-drafts/issues/4754

triple-underscore commented 4 years ago

Along with the commit https://github.com/w3c/csswg-drafts/commit/ef3269f022324d4cb0203085e9d934716cfa0f38, still needs an edit for css-color-4 § Changes, that the following sentence should be removed:

Clarify hue in LCH is modulo 360deg

svgeesus commented 4 years ago

Oh, good catch.

svgeesus commented 4 years ago

Changing to

Clarify hue in LCH is modulo 360deg (change now reverted)

because a draft was published with that change.