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-images-4] Consider allowing gradients to include a color space for interpolation to take place in #5833

Closed
samweinig opened this issue Jan 4, 2021 · 19 comments

Comments

@samweinig
Copy link

Currently, all gradient interpolation takes place in pre-multiplied sRGB space. Now that we have the some predefined color spaces from css-color (https://drafts.csswg.org/css-color-4/#predefined) it would be nice to allow gradients to use those color spaces to produce nicer looking results (I have heard Lab is a color space people particular like using for gradients due to the perceptually uniformity characteristics).

One potential way to introduce this would be to all the color space to be specified as a new optional first parameter to the gradient functions.

@svgeesus
Copy link
Contributor

svgeesus commented Jan 5, 2021

Thank you. This gap in functionality is one I have been meaning to address once I have a concrete syntax proposal You are right though that at least raising it as an issue is the first step!

@svgeesus
Copy link
Contributor

svgeesus commented Jan 5, 2021

And yes, both Lab and LCH are useful here. Both are (approximately) perceptually uniform; LCH is also chroma-preserving so a gradient between two very different, saturated hues will go around the hue circle rather than in a straight line, which will get greyish near the middle.

Both options avoid the un-necessary darkening that happens when calculations are done directly on the gamma-encoded RGB values rather than converting them to linear-light first.

CSS Color 4 has a section on color interpolation which can be used as a basis (and allows an individual CSS feature to pick a default colorspace for a given operation, as well as encouraging ssntax so authors can pick other colorspaces). In particular it defines interpolating with alpha, and hue interpolation for polar spaces with a hue angle. And unlike the legacy parts of CSS, CSS Color 4 does not make assumptions like "all colors are sRGB".

As a first step, in the new year I want to make some examples with pairs of colors interpolated in various spaces, so people can see the practical effect of doing so.

@sobotka

This comment was marked as off-topic.

@Artoria2e5
Copy link

Artoria2e5 commented Feb 26, 2021

I think everyone here is aware of the SVG color-interpolation property. Having such a setting that alters the default interpolation is helpful too, although not as versatile as directly making it a part of the gradient properties.

Re "muddle": CSS doesn't care what the colorspace is intended to model. It just needs know a space and a way to draw a line through the space.

PS: the markdown is a bit broken in the color-4 interpolation…


I think I am going to straw a proposal here. Here goes.

Types and global properties (css-color?)

<interpolation-space> = <colorspace> | default
<interpolation-hue> = shorter | longer | increasing | decreasing | specified
<interpolation> = <interpolation-space> [<interpolation-hue>]? | linearRGB
  • Property color-interpolation has value <interpolation>. It defaults to default. It defines how any blending is done by default.
    • A value of linearRGB indicates that color interpolation occurs in a linear sRGB color space. This also gives SVG compatibility.
  • Property color-interpolation-space has value <interpolation-space>. It defaults to default.
    • A value of <colorspace> indicates a color space as in css-color-5.1
    • A value of default specifies the behavior in css-color-4 section 13, where Lab is used except for colors specified in a legacy syntax, which would be interpreted in sRGB.
  • Property color-interpolation-hue has value <interpolation-hue>. It defaults to some weird empty value that acts like shorter. It controls the hue interpolation mode as in css-color-4 section 13.

1 Maybe <ident> | <dashed-ident> for more choices, but then we need a linear/gamma switch for the RGB ones. Moving "linearRGB" to the bigger interpolation thing is in consideration of that.

Gradient properties (css-image)

Maybe just add a , [<interpolation>]? after the position specification. I don't know.

@faceless2
Copy link

Previous discussion on this: #4647

I'm all for this, of course, and have been mulling it over as we implemented it.

  • agree with @Artoria2e5 that we certainly need some sort of general interpolation property (inheritable), and that - to allow people to use different interpolation strategies on the same element - we should probably add an optional argument to each gradient too. In particular, mask-image: radial-gradient(...) needs the option of using linearRGB even if the other parts of the element use another strategy.
  • there are likely existing gradients with colors defined in HSL; these have to continue to interpolate in sRGB by default.

So I'm broadly in agreement with the above proposal, and in particular think we should definitely extend the existing color-interpolation property to make it apply universally, not just to SVG. I would suggest adding two new values to that property:

color-interpolation: auto | srgb | linearrgb | match | lch

  • "auto" is currently defined as "the user agent can choose either the sRGB or linearRGB spaces for color interpolation. This option indicates that the author doesn't require that color interpolation occur in a particular color space". I'd propose changing this to be be "Choose "srgb" for colors specified as sRGB or HSL, and otherwise act as "match".
  • "match" means use the color-space of the colors being interpolated. So two sRGB colors interpolate in sRGB, two HSL colors in HSL, two LCH colors in LCH. If the colors have different spaces, the interpolation is in LCH.
  • "lch", "srgb", "linearrgb" - interpolation always happens in that space.

The "match if we can, lch if we can't" approach was roughly where we got to in #4647, but that's obviously subject to revision. And I'd keep the list of options to a bare minimum - there's no need to blend Lab colors in display-p3.

My only worry is that the default value is currently "sRGB" not "auto". I'm not really sure why auto exists, actually - it doesn't seem to do anything except introduce ambiguity. As it currently gives the user agent the option of choosing between srgb and linearrgb, making it the default and redefining it to mean "always choose srgb for colors defined prior to color-4" doesn't seem to be a breaking change to me.

The "shorter, longer" hue issue was discussed here: #4735.

@weinig
Copy link

weinig commented Feb 26, 2021

  • "auto" is currently defined as "the user agent can choose either the sRGB or linearRGB spaces for color interpolation. This option indicates that the author doesn't require that color interpolation occur in a particular color space". I'd propose changing this to be be "Choose "srgb" for colors specified as sRGB or HSL, and otherwise act as "match".
  • "match" means use the color-space of the colors being interpolated. So two sRGB colors interpolate in sRGB, two HSL colors in HSL, two LCH colors in LCH. If the colors have different spaces, the interpolation is in LCH.
  • "lch", "srgb", "linearrgb" - interpolation always happens in that space.

What would the intended use case of something like auto or match be for something like gradient.

In general, I think we should try to avoid defaults and automatic picking of color spaces, especially based on input colors, as I think the results can be quite confusing. By always requiring a specific color space (or color-interpolation), (except for legacy case of sRGB where it is excluded), I think it makes it easier to explain the concept to new users.

It certainly has been the case for me that the more explicit tagging of input and output color spaces in our code (that is, in WebKit internals, so a bit different I will admit) has made things easier to understand and follow.

@faceless2
Copy link

faceless2 commented Feb 26, 2021

I'm not quite sure I follow what you're proposing - dropping auto or match? So presumably all interpolation would default to sRGB unless the author explicitly specified (for example) lch or lab?

I suspect we agree that sRGB has to remain the default for sRGB and HSL. But I don't believe sRGB should be the default for LCH or device-cmyk color. Two reasons.

  1. Colors are all eventually converted to the display's RGB gamut on screen, but in the print world it's important that the colors remain in the space they were specified in. Many variants of PDF completely disallow RGB in the document.
  2. Interpolating between two colors that are out-of-gamut in sRGB would lead to nonsensical results.

For me, the least confusing default choice is "the same colorspace as the input colors". That's match. But we can't do that for HSL for compat reasons, hence auto.

This all covered in #4647 - do read it through if you haven't already. I should note I also didn't see the value in LCH originally, but was won over in the discussion in that issue.

(edit: also, auto exists now for color-interpolation, so if that's the property we're using for this, it has to stay)

@weinig
Copy link

weinig commented Feb 26, 2021

I'm not quite sure I follow what you're proposing - dropping auto or match? So presumably all interpolation would default to sRGB unless the author explicitly specified (for example) lch or lab?

Yes, I proposing that for gradients (I am not making a point on any other type of interpolation, nor am I proposing we use the existing color-interpolation concept) we keep the current behavior that no specified color space means sRGB, and extend it to allow specifying a specific color space.

I am also expressing my opinion that I don't think match as a concept is good one, as I don't think it should matter how a color was specified when creating things like gradient. Namely, I think it would be confusing that a gradient specified as:

background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(0,0,255,1) 100%);

