1/*
2 Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "TextureMapperAnimation.h"
22
23#include "LayoutSize.h"
24
25namespace WebCore {
26
27static RefPtr<FilterOperation> blendFunc(FilterOperation* fromOp, FilterOperation& toOp, double progress, const FloatSize&, bool blendToPassthrough = false)
28{
29 return toOp.blend(fromOp, progress, blendToPassthrough);
30}
31
32static FilterOperations applyFilterAnimation(const FilterOperations& from, const FilterOperations& to, double progress, const FloatSize& boxSize)
33{
34 // First frame of an animation.
35 if (!progress)
36 return from;
37
38 // Last frame of an animation.
39 if (progress == 1)
40 return to;
41
42 if (!from.isEmpty() && !to.isEmpty() && !from.operationsMatch(to))
43 return to;
44
45 FilterOperations result;
46
47 size_t fromSize = from.operations().size();
48 size_t toSize = to.operations().size();
49 size_t size = std::max(fromSize, toSize);
50 for (size_t i = 0; i < size; i++) {
51 RefPtr<FilterOperation> fromOp = (i < fromSize) ? from.operations()[i].get() : nullptr;
52 RefPtr<FilterOperation> toOp = (i < toSize) ? to.operations()[i].get() : nullptr;
53 RefPtr<FilterOperation> blendedOp = toOp ? blendFunc(fromOp.get(), *toOp, progress, boxSize) : (fromOp ? blendFunc(nullptr, *fromOp, progress, boxSize, true) : nullptr);
54 if (blendedOp)
55 result.operations().append(blendedOp);
56 else {
57 auto identityOp = PassthroughFilterOperation::create();
58 if (progress > 0.5)
59 result.operations().append(toOp ? toOp : WTFMove(identityOp));
60 else
61 result.operations().append(fromOp ? fromOp : WTFMove(identityOp));
62 }
63 }
64
65 return result;
66}
67
68static bool shouldReverseAnimationValue(Animation::AnimationDirection direction, int loopCount)
69{
70 return (direction == Animation::AnimationDirectionAlternate && loopCount & 1)
71 || (direction == Animation::AnimationDirectionAlternateReverse && !(loopCount & 1))
72 || direction == Animation::AnimationDirectionReverse;
73}
74
75static double normalizedAnimationValue(double runningTime, double duration, Animation::AnimationDirection direction, double iterationCount)
76{
77 if (!duration)
78 return 0;
79
80 const int loopCount = runningTime / duration;
81 const double lastFullLoop = duration * double(loopCount);
82 const double remainder = runningTime - lastFullLoop;
83 // Ignore remainder when we've reached the end of animation.
84 const double normalized = (loopCount == iterationCount) ? 1.0 : (remainder / duration);
85
86 return shouldReverseAnimationValue(direction, loopCount) ? 1 - normalized : normalized;
87}
88
89static double normalizedAnimationValueForFillsForwards(double iterationCount, Animation::AnimationDirection direction)
90{
91 if (direction == Animation::AnimationDirectionNormal)
92 return 1;
93 if (direction == Animation::AnimationDirectionReverse)
94 return 0;
95 return shouldReverseAnimationValue(direction, iterationCount) ? 1 : 0;
96}
97
98static float applyOpacityAnimation(float fromOpacity, float toOpacity, double progress)
99{
100 // Optimization: special case the edge values (0 and 1).
101 if (progress == 1.0)
102 return toOpacity;
103
104 if (!progress)
105 return fromOpacity;
106
107 return fromOpacity + progress * (toOpacity - fromOpacity);
108}
109
110static TransformationMatrix applyTransformAnimation(const TransformOperations& from, const TransformOperations& to, double progress, const FloatSize& boxSize, bool listsMatch)
111{
112 TransformationMatrix matrix;
113
114 // First frame of an animation.
115 if (!progress) {
116 from.apply(boxSize, matrix);
117 return matrix;
118 }
119
120 // Last frame of an animation.
121 if (progress == 1) {
122 to.apply(boxSize, matrix);
123 return matrix;
124 }
125
126 // If we have incompatible operation lists, we blend the resulting matrices.
127 if (!listsMatch) {
128 TransformationMatrix fromMatrix;
129 to.apply(boxSize, matrix);
130 from.apply(boxSize, fromMatrix);
131 matrix.blend(fromMatrix, progress);
132 return matrix;
133 }
134
135 // Animation to "-webkit-transform: none".
136 if (!to.size()) {
137 TransformOperations blended(from);
138 for (auto& operation : blended.operations())
139 operation->blend(nullptr, progress, true)->apply(matrix, boxSize);
140 return matrix;
141 }
142
143 // Animation from "-webkit-transform: none".
144 if (!from.size()) {
145 TransformOperations blended(to);
146 for (auto& operation : blended.operations())
147 operation->blend(nullptr, 1 - progress, true)->apply(matrix, boxSize);
148 return matrix;
149 }
150
151 // Normal animation with a matching operation list.
152 TransformOperations blended(to);
153 for (size_t i = 0; i < blended.operations().size(); ++i)
154 blended.operations()[i]->blend(from.at(i), progress, !from.at(i))->apply(matrix, boxSize);
155 return matrix;
156}
157
158static const TimingFunction& timingFunctionForAnimationValue(const AnimationValue& animationValue, const Animation& animation)
159{
160 if (animationValue.timingFunction())
161 return *animationValue.timingFunction();
162 if (animation.timingFunction())
163 return *animation.timingFunction();
164 return CubicBezierTimingFunction::defaultTimingFunction();
165}
166
167TextureMapperAnimation::TextureMapperAnimation(const String& name, const KeyframeValueList& keyframes, const FloatSize& boxSize, const Animation& animation, bool listsMatch, MonotonicTime startTime, Seconds pauseTime, AnimationState state)
168 : m_name(name.isSafeToSendToAnotherThread() ? name : name.isolatedCopy())
169 , m_keyframes(keyframes)
170 , m_boxSize(boxSize)
171 , m_animation(Animation::create(animation))
172 , m_listsMatch(listsMatch)
173 , m_startTime(startTime)
174 , m_pauseTime(pauseTime)
175 , m_totalRunningTime(0_s)
176 , m_lastRefreshedTime(m_startTime)
177 , m_state(state)
178{
179}
180
181TextureMapperAnimation::TextureMapperAnimation(const TextureMapperAnimation& other)
182 : m_name(other.m_name.isSafeToSendToAnotherThread() ? other.m_name : other.m_name.isolatedCopy())
183 , m_keyframes(other.m_keyframes)
184 , m_boxSize(other.m_boxSize)
185 , m_animation(Animation::create(*other.m_animation))
186 , m_listsMatch(other.m_listsMatch)
187 , m_startTime(other.m_startTime)
188 , m_pauseTime(other.m_pauseTime)
189 , m_totalRunningTime(other.m_totalRunningTime)
190 , m_lastRefreshedTime(other.m_lastRefreshedTime)
191 , m_state(other.m_state)
192{
193}
194
195void TextureMapperAnimation::apply(ApplicationResult& applicationResults, MonotonicTime time)
196{
197 if (!isActive())
198 return;
199
200 Seconds totalRunningTime = computeTotalRunningTime(time);
201 double normalizedValue = normalizedAnimationValue(totalRunningTime.seconds(), m_animation->duration(), m_animation->direction(), m_animation->iterationCount());
202
203 if (m_animation->iterationCount() != Animation::IterationCountInfinite && totalRunningTime.seconds() >= m_animation->duration() * m_animation->iterationCount()) {
204 m_state = AnimationState::Stopped;
205 m_pauseTime = 0_s;
206 if (m_animation->fillsForwards())
207 normalizedValue = normalizedAnimationValueForFillsForwards(m_animation->iterationCount(), m_animation->direction());
208 }
209
210 applicationResults.hasRunningAnimations |= (m_state == AnimationState::Playing);
211
212 if (!normalizedValue) {
213 applyInternal(applicationResults, m_keyframes.at(0), m_keyframes.at(1), 0);
214 return;
215 }
216
217 if (normalizedValue == 1.0) {
218 applyInternal(applicationResults, m_keyframes.at(m_keyframes.size() - 2), m_keyframes.at(m_keyframes.size() - 1), 1);
219 return;
220 }
221 if (m_keyframes.size() == 2) {
222 auto& timingFunction = timingFunctionForAnimationValue(m_keyframes.at(0), *m_animation);
223 normalizedValue = timingFunction.transformTime(normalizedValue, m_animation->duration());
224 applyInternal(applicationResults, m_keyframes.at(0), m_keyframes.at(1), normalizedValue);
225 return;
226 }
227
228 for (size_t i = 0; i < m_keyframes.size() - 1; ++i) {
229 const AnimationValue& from = m_keyframes.at(i);
230 const AnimationValue& to = m_keyframes.at(i + 1);
231 if (from.keyTime() > normalizedValue || to.keyTime() < normalizedValue)
232 continue;
233
234 normalizedValue = (normalizedValue - from.keyTime()) / (to.keyTime() - from.keyTime());
235 auto& timingFunction = timingFunctionForAnimationValue(from, *m_animation);
236 normalizedValue = timingFunction.transformTime(normalizedValue, m_animation->duration());
237 applyInternal(applicationResults, from, to, normalizedValue);
238 break;
239 }
240}
241
242void TextureMapperAnimation::pause(Seconds time)
243{
244 m_state = AnimationState::Paused;
245 m_pauseTime = time;
246}
247
248void TextureMapperAnimation::resume()
249{
250 m_state = AnimationState::Playing;
251 // FIXME: This seems wrong. m_totalRunningTime is cleared.
252 // https://bugs.webkit.org/show_bug.cgi?id=183113
253 m_pauseTime = 0_s;
254 m_totalRunningTime = m_pauseTime;
255 m_lastRefreshedTime = MonotonicTime::now();
256}
257
258Seconds TextureMapperAnimation::computeTotalRunningTime(MonotonicTime time)
259{
260 if (m_state == AnimationState::Paused)
261 return m_pauseTime;
262
263 MonotonicTime oldLastRefreshedTime = m_lastRefreshedTime;
264 m_lastRefreshedTime = time;
265 m_totalRunningTime += m_lastRefreshedTime - oldLastRefreshedTime;
266 return m_totalRunningTime;
267}
268
269bool TextureMapperAnimation::isActive() const
270{
271 return m_state != AnimationState::Stopped || m_animation->fillsForwards();
272}
273
274void TextureMapperAnimation::applyInternal(ApplicationResult& applicationResults, const AnimationValue& from, const AnimationValue& to, float progress)
275{
276 switch (m_keyframes.property()) {
277 case AnimatedPropertyTransform:
278 applicationResults.transform = applyTransformAnimation(static_cast<const TransformAnimationValue&>(from).value(), static_cast<const TransformAnimationValue&>(to).value(), progress, m_boxSize, m_listsMatch);
279 return;
280 case AnimatedPropertyOpacity:
281 applicationResults.opacity = applyOpacityAnimation((static_cast<const FloatAnimationValue&>(from).value()), (static_cast<const FloatAnimationValue&>(to).value()), progress);
282 return;
283 case AnimatedPropertyFilter:
284 applicationResults.filters = applyFilterAnimation(static_cast<const FilterAnimationValue&>(from).value(), static_cast<const FilterAnimationValue&>(to).value(), progress, m_boxSize);
285 return;
286 default:
287 ASSERT_NOT_REACHED();
288 }
289}
290
291void TextureMapperAnimations::add(const TextureMapperAnimation& animation)
292{
293 // Remove the old state if we are resuming a paused animation.
294 remove(animation.name(), animation.keyframes().property());
295
296 m_animations.append(animation);
297}
298
299void TextureMapperAnimations::remove(const String& name)
300{
301 m_animations.removeAllMatching([&name] (const TextureMapperAnimation& animation) {
302 return animation.name() == name;
303 });
304}
305
306void TextureMapperAnimations::remove(const String& name, AnimatedPropertyID property)
307{
308 m_animations.removeAllMatching([&name, property] (const TextureMapperAnimation& animation) {
309 return animation.name() == name && animation.keyframes().property() == property;
310 });
311}
312
313void TextureMapperAnimations::pause(const String& name, Seconds offset)
314{
315 for (auto& animation : m_animations) {
316 if (animation.name() == name)
317 animation.pause(offset);
318 }
319}
320
321void TextureMapperAnimations::suspend(MonotonicTime time)
322{
323 // FIXME: This seems wrong. `pause` takes time offset (Seconds), not MonotonicTime.
324 // https://bugs.webkit.org/show_bug.cgi?id=183112
325 for (auto& animation : m_animations)
326 animation.pause(time.secondsSinceEpoch());
327}
328
329void TextureMapperAnimations::resume()
330{
331 for (auto& animation : m_animations)
332 animation.resume();
333}
334
335void TextureMapperAnimations::apply(TextureMapperAnimation::ApplicationResult& applicationResults, MonotonicTime time)
336{
337 for (auto& animation : m_animations)
338 animation.apply(applicationResults, time);
339}
340
341bool TextureMapperAnimations::hasActiveAnimationsOfType(AnimatedPropertyID type) const
342{
343 return std::any_of(m_animations.begin(), m_animations.end(),
344 [&type](const TextureMapperAnimation& animation) { return animation.isActive() && animation.keyframes().property() == type; });
345}
346
347bool TextureMapperAnimations::hasRunningAnimations() const
348{
349 return std::any_of(m_animations.begin(), m_animations.end(),
350 [](const TextureMapperAnimation& animation) { return animation.state() == TextureMapperAnimation::AnimationState::Playing; });
351}
352
353TextureMapperAnimations TextureMapperAnimations::getActiveAnimations() const
354{
355 TextureMapperAnimations active;
356 for (auto& animation : m_animations) {
357 if (animation.isActive())
358 active.add(animation);
359 }
360 return active;
361}
362
363} // namespace WebCore
364