w3c / csswg-drafts

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

[css-images-3] interpolating cross-fade(A, B) with cross-fade(B, A) #2852

Open fantasai opened 6 years ago

fantasai commented 6 years ago

Currently we spec that the arguments to cross-fade() are interpolated independently. However, this doesn't handle the case of cross-fade(A, B) to cross-fade(B, A) very nicely: it ends up creating a tree of cross-fades where one isn't necessary.

This is particularly a concern when the cross-fades are triggered by transitions which are reversed partway.

fantasai commented 6 years ago

Proposed edits:

diff --git a/css-images-3/Overview.bs b/css-images-3/Overview.bs
index 701eb90..aef55a8 100644
--- a/css-images-3/Overview.bs
+++ b/css-images-3/Overview.bs
@@ -1681,8 +1681,38 @@ Interpolating cross-fade() {#interpolating-image-combinations}
 --------------------------------------------------------------

        The three components of ''cross-fade()'' are interpolated independently.
+       However, if the two arguments match,
+       but are in opposite orders,
+       then first invert the percentage and
+       reverse the order of the images of the second ''cross-fade()''
+       before interpolating.
        Note this may result in nested ''cross-fade()'' notations.

+       <div class="example">
+               The following table shows some examples of image interpolation.
+
+               <table class="data">
+                       <thead>
+                               <tr>
+                                       <th>Start Image
+                                       <th>End Image
+                                       <th>50% Interpolation
+                       <tbody>
+                               <tr>
+                                       <td>''cross-fade(20% A, B)''
+                                       <td>''cross-fade(40% A, B)''
+                                       <td>''cross-fade(30% A, B)''
+                               <tr>
+                                       <td>''cross-fade(20% A, B)''
+                                       <td>''cross-fade(60% B, A)''
+                                       <td>''cross-fade(30% A, B)''
+                               <tr>
+                                       <td>''cross-fade(20% A, B)''
+                                       <td>''cross-fade(20% A, C)''
+                                       <td>''cross-fade(20% A, cross-fade(50% B, C)''
+               </table>
+       </div>
+
fantasai commented 6 years ago

Should probably flip start argument rather than end argument, actually.

css-meeting-bot commented 6 years ago

The Working Group just discussed Images Level 3, and agreed to the following:

The full IRC log of that discussion <fantasai> Topic: Images Level 3
<heycam> ScribeNick: heycam
<fantasai> github: https://github.com/w3c/csswg-drafts/issues/2852
<heycam> leaverou: we were trying to define interoplation between crossfades more reasonably with Elika yesterday
<fantasai> s/Elika/fantasai/
<heycam> ... when we're interpolating two cross fades with same images in a different order, A B , then B A
<heycam> ... it ends up creating a cross fade of the cross fade
<heycam> ... this comes up a lot, this interpolation happens when you're reversing a transition
<heycam> ... so fantasai suggested when adding a rule, when the images is the same and the order is different, we flip the order and change the percentages to account for it
<heycam> ... so 100% minus what it was
<heycam> ... is that OK? since it's CR we should check
<heycam> fantasai: the use case for getting this to work simply is that when you're doing transitions between one image and another, when you interrupt that transition, you're half way between
<heycam> ... so you want to interpolate between a crossfade and a start point
<heycam> ... your computed value gets more and more complex
<heycam> dbaron: is this the only interoplation type that has this problem?
<heycam> emilio: I suspect it can happen with transform as well
<heycam> dbaron: yes
<heycam> birtles: with calc
<heycam> ericwilligers: with transform didn't we define ...
<heycam> TabAtkins: the interpolate() function
<heycam> dbaron: so transform can do the same thing with interpolate() nesting in the same way
<heycam> fantasai: we should probably do the same thing for transform then
<heycam> ericwilligers: does interpolate() make cross-fade() redundant?
<heycam> leaverou: it only helps you get the intermediate values
<heycam> fantasai: there are use cases for cross fade where the author explicitly wants to fade between two things
<heycam> ... and there are differnet ways of interpolating images, cross fade is one of them
<heycam> fantasai: the proposed resolution is that interpolating crossfade or the interpolate function, where the end point and start point have opposite order of arguments, you swap the order, so you don't nest the interpolating function
<heycam> xidorn: shouldn't we just merge things when one side is crossfade A to B, and the other side is A or B?
<heycam> fantasai: that's the next question
<heycam> dbaron: what stage is this happening?
<heycam> fantasai: computed value?
<heycam> dbaron: so part of the process of computing the value of a nested cross fade is to un-nest
<heycam> fantasai: no, when you're computing the mid point for
<heycam> dbaron: so you're changing the interpolation rules
<heycam> fantasai: yes
<heycam> Rossen: does that sounds reasonable to people?
<heycam> dbaron: yes, if you make it consistently apply to other places where this problem comes up
<heycam> TabAtkins: yes
<heycam> Rossen: any objections to this? do we need to add a note for the general interpolate function to be added as well?
<heycam> birtles: I misunderstood
<heycam> ... I thought this was just about computed value simplification of nested cross-fade
<heycam> ericwilligers: [writes examples on white board]
<heycam> birtles: I'm just wondering why we're adding rules to interpolation. would the alternative be to say this is how nested crossfades are simplified at computed value time?
<heycam> ... then you don't need to do anything particular for interpolation
<heycam> fantasai: we could do that
<heycam> leaverou: I'd be fine with that
<heycam> dino: cross-fade(A, B, 50%) is not the same as cross-fade(B, A, 50%)
<heycam> ... it's 50% of src-overing the second on top of the first
<heycam> fantasai: and with transparency?
<heycam> dino: you can tell the difference between B over A 50% and A over B 50%, yes
<heycam> ericwilligers: would you advocate nested cross-fades()?
<heycam> dino: I'd advocate not interpolating
<heycam> fantasai: I don't think that's a good solution
<heycam> dino: if we're going to do it, then nested cross-fade(), maybe
<heycam> leaverou: nested cross-fades() should be supported anyway
<heycam> dino: so, yes, nested cross-fades() would be my preferred solution
<heycam> leaverou: I think it's Safari and Chrome at the moment
<heycam> birtles: pre-fork, so one implementation
<heycam> TabAtkins: dino your point before about A B 50% and B A 50% being different is wrong, since it's a dissolve
<heycam> ... dissolve op rather than src-over op
<heycam> ... since that's the correct way to fade between two images with potential transparencies
<heycam> Rossen: do we need a whiteboarding breakout for this?
<heycam> leaverou: if reversing the order does produce the same image, what's the argument against it
<heycam> ... if nobody has any then why do we need to break out
<fantasai> http://drafts.csswg.org/css-images-3/
<fantasai> https://drafts.csswg.org/css-images-3/#cross-fade-function
<heycam> TabAtkins: only options are (a) do nothing, allowing nesting, (b) accept these rules for interpolation simplification, or (b) accept these rules for computed value simplification and allow interpolation to fall out
<heycam> emilio: why (c)?
<heycam> leaverou: then it also allows us to simplify computed values returned
<heycam> emilio: I thought the point was nested cross fade is hard
<heycam> TabAtkins: it's not that it's hard, but in common cases, like repeatedly interrupting a transition, it resutls in a stack of cross-fade()s
<heycam> ... when you could simplify it down
<heycam> emilio: the difference between (b) and (c) is not observable
<heycam> TabAtkins: yes it is
<heycam> emilio: I think I prefer (b)
<heycam> leaverou: [shows demo]
<heycam> emilio: if you want to simplify what you interpolate, to avoid growing stacks of cross-fades(), you do it there
<heycam> ... but you also need to do it at computed values
<heycam> ... because the author might have specified a nested cross-fade()
<heycam> fantasai: the interoplated intermediate value must be a computed value
<TabAtkins> Note that if we *do* finally do the "any number of images" extension, then we can simplify *all* nested cross-fades, in all situations.
<heycam> leaverou: if I'm understanding emilio correctly, you don't want to create this thing if you're going to simplify it anyway
<heycam> ... if you're already going to simplify serialization of a specified (and computed) value
<heycam> emilio: it complicates the code
<heycam> birtles: I wonder if you're going to need to simplify it first anyway, in order to interpolate potentially nested inputs that you get
<heycam> ericwilligers: yes
<heycam> birtles: also will need to do it for addition, assuming that makes sense
<heycam> leaverou: ideally it would be nice if cross-fade() accepted any number of arguments, and flatten them down to a single one
<heycam> ericwilligers: then you've got more computations
<heycam> s/computations/permutations/
<heycam> leaverou: if you could collapse the tree down to a flat cross fade list of values, it's simpler to apply
<heycam> TabAtkins: since dissolve commutes, you can collapse all cross fades
<heycam> dino: plus commutes, dissolve doesn't
<heycam> dino: what I got wrong is I'm src-overing not plusing
<heycam> TabAtkins: plusing an appropriate dissolved image commutes, such that you can take nested cross fades and flatten it to a list of images that plus together
<heycam> leaverou: should we just do that, allow cross-fade() to take a list of arguments
<heycam> birtles: I think it's nicer from an authoring point of view too
<birtles> that is, parsing the result of getComputedStyle is easier if you don't need to handle nested cross-fade functions
<heycam> leaverou: another issue, if you're interpolating between A and cross-fade(A, ...), with the current rules you'll get a nested cross-fade()
<heycam> ... we could define that the A gets promoted to 100% cross-fade, and just interpolate percentage
<leaverou> https://github.com/w3c/csswg-drafts/issues/2853
<heycam> fantasai: I think the proposal one this one is you get a single cross-fade() with A and B with arguments and the appropriate percentage
<heycam> leaverou: we can, but if we're collapsing trees of cross-fade()s, it's less important
<heycam> fantasai: collapsing trees is different from merging
<heycam> TabAtkins: depends how we collapse
<heycam> dino: [looking at the issue] wouldn't it be x is between 0 and p, not p and 100?
<heycam> leaverou: you're interpolating between full A and the cross-fade
<heycam> ... you want it to start at 100
<heycam> dbaron: I think we should try to resolve both at the same tim
<heycam> s/tim/time/
<heycam> ... where the simplification happens, turning into multi arg cross-fade(), applies to both
<heycam> myles: the proposal is change the behavior because it makes it easier to compute?
<heycam> leaverou: this way you wouldn't need to interpolate to make a new cross-fade() at all
<heycam> myles: there is a difference between having a tree of cross fades and not having a tree
<heycam> TabAtkins: nobody has trees yet
<heycam> ... we're trying to avoid that now
<heycam> TabAtkins: overall proposal is, avoid all trees of cross-fades, in all situations, by making it accept more than two arguments, just dissolve and plus throughout those
<fantasai> fantasai explains that interpolating cross-fade(x% A, B) and cross-fade(y% A, B) results in cross-fade(z% A, B), not a tree of cross-fades()s
<heycam> ... and make nested cross-fade() invalid, and make interpolations between them build the appropriate multi-arg value
<fantasai> this issue about treating interpolation of A & cross-fade(y% A, B) and cross-fade(100% A, B) & cross-fade(y% A, B) the same way
<dbaron> q+ to comment on (a) nesting through other functions and (b) validity of percentages that add to more than 100%
<heycam> [whiteboard discussion of particular interpolations]
<heycam> leaverou: I still think nested cross-fades() should be valid, since they could come from variables
<heycam> myles: if the goal is to avoid avoiding trees, by making a list that has all the nodes of the tree, I don't see why that's better
<heycam> TabAtkins: you don't need to generate all the intermediate images
<heycam> myles: is there a behaviour change?
<heycam> TabAtkins: no
<heycam> myles: then we should just say "as if", a browser optimization
<heycam> myles: so we're talking about computed values
<heycam> florian: do we disallow in specified value?
<heycam> TabAtkins: before we decide on that, let's look at the core thing
<heycam> ... multi-arg cross-fade(), does it sound reasonable
<heycam> dino: and you must provide %s up to the last one?
<heycam> ... allowed to go over 100%?
<heycam> TabAtkins: yes [for first], and no.
<heycam> TabAtkins: if the percentages add up to over a 100%, you sum them and normalize to 100%
<heycam> ... then the last one gets zero
<heycam> myles: every time gets a percentage
<heycam> TabAtkins: except the last
<heycam> ... last one gets what's left over
<heycam> dino: complete error if you did (A 10%, B, C)?
<heycam> TabAtkins: syntax error
<heycam> myles: is the purpose of this that humans will write this? or just to make the getCS output smaller?
<heycam> TabAtkins: so clearly we want this for interpolation
<fantasai> heycam: Simplifying trees down to one level is same as flat list
<fantasai> heycam: Want to collapse list to minimum number of items
<fantasai> TabAtkins: If the goal is to avoid explosion...
<heycam> myles: if this is for humans to use, it's useful
<heycam> ericwilligers: negative %s allowed?
<heycam> TabAtkins: no, individual %s above 100% should be disallowed
<fantasai> s/disallowed/invalid/
<fantasai> TabAtkins: But we can't syntacitcally restrict the sum
<heycam> birtles: I'm not 100% sure about the normalization part
<heycam> ... the syntax I like
<heycam> ericwilligers: not sure about normalization when you have interpolations between lists
<heycam> birtles: I was thinking, once the bucket's full, the overflow could be dropped
<heycam> myles: should make it possible for the last one to have a %
<heycam> florian: to use them as 'fr's?
<heycam> leaverou: right now, cross-fade() percentage is optional
<heycam> ... probably it should retain this
<heycam> ... right now (A, B), the 50% is implied
<heycam> ... (A 20%, B, C) should make sense
<heycam> shans: distribute the rest
<heycam> leaverou: yes
<heycam> myles: allow the last percentage to be specified, then just weight them
<astearns> q?
<heycam> fantasai: if you have (A 20%, C 20%, C 20%)
<heycam> ... you could simply transparent black to take the slack
<heycam> florian: rather than normalizing to 100%?
<heycam> fantasai: if you're under
<heycam> shans: one problem with diff behaviours on either side of 100% gets you non-linearities, bad for animation
<heycam> ericwilligers: like opacity:1
<astearns> ack dbaron
<Zakim> dbaron, you wanted to comment on (a) nesting through other functions and (b) validity of percentages that add to more than 100%
<heycam> TabAtkins: the suggest grammar is, cross-fade() takes one or more args, each has an optional %
<heycam> ... we'll work out what missing %s mean
<heycam> Rossen: any objections to that?
<heycam> RESOLVED: cross-fade() takes one or more args, each has an optional %
<heycam> leaverou: next, collapsing trees of cross-fade()s
<heycam> ... it's much easier to define interpolatino that way
<heycam> ... for authors reading it back
<heycam> ... don't know why we wouldn't collapse it down, esp given it's commutative
<heycam> emilio: can we disallow nested cross-fade() in specified values?
<heycam> TabAtkins: no for the same reason you allow nested calc()s
<heycam> leaverou: with variables
<heycam> dbaron: two other points
<Rossen> q?
<heycam> ... there are going to be other image-ish functions
<heycam> ... cross-fade() one of these other functions continaing a cross-fade() will be a reasonable thing
<heycam> ... I think the other piece is that I think one of the things that results from disallowing it, everyone has to go and implement that, then later we'll allow it, which is more work
<heycam> ... so we should just allow it now
<heycam> TabAtkins: agreed
<heycam> ... specified values should allow it. interpolated values collapse to a single value
<heycam> ... interpolated values, don't care?
<heycam> leaverou: I would collapse
<heycam> birtles: collapse
<heycam> fantasai: I thin kso
<heycam> s/thin kso/think so/
<heycam> florian: both kinds of smooshing down, flattening tree, and merging items in the list?
<heycam> leaverou: they are separate issues but I support both
<heycam> myles: so it's not just a simple substitution, there are formulas to calcalate the %s
<heycam> TabAtkins: yes. it's just mulitplican tho
<heycam> RESOLVED: At computed value time, simplify directly nested cross-fade()s into a single cross-fade()
<heycam> xidorn: that means the flattened value is the canonical form of that
<heycam> TabAtkins: yes
<heycam> leaverou: now dropping duplicated images
<heycam> TabAtkins: if you have multiple args which are the same image
<heycam> ... should the canonical form automatically collapse those together and add their percentage
<heycam> leaverou: do we have an algorithm for same image?
<heycam> fantasai: the compued value of the <url> is the absolute URL
<heycam> Rossen: the reason for simplifying is avoiding growth of the values
<heycam> leaverou: we should not only combine the duplicates but also sort them
<heycam> fantasai: I think we need to simplify this, to avoid growing lists
<heycam> ... we can do it very simply
<heycam> emilio: how do you define sorting images?
<heycam> myles: if you didn't want this, didn't want to collapse -- internally you want to
<Rossen> heycam: are the cases where we want avoid this?
<heycam> fantasai: I don't think so
<heycam> ... it's going to be the same when you collapse it all down
<heycam> fantasai: notion of equality we want is "computed values are the same"
<heycam> frremy: with gradients, with px and %s ....
<heycam> fantasai: that's not computed value
<heycam> ... that's used value
<heycam> RESOLVED: At computed value time we collapse same <image> values in cross-fade()s and adding their percentages
<heycam> leaverou: sorting
<heycam> fantasai: you don't want computed value of cross-fade() where you randomly swap the order
<heycam> fantasai: re-sort into the target order and go from there?
<heycam> ... so don't sort the computed value
<heycam> ... but when interpolating, you re-sort the start point into the end point order
<heycam> myles: that's cool then CSS doesn't have to define order
<heycam> so no sorting occurs, for interpolation it falls out of the simplification process
<TabAtkins> When interpolating be3tween A and B, you just cross-fade(A,B) then collapse; whatever that results in is the answer.

See official minutes

SelenIT commented 6 years ago

Could you please also clarify where is the cross-fade() function currently defined? I see a reference to it in css-image-3, but it seems to link to the definition in css-image-4. Is it intended or did I miss something?

tabatkins commented 6 years ago

Yeah, it's defined in level 4.