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] Add easing functions to color stops #1332

Open
meyerweb opened this issue May 8, 2017 · 62 comments
Open

[css-images] Add easing functions to color stops #1332

meyerweb opened this issue May 8, 2017 · 62 comments

Comments

@meyerweb
Copy link
Member

meyerweb commented May 8, 2017

A recent CSS-Tricks article (https://css-tricks.com/easing-linear-gradients/) and subsequent Twitter discussion (https://twitter.com/stubbornella/status/861603397677359104) spurred me to file this request for Level 4.

In summary, linear gradients are not always visually acceptable. This is particularly true when “fading out” a dark color to transparent. The article describes how to set up a bunch of color stops to ease out the gradient. A much better solution would be to add easing functions to all color stops after the first, with a linear default for backward compatibility.

The example in the article could be approximated like this:

linear-gradient(to bottom, black 0%, transparent 100% ease-in-out);

…instead of the 11 color stops used to get the effect. (Note that I don’t claim this would be a precise match; a cubic-bezier() easing would most likely be required for that. But it would be close.)

This would change the definition of <color-stop> (https://drafts.csswg.org/css-images-3/#typedef-color-stop) from:

<color-stop> = <color> <length-percentage>?

…to the following at a minimum:

<color-stop> = <color> <length-percentage>? <timing-function>?

As an author, I would probably prefer:

<color-stop> = <color> [ <length-percentage> || <timing-function> ]?

…since that would allow me to write the easing and distance in whichever order I liked. (For that matter, I’d prefer to be able to write all three in any order, but I don’t know if that would upset any implementors’ apple carts.)

@meyerweb
Copy link
Member Author

meyerweb commented May 8, 2017

Apologies for forgetting this at initial filing: <timing-function> would use the same values as transition-timing-function, with the same definitions. (https://www.w3.org/TR/css3-transitions/#transition-timing-function-property)

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 8, 2017

I really like this syntax. I've long had an idea of proposing easing for SVG gradients by borrowing the SMIL keySplines attribute. But of course, named CSS easing functions are much nicer.

However, we would need to figure out how it interacted with the gamma curves created by color hints, since those are already implemented & will be moved up to CSS Images Level 3.

@svgeesus
Copy link
Contributor

svgeesus commented May 8, 2017

However, we would need to figure out how it interacted with the gamma curves created by color hints, since those are already implemented & will be moved up to CSS Images Level 3.

Good point, see #1284 which adds a 'midpoint' - somewhat oddly named, it turns the linear interpolation between adjacent color stops into a curve, effectively moving the point at which the midway color is reached to be earlier (curve up) or later (curve down). This is way easier to understand with a diagram, of course. (an interactive diagram would be even better).

Being defined by a single point, it is less flexible than easing functions which are defined by two points, giving S-shaped curves as well..

@larsenwork
Copy link

larsenwork commented May 8, 2017

@meyerweb My attempt was mostly a proof of concept and as such nowhere near flexible enough so thank you very much for picking up my idea and making it way better 👍
I'm curious how the <color-stop> = <color> [ <length-percentage> || <timing-function> ]? would work in practice since timing functions refers to the transition between two stops.

Should one only be able to add timing-function to subsequent stops so it always describes how the gradient transitions from the previous stop to the stop with the timing-function applied?

Or would

linear-gradient(black 0%, transparent 100% ease-in-out);

effectively be the same as

linear-gradient(black 0% ease-in-out, transparent 100%);

and if so, then how should

linear-gradient(black 0% ease-in-out, transparent 100% ease-in);

be interpreted?

@meyerweb
Copy link
Member Author

meyerweb commented May 8, 2017

@larsenwork: My thought was that the timing function (really an easing function) for a color stop described the easing from the previous stop to itself, so an easing function on the first color stop would be ignored (though not invalid).

Thus:

linear-gradient(black 0%, transparent 100% ease-in-out);

…describes an ease-in-out from the previous color stop (black) to the current color stop (transparent). Therefore, your second example:

linear-gradient(black 0% ease-in-out, transparent 100%);

…is actually equivalent to:

linear-gradient(black 0% ease-in-out, transparent 100% linear);

If the <color> [ <length-percentage> || <timing-function> ]? syntax were adopted, then this would also be equivalent:

linear-gradient(black 0% ease-in-out, transparent linear 100%);

…because the easing function and distances could be written in any order.

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 8, 2017

To address @larsenwork's question, and also to synchronize with the mid-points syntax, maybe the easing function should be a separate item in the list. You would either use a or a mid-point position

So, working from the value definition in CSS Images 4 the full syntax for a <color-stop-list> would be:

<color-stop-list> =
  [ <linear-color-stop> [, <linear-color-hint>]? ]# , <linear-color-stop>
<linear-color-stop> = <color> && <color-stop-length>
<linear-color-hint> = [<length-percentage> | <animation-timing-function>]
<color-stop-length> = <length-percentage>{0,2}

In English:
A color stop list is a comma-separated list of two or more color stops.

Each color stop consists of a color value and an optional position value, or start and end positions values.

Each pair of consecutive color stops may optionally be separated by an interpolation instruction, which is also set off by commas. The interpolation instruction may take one of two forms:

  • a mid-point position, which is defined in the same way as a color stop position, or
  • an interpolation function, which is identical to the timing functions defined in CSS Timing Functions 1.

PS, If this goes ahead, CSS Timing Functions 1 should probably be changed to CSS Interpolation Functions or some other dimension-agnostic name.

@larsenwork
Copy link

larsenwork commented May 8, 2017

so an easing function on the first color stop would be ignored (though not invalid)

Just out of curiosity then why no make it invalid on the first color stop?

I'm also not sure what would be gained by making the order of easing and distance flexible. I think it could end up making the gradient function harder to read so I'd vote for

<color-stop> = <color> <length-percentage>? <timing-function>?

but I think you @AmeliaBR make some really interesting points too. Just to understand the syntax you're proposing it would be something like

linear-gradient(black, ease-in-out, white)

right?

Which would make intuitively sense since the transition-functions (as I'd agnostically call them) describes how to transition between to stops.

@meyerweb
Copy link
Member Author

meyerweb commented May 8, 2017

Just out of curiosity then why no make it invalid on the first color stop?

Because I don’t want to have the entire gradient fail to render just because someone left an easing function in when they deleted the first stop during authoring. Also it makes defining the value syntax easier, since there can just be <color-stop> instead of <first-color-stop> and <color-stop>.

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 8, 2017

@larsenwork

Just to understand the syntax you're proposing it would be something like linear-gradient(black, ease-in-out, white) right?

Right! Sorry, I should have actually included some examples.

It's not a perfect syntax, because it means the entries in the comma-separated list aren't all equal, but it's consistent with the existing mid-point color hint syntax.

@larsenwork
Copy link

larsenwork commented May 9, 2017

Having slept on it I'm leaning towards @AmeliaBR approach. To sum up:

Pros

  • Consistent with current <linear-color-hint> syntax
  • Has the logical limitation that easing can only be applied between color-stops

Cons

  • The comma separated values aren't created equal but was already the case with hints

@meyerweb
Copy link
Member Author

meyerweb commented May 9, 2017

What are color interpolation hints meant to be now? I can’t seem to find any examples, and the definition (https://drafts.csswg.org/css-images-4/#color-interpolation-hint) doesn’t make it at all clear what’s meant. Is it basically what this issue is about, or something else?

@larsenwork
Copy link

larsenwork commented May 9, 2017

@meyerweb the color hints only shifts the halfway point and effectively creates two linear gradients in the process — at least that's what I'm gathering from the description and examples on csswg where e.g.

linear-gradient(rgb(100%, 0%, 0%), 25%, rgb(100%, 100%, 100%))
renders like
linear-gradient(rgb(100%, 0%, 0%) 0%, rgb(100%,50%,50%) 25%, rgb(100%, 100%, 100%) 100%)

and as such of fairly limited use when wanting to create smooth gradients but maybe @AmeliaBR could clarify.

EDIT: no, I was incorrect. It does some smoothing. It's still not quite there visually compared to proper ease-functions.

EDIT 2: made a quick comparison of the output here: https://codepen.io/larsenwork/pen/pPpMpb/

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 9, 2017

Yes, the color hint creates a curve. I'd really like to see better figures in the spec, but it uses the same math as a gamma correction curve like this one:

An XY graph with a range of 0 to 1 on both axis. Three lines on the graph: a straight x=y line, a concave curve, and a convex curve.  Points at x=0.5 on each curve are connected, labelled by the y-values: 0.73, 0.5, and 0.218 for each.

That graph is showing paired encoding and decoding gamma curves for luminance adjustment, which isn't relevant to this discussion.

But the shape of the curves is. One axis would be the distance along the gradient and the other axis is the distance in color-space. Normally, for an easing function the Y axis would be the color value, but for re-interpretting this graph as it's currently labelled, it's easier if you treat the X axis as the color value and the Y axis as the distance. A midpoint color hint of 73% says to use the curve where 50% in color-space is positioned at 73% in the gradient distance. A color hint of 21% says to use the curve where the mid-point color is positioned at 21% distance.

(And of course, a mid-point of 50% means the mid-point color gets positioned at the 50% distance, so it's equivalent to simple linear interpolation.)

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 9, 2017

But to answer @meyerweb's question

Is it basically what this issue is about, or something else?

The color hints were one way to address the problems with linear gradients being ugly in many cases. They are based on gradients used in Adobe products. The mid-point position is easy to represent and intuitive to adjust in a visual editor.

But as @svgeesus noted, the curves created by mid-point adjustments are less flexible than the cubic bezier curves used in the transition functions, because they are defined by one point, not two. In particular, you can't create an S-curve, ease-in-out relationship. And you definitely cannot create steps. So I think there is a value in having both: the mid-points for their simplicity, and full transition functions for more control.

@meyerweb
Copy link
Member Author

meyerweb commented May 9, 2017

Ah! Given that, I agree, putting the interpolation function into the same spot as the hints (as a separate comma-separated value) makes the most sense. To authors, it will feel like “here’s this new interstitial thing that gives me a lot of easing options” and that’s perfect. It also makes hard-stop gradients a lot easier to create, simply by putting in a step rather than having to string together a whole series of color pairs with stop distances that touch adjacent pairs.

Would there be utility in allowing both a <length-percentage> and an interpolation function, as a way of modifying the shape of the interpolation? Or too many curves being smooshed together that way?

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 9, 2017

Would there be utility in allowing both a and an interpolation function, as a way of modifying the shape of the interpolation? Or too many curves being smooshed together that way?

How are you imagining that would work? I think the math would get pretty hairy.

Authors can always break the gradient down into multiple stops if they really want more control.

@meyerweb
Copy link
Member Author

I have no idea how that would work, to be honest. I brought it up mostly to see if the smarter and more math-oriented people on the thread (read: everyone) thought it would be useful.

@larsenwork
Copy link

larsenwork commented May 10, 2017

If the goal is modifiable interpolations then instead of length-percentage together with interpolation function I'd vote for ability to use cubic-bezier and not just ease, ease-in, linear etc. — or that was maybe already the idea?

so something like:

linear-gradient(black, cubic-bezier(0.470, 0.000, 0.745, 0.715), white)

Not sure how that would work for numbers > 1 though.

@AmeliaBR
Copy link
Contributor

@larsenwork

If the syntax referenced the timing-function/transition function data type, then a cubic-bezier() function would automatically be included, as an alternative to the shorthand keywords.

@tabatkins
Copy link
Member

If we added this, it would indeed extend the <linear-color-hint> grammar, so @larsenwork's example syntax (the timing function on its own, comma-separated from the two surrounding color-stops) is indeed how it would look.

This proposal overall sound interesting and is definitely potentially doable, but I'd like to see how far people can take just the mid-point approach first. None of the blogposts talking about smoothing gradients seem to acknowledge their existence (which is understandable, given that they're embedded deep in Images 4 right now). They should help things quite a bit.

@larsenwork
Copy link

@tabatkins I will try to do a proper stress test of the hint approach to see what it can render but I'm not too confident in what it will yield as the critical part of easing a gradient is having something with a terminal and/or final "velocity" of 0 which doesn't seem doable with hints. But I'll give it a go and also update the plugin with the proposed syntax here to see an approximation of what you could do if this syntax was supported

@tabatkins
Copy link
Member

My concern is just whether the midpoint approach can give a decent-looking "soft fade", not whether it can reproduce any particular mathematical definition. It appears to be good enough for Photoshop, so I'm curious to see example of it not being good enough for CSS.

@larsenwork
Copy link

I've quickly updated the playground to support the syntax proposed by @AmeliaBR e.g.

linear-gradient(black, cubic-bezier(0.48, 0.30, 0.64, 1.00), transparent)

NB - the plugin has some limits for now:

  • hacks by creating many color stops and as such might have some artefacts.
  • only supports cubic-bezier(), ease, ease-in, ease-out, ease-in-out but adding steps() should be fairly trivial.
  • doesn't yet support color-stop-positions but should also be easy to add.

http://codepen.io/larsenwork/pen/WjJorb?editors=0100

@tabatkins I haven't been able to easily recreate the curve in the example with hints (but again I'm not too familiar with the approach).
Basically I wanted a gradient that faded out nicely but also had a modified s-shape to avoid being too dark in top. Creating an asymmetrical s-shape like that with cubic-bezier() is much more intuitive than trying to balance out three color-stops with two hints even if it's possible to get something visually similar that way (I wasn't). I've never been satisfied with the gradient options in Photoshop and know designers who either stack gradients, use gaussian blur or other "hacks" to create visually pleasing gradients.

@bradkemper
Copy link
Contributor

Do we need the timing function for each stop, or could there just be one that applied to the entire gradient? Intermediate stops would just get interpolated.

@larsenwork
Copy link

larsenwork commented May 13, 2017

Created another demo. This one doesn't show why we'd want this compared to hints in terms of "soft-fades" (see the previous one for that) but I've added support for steps() which I also find quite useful. https://codepen.io/larsenwork/pen/LymMJy/?editors=0100

EDIT: Removed sidenote about steps — see #1371

@AmeliaBR
Copy link
Contributor

AmeliaBR commented May 13, 2017

@larsenwork

Symmetrical step functions are currently being called frames(). But that is being debated in #1301 .

Your fourth example matches frames(4): four equally-allocated values, including both the start and end values. Your third example doesn't match any existing timing function.

@larsenwork
Copy link

larsenwork commented May 14, 2017

@AmeliaBR cheers, I'll crop it out of this discussion to keep it on track then :)
@bradkemper we want to add it between stops — see #1332 (comment) and #1332 (comment) for details. Maybe @meyerweb can update the first comment so that newcomers can quickly see what the request is :)

@meyerweb
Copy link
Member Author

@bradkemper: I can envision wanting a single easing over an entire gradient of un-positioned stops, like red, orange, yellow, green, blue, purple, ease-out but I don’t see how that can be done while also allowing for different easings between the various stops, like red, ease-out, orange, ease-in, yellow, linear, green….

@Bryce-MW
Copy link

Bryce-MW commented Apr 25, 2021

Sorry if anyone saw my comment on a different thing. I commented in the wrong place before and I apologize for any confusion about that. I've been looking at a number of threads to try and understand where this proposal is at.

If I'm right, this is the proposal to add syntax like linear-gradient(red, ease-in-out, blue) to allow gradients that look in many cases a lot better (there are other concerns with gradients but this is a good start).

Anyway, it looks like this kind of thing was never implemented. I'm wondering where the process got stuck. Is implementation something that can be done at this point or is there more standards work required. Is this already in the spec or does there need to be a draft or something. I'm new to this whole process so I apologize if I've made a mistake somewhere. But I would love to improve gradients a bit. If it was at the point where I could go and contribute to Chromium or FireFox or something like that to add this feature, that would be ideal.

As an aside, I saw that #4754 was closed as a duplicate of this but it's actually not! It's something to think about later but I'll clarify the difference. With this proposal the line through color space will still be linear whereas I think that #4754 is proposing an arbitrary line through color space. It's hard to explain with words so here is a picture. (Image from this discussion)

image

The middle image is what the line through color space will look like even with this proposal. All this proposal does is allow you to change the rate that you traverse the line from contant to something that accelerates and decelerates.

Photoshop can kind of smooth out that line as seen in the top image. It's unclear if that also changes the rate at which the line is traversed like this proposal does.

The bottom is a smooth curve through color space which is what #4754 seems to be proposing.

This is part of why these discussions are difficult. It seems like it is very often that not everyone knows exactly what is being proposed since there are three things that are linear about a linear gradient. Luckily, this discussion appears to have everyone on the same page and in agreement that this is a good idea to implement. Which is why I am confused why no one has done anything with it.

@proimage
Copy link

It seems like it is very often that not everyone knows exactly what is being proposed since there are three things that are linear about a linear gradient.

That's confused me a number of times, where I wanted to propose a simple ease-gradient([direction & color stops]) syntax. Then my brain kept on kicking back into gear to remind me that the "linear" in linear-gradient() purely refers to the visual shape/direction of the gradient, not how it transitions from color stop to color stop. We can know this because the other existing gradient syntaxes are repeating-linear-gradient(), radial-gradient(), repeating-radial-gradient(), and the ever-elusive conic-gradient()—all of which describe the shape/direction of the gradient. :)

@danburzo
Copy link

danburzo commented Aug 8, 2021

Some notes on @Bryce-MW's comment above.

As an aside, I saw that #4754 was closed as a duplicate of this but it's actually not!

The comment in question asks for adding some facility for non-linear interpolation on each segment, which the syntax for adding easing between two color stops allows, with cubic-bezier() being a particularly expressive one.

With this proposal the line through color space will still be linear whereas I think that #4754 is proposing an arbitrary line through color space.

What makes a particular interpolation between colors straight or a curve in RGB space is whether the change happens at the same rate on all three dimensions (R, G, and B) identically. The easing function discussed in this proposal applies to each channel identically, thus it produces straight lines between colors.

The thing about spline interpolation (the graphs you see in the first and third row in the attached image) is to produce these "easings" between adjacent colors while taking the bigger picture into account, so that the values match up at the seams. The result can still be decomposed in terms of a series of piecewise easings, but these easings are different for each channel (R, G, B) and thus produce a curve (more or less smooth) rather than a series of straight segments.

A graphical tool could users let serialize spline-interpolated gradients into a CSS syntax as long as you could have separate cubic-bezier() functions per channel, e.g.:

linear-gradient(
  royalblue, 
  cubic-bezier(...) / cubic-bezier(...) / cubic-bezier(...), /* red / green / blue */
  tomato, 
  cubic-bezier(...) / cubic-bezier(...) / cubic-bezier(...), /* red / green / blue */ 
  goldenrod
)

...but this kind of syntax would be ridiculous to manage by hand, and automatic generation has other tools at its disposal (i.e. generate a large enough number of color stops so that basic linear interpolation does the trick, se for example @larsenwork's gradient editor)

@danburzo
Copy link

danburzo commented Aug 8, 2021

Something I've recently run into: what should happen when the easing function produces values outside the [0, 1] range? Here's an example:

linear-gradient(red, green, cubic-bezier(.25,-0.25,.75,1.25), blue);

When we reach the green-blue segment, the cubic-bezier(.25,-0.25,.75,1.25) easing has regions of overshoot at both ends of the segment. What color are those regions?

A. Always extrapolate easing values outside [0, 1] solely based on the two participating colors in the current segment?
B. Prefer reaching to adjacent segments, e.g. interpolate(green, blue, -0.5) picks up the red from the previous segment, and only extrapolate if that's not possible?

An alternative to extrapolation is clamping to the color values at the edges of the segment to avoid weird overshoots.

@tabatkins
Copy link
Member

Nothing wrong with extrapolation; it's simply what happens when you extend the interpolation math. I don't see any reason why we wouldn't do that.

I wouldn't extend into neighboring segments, because it's not clear how to map distances cross-segment.

@Bryce-MW
Copy link

Thanks for the comments. I did not think about how you could make a non-linear curve through colour space by using separate non-linear interpolation on each colour channel. From what I have read about this online, it seems that these two concepts are referred to separately when they are actually the same as you have shown. It certainly taught me something new! I do agree that writing the separate curves for each channel is probably not the best syntax.

The gradient editor that you linked is very good and what got me interested in this in the first place. It doesn't support a truly arbitrary curve through colour space but does support straight and circular lines through multiple colour spaces including L*a*b* which this proposal does not support. I think that if we want those options, we will have to wait a while until alternate colour spaces are supported in browsers outside Safari (and even Safari seems to only have limited support).

As for extrapolation, I think we should try to extrapolate in the way that you described as option A where possible. The issue is what to do when the colour does not exist or goes out of the gamut. I think the best option is to just choose the closest colour that is available similar to the relative colorimetric rendering intent.

If you want to do something different, it should be possible (though perhaps only reasonable for a tool) to add additional segments and set the exact cubic-bezier that you want.

As a personal update: I'm still not totally sure what the actual status is on this proposal (i.e. what the next step is). I have tried looking into adding this feature to browsers directly. It seems to me that modern browsers are so complex that I can't figure out where the code is that actually deals with gradients. I'm mostly used to working on small projects. I looked at Chromium and Firefox but maybe I should look at something like Servo for a proof of concept before trying to implement it in a full browser.

@proimage
Copy link

It seems like everyone wants so much from background gradients that perhaps the time has come to break it out from the confines of background-image, into its own set of background-gradient-____ CSS rules. Something like this, perhaps:

background-gradient-direction: [to top / 45deg / etc];
background-gradient-shape: [linear / circular / conic / hexagonal / yermom ];
background-gradient-color-stops: #bada55 45%, rebeccapurple 83%;
background-gradient-easing: [linear / ease / etc];
// etc...

@LeaVerou
Copy link
Member

I agree with Tab that it should extrapolate colors rather than extend into neighboring segments. That way, it retains the property that interpolating between A and B only depends on A and B (and parameters like easing function) and not the context. Also, this keeps it consistent with any other interpolation between colors (e.g. in transitions).

@proimage That wouldn't work for a number of reasons:

  1. There can be multiple background images, and it's unclear how to extend this syntax to accommodate for that usably.
  2. CSS images (of which gradients are only one special case) are not only accepted in backgrounds, but a number of other properties (border-image, masks, cursor, list-style-image to name a few).

@proimage
Copy link

proimage commented Aug 12, 2021

@LeaVerou Good point; my suggestion definitely doesn't take all that into account. However, I think the problem still stands: we're starting to ask so much of the *-gradient() syntax that it's becoming pretty unwieldy. I suppose an associative array for a value is too much to hope for...?

background-image: gradient({
    direction: to bottom,
    shape: linear,
    stops: [
        #aabbcc 33% linear,
        #112233 33% ease,
        #ddeeff 67%
    ]
});

@LeaVerou
Copy link
Member

@proimage What you are proposing is not idiomatic to CSS and thus inconsistent with the rest of the language. I believe it is even incompatible with the current syntax, which does not permit curly braces in property values. However, I do agree there is room for improving the readability of gradients. Please open a new issue if you want to discuss this further. Currently this is a side discussion that is adding noise to this one, which is about adding support for easing functions in the existing gradient functions.

@Bryce-MW
Copy link

While I certainly would be happy to have future expansions to gradients, I think that we should focus on implementing this proposal before considering more expansions. This proposal is a somewhat minor change. It's a lot easier to make multiple well-thought-out minor changes than one large less well-thought-out change. Many areas of CSS, and the web in general, use this methodology. For example, we got the basic CSS Grid before adding subgrids, and we got the Web Assembly MVP before getting SIMD.

What I would like to know, which it seems no one does know, is what the next step for this proposal is. The proposal we have previously been discussing would allow substituting an easing function instead of a midpoint for better control of colour-stop interpolation. I'm not sure whether that needs a written up standard change or RFC or something like that (which I do not know what looks like), or if we just need to have this brought up in a meeting, or even implemented in browsers directly. I am happy to work on it in whatever way I can. I didn't have much success with the browser route since I am unfamiliar with browsers but I would be happy to have another look if that was the right direction for this proposal to go in.

To be specific, the new linear gradient syntax which it seems that we have mostly agreed on is as follows (based on the existing syntax)

linear-gradient() = linear-gradient(
  [ <angle> | to <side-or-corner> ]? ,
  <color-stop-list>
)
<side-or-corner> = [left | right] || [top | bottom]
<color-stop-list> =
  <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]#
<linear-color-stop> = <color> && <length-percentage>?
<linear-color-hint> = [ <length-percentage> || <easing-function> ]

I'm not totally sure how these definitions work, this is my first time writing one rather than just reading it. I am not sure of the significance of one or two pipes when indicating multiple options. What I am trying to express is that a <linear-color-hint> could be a <length-percentage> or <easing-function>. This syntax was first proposed by @AmeliaBR and appears to not have any opposition or suggested alternatives.

@LeaVerou
Copy link
Member

@Bryce-MW

What I would like to know, which it seems no one does know, is what the next step for this proposal is.

While the exact path differs with each feature, a reasonable/common path forwards would be:

  1. We first resolve that the group actually agrees with this change. I've just Agenda+ it so we can discuss it in next week's meeting.
  2. One of the editors adds the feature in CSS Images 4 (I’m one of the editors, so is @fantasai). To speed up this process, you could also send a PR, that the editors could review and iterate on.
  3. At this point, you (or other authors) could start asking browsers for implementations, by opening issues in their trackers. In general browsers prioritize what to implement based on developer interest, so if you can demonstrate developer interest that makes it more likely to see implementations earlier rather than later. Of course, if you have the knowledge you could try sending a PR to a browser engine, but that requires a lot of experience.

@Bryce-MW
Copy link

Bryce-MW commented Aug 13, 2021

Thank you so much for the explanation @LeaVerou! I'm new to web standards, or really the web in general, I'm usually more of a systems guy. I've been trying to get those exact answers for a while but had not had any luck. I would be happy to help out with a PR. Would that be made to this repository or another one? I can have a look at some past PRs for an example. Also, is the meeting a public one? I would be interested to see how the process works.

I would like to try to make a browser PR. I know Firefox's CSS engine is written in Rust which I have a decent amount of experience with. I just can't quite figure out where in their source tree the CSS engine actually is. I might try first looking at Servo (EDIT: Servo doesn't support color transition hints as far as I can tell. It also claims that Gecko uses a piecewise approximation so maybe this implementation would be harder than expected) which their engine is based on. I think Chromium uses C++ which I am not as experienced in.

Again, I just want to thank you so much for taking the time to explain the steps and adding it to the agenda.

@Bryce-MW
Copy link

Bryce-MW commented Aug 15, 2021

EDIT: Rewrote this with my new understanding

One thing that I think has not totally been agreed on is whether these functions should influence colour-stop fixup. The current interpolation hints, the midpoints, do influence colour-stop fixup as if they were positioned stops. See how the first and third gradients on my test page are different and the first has the white colour-stop in the same place as the second.

The question is what we should do with the new easing function interpolation hints. They don't have positions the same way that midpoints do. In the example implementation that I am working on, I have made them act as if they were unpositioned stops. While it has not been discussed at length, it does seem like there is some wish for it to not influence colour-stop fixup at all.

I have been working on an implementation of this proposal in Servo. Currently, Servo does not even support midpoints though they are parsed. I have made it so that midpoints influence colour-stop fixup but they are not rendered yet (i.e. a 50% midpoint and 75% midpoint look exactly the same). I have also made a separate branch where I have enabled parsing of the new easing-function syntax and allowed it to influence colour-stop fixup in the way that I specified earlier. So if you pull my branch, compile, and run Servo with my test page, you will see the fourth gradient on the page appear (which it does not on any other browser) but it will not be easing yet.

My next goal is to try to implement midpoint rendering within webrender which is what Servo uses for rendering and is thus required for midpoints to display properly in Servo. Then I can look into also rendering easing functions. One thing I could consider is implementing the steps easing function by creating additional colour stops that are passed to the renderer. This would complicate how colour-stop fixup is calculated, however.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-images] Add easing functions to color stops, and agreed to the following:

  • RESOLVED: Expand the color stop hint grammar with easing functions and define how gradients respond to that
The full IRC log of that discussion <dael> Topic: [css-images] Add easing functions to color stops
<dael> github: https://github.com//issues/1332
<dael> TabAtkins: Put on by Lea. I can run with it
<dael> TabAtkins: There's a long thread. jist is color stops between gradients default to linear interp. Can supply an adjuster that does exponetial curve through that spot.
<dael> TabAtkins: People want more control over how colors ease between stops
<dael> TabAtkins: Suggestion developed in thread is allow color stop hints to be an easing function
<dael> TabAtkins: That describes the interpolation. There's a bit of details for how to do beziers that go outside 0-1. Can continue to extrapolate.
<hober> q+
<dino> wouldn't it behave the same as a color animation/transition if the bezier goes outside 0,1?
<dael> TabAtkins: Sounds reasonable. Haven't written. I think lea would take editorship if we approve
<astearns> ack hober
<dael> TabAtkins: Reasonable idea?
<dael> hober: Offhand that doesn't sound unreasonable. Concern is that CG is a graphics library that webkit uses and I don't think supports easing functions in this situation. might be a pain to impl
<dael> hober: It's only a vague concern
<dael> TabAtkins: Worst case you fill in intermediate stops
<dael> hober: Exactly. not terribly but inelegant. Not a huge concern
<dael> astearns: [reads dino in IRC]
<dael> TabAtkins: yes it would. Behavior matches animation when you go outside 0,1
<astearns> ack dbaron
<dael> dbaron: I don't remember if we added a control to let you change what space you're going through, srgb, linear rgb, lab...I don't know if added that but if we have or even if haven't seems worth thinking about syntax here would extend to that
<dino> q+
<dael> TabAtkins: I don't believe that's made it into spec. There has been plans, perhaps in color 5. Idea of a gradient across a color space is explored by lea and chris. Color space should be fine because these are between color stops and shouldn't conflict in syntax
<astearns> ack dino
<dino> don't worry
<dino> i'll type here. don't wait for me
<dbaron> s/lab.../lab, lch. /
<dino> please explain what happens if they try to animate the bezier
<dino> in the spec
<dino> not now
<dino> animate the gradient
<dael> TabAtkins: I'm curious what you mean by "that". Presume it's if the gradient itself is animated and start and end use easing
<dino> yep - what tab said
<dino> just noting that
<dael> dbaron: I think another issue is no interp rules for beziers
<dael> TabAtkins: No one does smart interp of gradients. We could throw up hands and say you can't. Another way is define interop rules for easing functions. Agree spec should be clear
<dino> thanks!!
<dael> TabAtkins: Could open a second issue about gradient animation to expose if we want to continue having smart rules for animations or not
<dael> astearns: Other comments?
<dael> astearns: Should resolve to explore?
<dael> TabAtkins: Prop: Expand the color stop hint grammer with easing functions and define how gradients respond to that
<dael> astearns: Obj?
<dael> astearns: I would also add an issue about what to do with intrep
<dael> RESOLVED: Expand the color stop hint grammar with easing functions and define how gradients respond to that
<dino> fwiw, i think this will be the first place where we could potentially animate easing functions
<dael> astearns: One question- expect the current use of hints could be expressed with one easing function?
<dael> TabAtkins: not precicely
<dael> astearns: Current hint sort of adds another color stop with easing between beginning hint and hint end. That couldn't be a single easing function. Okay
<dael> astearns: Anything more on this?

@Bryce-MW
Copy link

Bryce-MW commented Sep 2, 2021

I'm glad that this is getting resolved! We do still need to decide if the easing functions should influence color-stop fixup. I think they should to be consistent with midpoints but with these acting as unpositioned stops rather than positioned stops for the purpose of fixup.

One other note on the comment Concern is that CG is a graphics library that webkit uses and I don't think supports easing functions in this situation. What Firefox does for the current midpoint syntax is essentially create 10 color-stops without midpoints to approximate. Webkit could easily do something similar until proper support is added. I'm working on support in webrender so that Servo and Firefox can have proper interpolation for midpoints and the new interpolation functions. I'm happy to look into implementing that elsewhere once I'm done though I will have less time with school starting.

Let me know if there is anything else that I can help with!

@danburzo
Copy link

danburzo commented Sep 3, 2021

We do still need to decide if the easing functions should influence color-stop fixup. I think they should to be consistent with midpoints but with these acting as unpositioned stops rather than positioned stops for the purpose of fixup.

While color transition hints didn't match my mental model from Photoshop etc., it sort of makes sense to think of them as positioned color stops: they are explicitly positioned on the gradient line, and their color is meant to be the 50-50% blend between the two surrounding color stops. And, for better or for worse, this is how all rendering engines interpret them.

In contrast, I don't see any logic in easing functions contributing to the color stop position fixup. They are not stand-ins for colors, they just adjust the interpolation. It would be very confusing for authors if adding an easing function shifted the position of colors:

linear-gradient(red, green, blue);
[◉----------◉]

linear-gradient(red, easeInOut, green, blue);
[◉---§------◉]

In a separate issue I have also proposed an easing function that matches the curve produced by color transition hints without the displacement of unpositioned color stops.

@Bryce-MW
Copy link

Bryce-MW commented Sep 3, 2021

That seems fine to me. Midpoints confused me at first because I thought it would be a percentage of the distance between adjacent colour stops rather than being a percentage of the length of the entire gradient. Since it does work by producing a position relative to the entire gradient, it makes sense to be a positioned stop.

It looks like your proposed easing function would be exactly how I originally expected midpoints to work. So if we decide that adding an easing function does not influence colour stop fixup, then using that function would work in a way that is at least more intuitive to me.

This would also allow linear-gradient(red, green, blue); to look the same as linear-gradient(red, linear, green, blue); which I think is pretty intuitive.

So I now agree that they shouldn't influence colour stop fixup. I'll make sure that they don't in my test implementations unless we later agree otherwise.

@manuelmeister
Copy link

@meyerweb is this somewhat resolved with the new colorspaces?

@Bryce-MW
Copy link

As far as I can tell, the new gradient color space selection doesn’t quite address the original issue though it is an important and appreciated improvement. The original issue of having gradients that follow the same path through color space but at a non-constant speed still exists. You can still see the unexpected bright or dark bands where the color suddenly goes from changing at a constant rate to not changing at all. One way I describe this to people is like being in a car and slowing down with the brake in the same position where you get a little jolt at the end vs easing of the break slightly at the end to come to a stop smoothly.

That being said, especially with the new color spaces, using multiple stops to approximate the desired gradient is not all that bad. Firefox has been using that approximation for transition hints for a while now and I don’t think too many people have noticed.

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