(that is a linear gradient from sRGB red to sRGB blue)

would be different that a gradient specified as:

background: linear-gradient(90deg, lch(54.29% 106.83 40.85) 0%, lch(29.57% 131.22 301.37) 100%);

(that is a linear gradient from sRGB red to sRGB blue, but the input colors use lch() syntax to describe the color)

especially given that the input colors can be, and often are, css variables. I think this would lead to confusing behavior as people start to adopt new (to CSS) ways of specifying color like lab(...) and lch(...).

@faceless2
Copy link

I'll counter your second example with this:

#div1 { background: lch(75% 67 180deg); }
#div2 { background: linear-gradient(lch(75% 67 180deg) lch(75% 101 82deg));
#div3 { background: lch(75% 101 82deg); }

Placed together, an author might reasonably expect the middle div to interpolate smoothly between div1 and div3 - the end colors are the same, after all. But if interpolation is in sRGB, it won't - both colors are outside the sRGB gamut, although within the display-p3 gamut. So if displayed on an iPhone or Macbook, there would be a sharp jump in color as div2 immediately converts the colors used in the gradient - including the colors at both ends - to sRGB.

I can't demonstrate this in a browser, but if you go to https://bfo.com/publisher/?job:I2eInQj00J2BBJIv#pdf, download the generated PDF and view it in Acrobat (which correctly interpolates gradients in Lab - you won't see this in other PDF viewers, including the one in your browser or macOS Preview) on a wide-gamut display such as a macbook, you'll see the gradient on the right has a hard boundary 1/3 and 2/3 of the way down. That's the effect I'm talking about.

