[csswg-drafts] [css-images-4] Clarifying CSS gradient rendering for edge cases involving "longer hue" interpolation (#11381)

jfkthame has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-images-4] Clarifying CSS gradient rendering for edge cases involving "longer hue" interpolation ==
I am seeing differences between Firefox, Chrome and Safari regarding how gradients with the "longer hue" interpolation method are rendered. I think I understand what the spec expects, but none of the browsers tested get this right, so I'd like to confirm my interpretation — and perhaps some clarification or additional examples may be needed in the spec.

Testcase: https://codepen.io/jfkthame/pen/azopZOx

(Note that there have been some recent patches to gradient rendering in Firefox. The description below refers to current Nightly builds; older versions have some different and less self-consistent results.)

This renders a number of 100px-wide gradient swatches, all of them defined as linear-gradient(to right in hsl longer hue, ...) with two color stops, red and blue, at a variety of positions. (The red and blue tick-marks above and below each swatch indicate the positions of the color stops defining the gradient.)

Results I'm seeing in Firefox Nightly, Chrome Canari, and Safari Tech Preview:
<img width="746" alt="image" src="https://github.com/user-attachments/assets/7c7661b1-9185-418c-8475-6def180120e4" />

The simplest case is (a), with the red stop at the left edge and the blue stop at the right. No issues here: we simply render a gradient from red to blue, going "the long way around" the HSL wheel.

What happens if the first and last stops defining the gradient do not cover the full extent of the area to be rendered? In (b), the blue stop is in the middle, so what happens in the right-hand half of the swatch? According to https://drafts.csswg.org/css-images-4/#coloring-gradient-line,

> Before the first color stop, the gradient line is the color of the first color stop, and after the last color stop, the gradient line is the color of the last color stop.

which seems to imply that the right-hand half of (b) should be solid blue. However, all three tested browsers actually paint a further gradient here, with a full cycle of the HSL wheel from the blue stop at 50px all the way around to blue again at the 100px edge. I presume the browsers are adding an "implicit" blue color stop at the right-hand edge, and then performing a longer-hue interpolation between the actual blue stop at 50px and that implicit one. I don't see any justification in the spec for this behavior: I think it's a bug, but given that all three browsers behave this way, maybe I'm missing something?

Example (c) is similar: here, both the red and blue color stops are moved in from the edges of the swatch, so I think the expected rendering should have 20px of solid red at the left, and 25px of blue at the right, with the longer-hue gradient from red to blue in between. But all three browsers also render a complete 360° gradient in both the left and right portions, outside the defined color stops. Bug?

(Aside: the description of linear-gradient on MDN https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient

> By default, if there is no color with a 0% stop, the first color declared will be at that point. Similarly, the last color will continue to the 100% mark, or be at the 100% mark if no length has been declared on that last stop.

may be a bit misleading here. That first sentence might be understood to imply creating an extra stop at 0%, duplicating the first declared color, rather than simply painting that color from its stop position back to 0%; and analogously, although the wording is less ambiguous, an implementer might think they should duplicate the last declared color at 100%. And that would indeed result in the observed behavior. But if my understanding of the actually-expected behavior is correct, I'll suggest rewording this for better clarity.)

Interestingly, in example (d), where the blue stop is moved beyond the right-hand end of the swatch, Chrome and Safari suddenly switch their behavior and now do not use a gradient in the area to the left of the red stop: they turn this into solid color. (Firefox, on the other hand, continues to apply a gradient there, just like in the preceding examples.) I think the Chrome/Safari behavior here is what the spec requires, but I don't understand why they only do this correctly once the blue color stop is beyond the end of the rendered area.

Example (e) shows the equivalent case with the red stop placed beyond the left edge: now, Chrome and Safari render a solid color for the area beyond the blue stop, while Firefox still paints a gradient there.

Examples (f) and (g) serve to illustrate the abrupt change in behavior in Chrome and Safari. The two stops are just shifted right by 1px in (g) compared to (f). In Firefox, this just results in a barely-perceptible shift in the gradient; but in Chrome and Safari it drastically changes the result.

QUESTION 1: Am I right in thinking that any area before the first color stop or after the last color stop should simply render as a solid color, or is longer-hue interpolation expected to somehow "project" into these areas (as the browsers currently do, though not entirely consistently)?

The second group of swatches, (h) through (l), have the two color stops at the exact same position. My reading of the spec is that the correct rendering would have solid red to the left of the stop position, and solid blue to the right. But all three browsers actually render a complete 360° gradient: to the right of the color stops, they render blue-to-blue (all the way around the hue circle), and to the left they render red-to-red (all the way around). For (h) through (j), this result is consistent across all the browsers, and appears to result from adding "implicit stops" at the left and right edges of the swatch, duplicating the color of the nearest real stop.

In example (k), the position of the red and blue stops has been moved beyond the right-hand edge of the swatch. In Firefox, this "stretches" the gradient to the right; it still begins with red at the left edge, but the visible gradient no longer cycles all the way back to red because the stop is outside the swatch area. In Chrome and Safari, on the other hand, the end of the 360° gradient seems to be "clamped" to the right-hand side of the swatch, so (k) looks identical to (j). I can't see any justification for that behavior — though I think the browsers are wrong to be rendering a gradient at all in this case.

Finally, in (l) the stops have been moved before the left-hand edge of the swatch. Again, Firefox stretches the gradient; Chrome "clamps" the left edge, so that (l) is identical to (h). Safari renders this example the same as (j) and (k), which I think is a bug.

QUESTION 2: Is a longer-hue linear gradient with stops at a single position a special case that should render some kind of gradient (as all browsers currently do, regardless of where the stops are positioned), or is that just an implementation bug?

(Note that there are a couple of existing WPT tests that expect this behavior; e.g. see http://wpt.live/css/css-images/gradient/gradient-single-stop-longer-hue-hsl.html. I'm suggesting this may be wrong. Does the web depend on it?)


Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/11381 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Monday, 16 December 2024 13:41:16 UTC