1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "KeyframeInterpolation.h"
namespace WebCore {
const KeyframeInterpolation::KeyframeInterval KeyframeInterpolation::interpolationKeyframes(Property property, double iterationProgress, const Keyframe& defaultStartKeyframe, const Keyframe& defaultEndKeyframe) const
{
// 1. If iteration progress is unresolved abort this procedure.
// 2. Let target property be the longhand property for which the effect value is to be calculated.
// 3. If animation type of the target property is not animatable abort this procedure since the effect cannot be applied.
// 4. Define the neutral value for composition as a value which, when combined with an underlying value using the add composite operation,
// produces the underlying value.
// 5. Let property-specific keyframes be the result of getting the set of computed keyframes for this keyframe effect.
// 6. Remove any keyframes from property-specific keyframes that do not have a property value for target property.
unsigned numberOfKeyframesWithZeroOffset = 0;
unsigned numberOfKeyframesWithOneOffset = 0;
Vector<const Keyframe*> propertySpecificKeyframes;
for (size_t i = 0; i < numberOfKeyframes(); ++i) {
auto& keyframe = keyframeAtIndex(i);
if (!keyframe.hasResolvedOffset() || !keyframe.animatesProperty(property))
continue;
auto offset = keyframe.offset();
if (!offset)
numberOfKeyframesWithZeroOffset++;
if (offset == 1)
numberOfKeyframesWithOneOffset++;
propertySpecificKeyframes.append(&keyframe);
}
// 7. If property-specific keyframes is empty, return underlying value.
if (propertySpecificKeyframes.isEmpty())
return { };
auto hasImplicitZeroKeyframe = !numberOfKeyframesWithZeroOffset;
auto hasImplicitOneKeyframe = !numberOfKeyframesWithOneOffset;
// 8. If there is no keyframe in property-specific keyframes with a computed keyframe offset of 0, create a new keyframe with a computed keyframe
// offset of 0, a property value set to the neutral value for composition, and a composite operation of add, and prepend it to the beginning of
// property-specific keyframes.
if (hasImplicitZeroKeyframe) {
propertySpecificKeyframes.insert(0, &defaultStartKeyframe);
numberOfKeyframesWithZeroOffset = 1;
}
// 9. Similarly, if there is no keyframe in property-specific keyframes with a computed keyframe offset of 1, create a new keyframe with a computed
// keyframe offset of 1, a property value set to the neutral value for composition, and a composite operation of add, and append it to the end of
// property-specific keyframes.
if (hasImplicitOneKeyframe) {
propertySpecificKeyframes.append(&defaultEndKeyframe);
numberOfKeyframesWithOneOffset = 1;
}
// 10. Let interval endpoints be an empty sequence of keyframes.
Vector<const Keyframe*> intervalEndpoints;
// 11. Populate interval endpoints by following the steps from the first matching condition from below:
if (iterationProgress < 0 && numberOfKeyframesWithZeroOffset > 1) {
// If iteration progress < 0 and there is more than one keyframe in property-specific keyframes with a computed keyframe offset of 0,
// Add the first keyframe in property-specific keyframes to interval endpoints.
intervalEndpoints.append(propertySpecificKeyframes.first());
} else if (iterationProgress >= 1 && numberOfKeyframesWithOneOffset > 1) {
// If iteration progress ≥ 1 and there is more than one keyframe in property-specific keyframes with a computed keyframe offset of 1,
// Add the last keyframe in property-specific keyframes to interval endpoints.
intervalEndpoints.append(propertySpecificKeyframes.last());
} else {
// Otherwise,
// 1. Append to interval endpoints the last keyframe in property-specific keyframes whose computed keyframe offset is less than or equal
// to iteration progress and less than 1. If there is no such keyframe (because, for example, the iteration progress is negative),
// add the last keyframe whose computed keyframe offset is 0.
// 2. Append to interval endpoints the next keyframe in property-specific keyframes after the one added in the previous step.
size_t indexOfLastKeyframeWithZeroOffset = 0;
int indexOfFirstKeyframeToAddToIntervalEndpoints = -1;
for (size_t i = 0; i < propertySpecificKeyframes.size(); ++i) {
auto offset = propertySpecificKeyframes[i]->offset();
if (!offset)
indexOfLastKeyframeWithZeroOffset = i;
if (offset <= iterationProgress && offset < 1)
indexOfFirstKeyframeToAddToIntervalEndpoints = i;
else
break;
}
if (indexOfFirstKeyframeToAddToIntervalEndpoints >= 0) {
intervalEndpoints.append(propertySpecificKeyframes[indexOfFirstKeyframeToAddToIntervalEndpoints]);
intervalEndpoints.append(propertySpecificKeyframes[indexOfFirstKeyframeToAddToIntervalEndpoints + 1]);
} else {
ASSERT(indexOfLastKeyframeWithZeroOffset < propertySpecificKeyframes.size() - 1);
intervalEndpoints.append(propertySpecificKeyframes[indexOfLastKeyframeWithZeroOffset]);
intervalEndpoints.append(propertySpecificKeyframes[indexOfLastKeyframeWithZeroOffset + 1]);
}
}
return { intervalEndpoints, hasImplicitZeroKeyframe, hasImplicitOneKeyframe };
}
static double transformProgressDuration(const WebAnimationTime& duration)
{
if (auto time = duration.time())
return time->seconds();
return 1.0;
}
void KeyframeInterpolation::interpolateKeyframes(Property property, const KeyframeInterval& interval, double iterationProgress, double currentIteration, const WebAnimationTime& iterationDuration, TimingFunction::Before before, const CompositionCallback& compositionCallback, const AccumulationCallback& accumulationCallback, const InterpolationCallback& interpolationCallback, const RequiresBlendingForAccumulativeIterationCallback& requiresBlendingForAccumulativeIterationCallback) const
{
auto& intervalEndpoints = interval.endpoints;
if (intervalEndpoints.isEmpty())
return;
auto& startKeyframe = *intervalEndpoints.first();
auto& endKeyframe = *intervalEndpoints.last();
auto usedBlendingForAccumulativeIteration = false;
// 12. For each keyframe in interval endpoints:
// If keyframe has a composite operation that is not replace, or keyframe has no composite operation and the
// composite operation of this keyframe effect is not replace, then perform the following steps:
// 1. Let composite operation to use be the composite operation of keyframe, or if it has none, the composite
// operation of this keyframe effect.
// 2. Let value to combine be the property value of target property specified on keyframe.
// 3. Replace the property value of target property on keyframe with the result of combining underlying value
// (Va) and value to combine (Vb) using the procedure for the composite operation to use corresponding to the
// target property’s animation type.
if (isPropertyAdditiveOrCumulative(property)) {
// Only do this for the 0 keyframe if it was provided explicitly, since otherwise we want to use the "neutral value
// for composition" which really means we don't want to do anything but rather just use the underlying style which
// is already set on startKeyframe.
if (startKeyframe.offset() || !interval.hasImplicitZeroKeyframe) {
auto startKeyframeCompositeOperation = startKeyframe.compositeOperation().value_or(compositeOperation());
if (startKeyframeCompositeOperation != CompositeOperation::Replace)
compositionCallback(startKeyframe, startKeyframeCompositeOperation);
}
// Only do this for the 1 keyframe if it was provided explicitly, since otherwise we want to use the "neutral value
// for composition" which really means we don't want to do anything but rather just use the underlying style which
// is already set on endKeyframe.
if (endKeyframe.offset() != 1 || !interval.hasImplicitOneKeyframe) {
auto endKeyframeCompositeOperation = endKeyframe.compositeOperation().value_or(compositeOperation());
if (endKeyframeCompositeOperation != CompositeOperation::Replace)
compositionCallback(endKeyframe, endKeyframeCompositeOperation);
}
// If this keyframe effect has an iteration composite operation of accumulate,
if (iterationCompositeOperation() == IterationCompositeOperation::Accumulate && currentIteration && requiresBlendingForAccumulativeIterationCallback()) {
usedBlendingForAccumulativeIteration = true;
// apply the following step current iteration times:
for (auto i = 0; i < currentIteration; ++i) {
// replace the property value of target property on keyframe with the result of combining the
// property value on the final keyframe in property-specific keyframes (Va) with the property
// value on keyframe (Vb) using the accumulation procedure defined for target property.
if (!startKeyframe.offset() && !interval.hasImplicitZeroKeyframe)
accumulationCallback(startKeyframe);
if (endKeyframe.offset() == 1 && !interval.hasImplicitOneKeyframe)
accumulationCallback(endKeyframe);
}
}
}
// 13. If there is only one keyframe in interval endpoints return the property value of target property on that keyframe.
if (intervalEndpoints.size() == 1) {
interpolationCallback(0, 1, IterationCompositeOperation::Replace);
return;
}
// 14. Let start offset be the computed keyframe offset of the first keyframe in interval endpoints.
auto startOffset = startKeyframe.offset();
// 15. Let end offset be the computed keyframe offset of last keyframe in interval endpoints.
auto endOffset = endKeyframe.offset();
// 16. Let interval distance be the result of evaluating (iteration progress - start offset) / (end offset - start offset).
auto intervalDistance = (iterationProgress - startOffset) / (endOffset - startOffset);
// 17. Let transformed distance be the result of evaluating the timing function associated with the first keyframe in interval endpoints
// passing interval distance as the input progress.
auto transformedDistance = intervalDistance;
if (!iterationDuration.isZero()) {
auto rangeDuration = (endOffset - startOffset) * transformProgressDuration(iterationDuration);
if (auto* timingFunction = timingFunctionForKeyframe(startKeyframe))
transformedDistance = timingFunction->transformProgress(intervalDistance, rangeDuration, before);
}
// 18. Return the result of applying the interpolation procedure defined by the animation type of the target property, to the values of the target
// property specified on the two keyframes in interval endpoints taking the first such value as Vstart and the second as Vend and using transformed
// distance as the interpolation parameter p.
auto iterationCompositeOperation = usedBlendingForAccumulativeIteration ? IterationCompositeOperation::Replace : this->iterationCompositeOperation();
currentIteration = usedBlendingForAccumulativeIteration ? 0 : currentIteration;
interpolationCallback(transformedDistance, currentIteration, iterationCompositeOperation);
}
} // namespace WebCore
|