(note the interpolation is otherwise fairly similar, despite the left gradient being LCH and the right sRGB, and the gradient covering a full 100° of hue. I just picked two random out-of-gamut colors, so no idea how representative this is)

Clearly neither solution is perfect and obviously there's no question this will be configurable - the only issue is which default is the least surprising.

I view sRGB is the option of last resort - it's guaranteed to be supported, but it's gamut is well below the majority (caution: assumption detected) of devices in use today. I just don't think we should be designing around it at this stage.

Either way I think we've both staked our respective positions out quite clearly, which is a good start. I'd welcome some other viewpoints on this.

@svgeesus
Copy link
Contributor

sRGB references light. Seems like a muddle to be mixing perceptual notions with light transport here.

All colors are produced by light, whether reflected or generated.

@svgeesus
Copy link
Contributor

In general, I think we should try to avoid defaults and automatic picking of color spaces, especially based on input colors, as I think the results can be quite confusing.

Yes. I'm thinking particularly of gradients where all the stops are in some form of sRGB and then, one stop gets changed to display-p3 or rec2020 or whatever.

In addition, we previously resolved to treat legacy-sRGB syntaxes and the new, as yet undeployed color(sRGB ...) differently, precisely to allow an sRGB opt-in opt-in to whatever better space we have for interpolating the new colors.

By always requiring a specific color space (or color-interpolation), (except for legacy case of sRGB where it is excluded), I think it makes it easier to explain the concept to new users.

I agree.

@svgeesus
Copy link
Contributor

@weinig wrote

we keep the current behavior that no specified color space means sRGB, and extend it to allow specifying a specific color space.

We have to keep the current behavior, for legacy syntax colors, for Web compat.

We don't have to require gamut-mapping to sRGB for gradients without an explicit color space. We could, and it simplifies some things while giving annoying and unexpected results in other cases.

We certainly need an opt-in to specify a better interpolation colorspace for gradients, going forward. (We also need the same for transitions and for animations, which are basically gradients except over time instead of space).

@weinig
Copy link

weinig commented Feb 26, 2021

In general, I think we should try to avoid defaults and automatic picking of color spaces, especially based on input colors, as I think the results can be quite confusing.

Yes. I'm thinking particularly of gradients where all the stops are in some form of sRGB and then, one stop gets changed to display-p3 or rec2020 or whatever.

Oh, one thing I learned recently is that WebKit is actually already a bit non-compliant in this case. WebKit interpolates a gradient in the so-called "Extended sRGB" color-space, which is just sRGB without the clamping and in the negative it is sign-reversed in the negative. So, if you do:

background: linear-gradient(90deg, color(display-p3 1 0 0) 0%, color(display-p3 0 1 0) 100%);

(or any other color that has a wider gamut than sRGB), we produce a gradient that includes those colors, but uses the sRGB curves.

I kind of doubt anyone is relying on this, but I don't know for sure.

@svgeesus
Copy link
Contributor

svgeesus commented Feb 26, 2021

@Artoria2e5 wrote

PS: the markdown is a bit broken in the color-4 interpolation…

Yeah I am having a problem with Bikeshed picking up inline GH issues like

Issue(#3088):

It doesn't insert the issue title or link, and it just runs on to the following paragraph.

Edit: it was my fault, unclosed <pre> in an example, now fixed.

@svgeesus
Copy link
Contributor

WebKit interpolates a gradient in the so-called "Extended sRGB" color-space, which is just sRGB without the clamping and in the negative it is sign-reversed in the negative.

Yes. As a means to store colors internally, for example, that is totally fine and avoids gamut mapping (and there is hardware support for it).

In general though interpolating on gamma-encoded colors will give you muddy midpoint colors. Legacy sRGB gradients have that already. I'd like to avoid it for non-legacy colors which don't have the Web-compat issue.

The better options are:

  1. Linear light (either linear-light sRGB, P3, etc or equivalently and more agnostic, XYZ). That avoids the mud The midpoint is not perceptually in the middle, though
  2. Perceptually linear like Lab (or more modern alternatives like ICtCp, Jzazbz, OKLab) where the midpoint color is clearly exactly between the two stops
  3. Perceptually linear and chroma-preserving polar spaces like LCH (etc) where the gradient between two very dissimilar hues follows a rainbow rather than passing close to grey.

@LeaVerou
Copy link
Member

LeaVerou commented Feb 26, 2021

My thoughts, most of which I have expressed before in the various discussions about this:

  • The idea of interpolating same color space colors in their own color space has been proposed multiple times (most recently by me) and every time we eventually realize it's not a good idea. Consider interpolating between two P3 colors: we really don't want them to interpolate in gamma-encoded P3 space.
  • I don't think a color-interpolation property is a good idea for a variety of reasons. a) Setting color interpolation on a per-element basis is too coarse. A single element may have rules that come from different stylesheets, with different ages and therefore assumptions. Given custom properties, even a single declaration could come from multiple stylesheets. b) What happens when color-interpolation is set to narrower spaces than the specified colors? How do you interpolate P3 colors in sRGB or Lab colors in P3 if they fall outside these gamuts?
  • Are there any use cases beyond backwards compat for interpolating in any gamma-encoded RGB space? If not, we probably don't want to provide more rope for people to hang themselves. The default behavior, whatever we decide it should be, can be specified via auto.
  • I have some doubts about whether backwards compat in this case would make good interpolation by default prohibitive. There is precedent in changing existing widely deployed behavior when the new behavior was undeniably better in every case, for example when UAs enabled text-decoration-skip-ink by default. Similarly, when we switched gradient interpolation to premultiplied RGBA, gradients had already been widely deployed at that point. Note that any undesirable interpolation can always be overridden by adding more stops, so there is an escape hatch for the handful of authors in the world that may prefer the old behavior.

@Myndex
Copy link
Member

Myndex commented Mar 1, 2021

The BEST colorspace for any blend or mix,

IS...

.....completely dependent on the desired result and use case.

You can not say that such and such colorspace is the most ideal for a gradient, not even for compositing. Not without knowing the full case and desired result.

Here are some examples in a GIST:
https://gist.github.com/Myndex/10caff6a68e844591e83eadeebfb4347#straight-talk-about-linear

More examples:
https://www.myndex.com/WEB/Gradients

And more more examples:
https://www.myndex.com/WEB/GradientsPartTwo

And the space/mode colors are being defined in is not necessarily the best space for performing a blend or mix or gradient.

@svgeesus
Copy link
Contributor

svgeesus commented Mar 2, 2021

And the space/mode colors are being defined in is not necessarily the best space for performing a blend or mix or gradient.

Agreed; for the non-legacy stuff we are careful to clearly separate what colorspace is being used for interpolation, from what is being used for specifying the colors (and to not require the two colors to be specified the same way or in the same colorspace).

@svgeesus
Copy link
Contributor

svgeesus commented Nov 4, 2021

This has now been done, see CSS Color 4: 13.1. Color space for interpolation which defines

<color-interpolation-method> = in [ <<ectangular-color-space> | <polar-color-space> <hue-interpolation-method>? ]

which is used in CSS Images 4:

3.1. Linear Gradients: the linear-gradient() notation
3.2. Radial Gradients: the radial-gradient() notation
3.3. Conic Gradients: the conic-gradient() notation

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