1/*
2 * Copyright (C) Research In Motion Limited 2010, 2011. All rights reserved.
3 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "SVGPathBlender.h"
23
24#include "AnimationUtilities.h"
25#include "SVGPathSeg.h"
26#include "SVGPathSource.h"
27#include <wtf/SetForScope.h>
28
29namespace WebCore {
30
31bool SVGPathBlender::addAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, unsigned repeatCount)
32{
33 SVGPathBlender blender(fromSource, toSource, &consumer);
34 return blender.addAnimatedPath(repeatCount);
35}
36
37bool SVGPathBlender::blendAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, float progress)
38{
39 SVGPathBlender blender(fromSource, toSource, &consumer);
40 return blender.blendAnimatedPath(progress);
41}
42
43bool SVGPathBlender::canBlendPaths(SVGPathSource& fromSource, SVGPathSource& toSource)
44{
45 SVGPathBlender blender(fromSource, toSource);
46 return blender.canBlendPaths();
47}
48
49SVGPathBlender::SVGPathBlender(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer* consumer)
50 : m_fromSource(fromSource)
51 , m_toSource(toSource)
52 , m_consumer(consumer)
53{
54}
55
56// Helper functions
57static inline FloatPoint blendFloatPoint(const FloatPoint& a, const FloatPoint& b, float progress)
58{
59 return FloatPoint(blend(a.x(), b.x(), progress), blend(a.y(), b.y(), progress));
60}
61
62float SVGPathBlender::blendAnimatedDimensonalFloat(float from, float to, FloatBlendMode blendMode, float progress)
63{
64 if (m_addTypesCount) {
65 ASSERT(m_fromMode == m_toMode);
66 return from + to * m_addTypesCount;
67 }
68
69 if (m_fromMode == m_toMode)
70 return blend(from, to, progress);
71
72 float fromValue = blendMode == BlendHorizontal ? m_fromCurrentPoint.x() : m_fromCurrentPoint.y();
73 float toValue = blendMode == BlendHorizontal ? m_toCurrentPoint.x() : m_toCurrentPoint.y();
74
75 // Transform toY to the coordinate mode of fromY
76 float animValue = blend(from, m_fromMode == AbsoluteCoordinates ? to + toValue : to - toValue, progress);
77
78 if (m_isInFirstHalfOfAnimation)
79 return animValue;
80
81 // Transform the animated point to the coordinate mode, needed for the current progress.
82 float currentValue = blend(fromValue, toValue, progress);
83 return m_toMode == AbsoluteCoordinates ? animValue + currentValue : animValue - currentValue;
84}
85
86FloatPoint SVGPathBlender::blendAnimatedFloatPoint(const FloatPoint& fromPoint, const FloatPoint& toPoint, float progress)
87{
88 if (m_addTypesCount) {
89 ASSERT(m_fromMode == m_toMode);
90 FloatPoint repeatedToPoint = toPoint;
91 repeatedToPoint.scale(m_addTypesCount);
92 return fromPoint + repeatedToPoint;
93 }
94
95 if (m_fromMode == m_toMode)
96 return blendFloatPoint(fromPoint, toPoint, progress);
97
98 // Transform toPoint to the coordinate mode of fromPoint
99 FloatPoint animatedPoint = toPoint;
100 if (m_fromMode == AbsoluteCoordinates)
101 animatedPoint += m_toCurrentPoint;
102 else
103 animatedPoint.move(-m_toCurrentPoint.x(), -m_toCurrentPoint.y());
104
105 animatedPoint = blendFloatPoint(fromPoint, animatedPoint, progress);
106
107 if (m_isInFirstHalfOfAnimation)
108 return animatedPoint;
109
110 // Transform the animated point to the coordinate mode, needed for the current progress.
111 FloatPoint currentPoint = blendFloatPoint(m_fromCurrentPoint, m_toCurrentPoint, progress);
112 if (m_toMode == AbsoluteCoordinates)
113 return animatedPoint + currentPoint;
114
115 animatedPoint.move(-currentPoint.x(), -currentPoint.y());
116 return animatedPoint;
117}
118
119bool SVGPathBlender::blendMoveToSegment(float progress)
120{
121 FloatPoint fromTargetPoint;
122 FloatPoint toTargetPoint;
123 if ((m_fromSource.hasMoreData() && !m_fromSource.parseMoveToSegment(fromTargetPoint))
124 || !m_toSource.parseMoveToSegment(toTargetPoint))
125 return false;
126
127 if (!m_consumer)
128 return true;
129
130 m_consumer->moveTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), false, m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
131 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
132 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
133 return true;
134}
135
136bool SVGPathBlender::blendLineToSegment(float progress)
137{
138 FloatPoint fromTargetPoint;
139 FloatPoint toTargetPoint;
140 if ((m_fromSource.hasMoreData() && !m_fromSource.parseLineToSegment(fromTargetPoint))
141 || !m_toSource.parseLineToSegment(toTargetPoint))
142 return false;
143
144 if (!m_consumer)
145 return true;
146
147 m_consumer->lineTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
148 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
149 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
150 return true;
151}
152
153bool SVGPathBlender::blendLineToHorizontalSegment(float progress)
154{
155 float fromX = 0;
156 float toX = 0;
157 if ((m_fromSource.hasMoreData() && !m_fromSource.parseLineToHorizontalSegment(fromX))
158 || !m_toSource.parseLineToHorizontalSegment(toX))
159 return false;
160
161 if (!m_consumer)
162 return true;
163
164 m_consumer->lineToHorizontal(blendAnimatedDimensonalFloat(fromX, toX, BlendHorizontal, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
165 m_fromCurrentPoint.setX(m_fromMode == AbsoluteCoordinates ? fromX : m_fromCurrentPoint.x() + fromX);
166 m_toCurrentPoint.setX(m_toMode == AbsoluteCoordinates ? toX : m_toCurrentPoint.x() + toX);
167 return true;
168}
169
170bool SVGPathBlender::blendLineToVerticalSegment(float progress)
171{
172 float fromY = 0;
173 float toY = 0;
174 if ((m_fromSource.hasMoreData() && !m_fromSource.parseLineToVerticalSegment(fromY))
175 || !m_toSource.parseLineToVerticalSegment(toY))
176 return false;
177
178 if (!m_consumer)
179 return true;
180
181 m_consumer->lineToVertical(blendAnimatedDimensonalFloat(fromY, toY, BlendVertical, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
182 m_fromCurrentPoint.setY(m_fromMode == AbsoluteCoordinates ? fromY : m_fromCurrentPoint.y() + fromY);
183 m_toCurrentPoint.setY(m_toMode == AbsoluteCoordinates ? toY : m_toCurrentPoint.y() + toY);
184 return true;
185}
186
187bool SVGPathBlender::blendCurveToCubicSegment(float progress)
188{
189 FloatPoint fromTargetPoint;
190 FloatPoint fromPoint1;
191 FloatPoint fromPoint2;
192 FloatPoint toTargetPoint;
193 FloatPoint toPoint1;
194 FloatPoint toPoint2;
195 if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToCubicSegment(fromPoint1, fromPoint2, fromTargetPoint))
196 || !m_toSource.parseCurveToCubicSegment(toPoint1, toPoint2, toTargetPoint))
197 return false;
198
199 if (!m_consumer)
200 return true;
201
202 m_consumer->curveToCubic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress),
203 blendAnimatedFloatPoint(fromPoint2, toPoint2, progress),
204 blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
205 m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
206 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
207 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
208 return true;
209}
210
211bool SVGPathBlender::blendCurveToCubicSmoothSegment(float progress)
212{
213 FloatPoint fromTargetPoint;
214 FloatPoint fromPoint2;
215 FloatPoint toTargetPoint;
216 FloatPoint toPoint2;
217 if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToCubicSmoothSegment(fromPoint2, fromTargetPoint))
218 || !m_toSource.parseCurveToCubicSmoothSegment(toPoint2, toTargetPoint))
219 return false;
220
221 if (!m_consumer)
222 return true;
223
224 m_consumer->curveToCubicSmooth(blendAnimatedFloatPoint(fromPoint2, toPoint2, progress),
225 blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
226 m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
227 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
228 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
229 return true;
230}
231
232bool SVGPathBlender::blendCurveToQuadraticSegment(float progress)
233{
234 FloatPoint fromTargetPoint;
235 FloatPoint fromPoint1;
236 FloatPoint toTargetPoint;
237 FloatPoint toPoint1;
238 if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToQuadraticSegment(fromPoint1, fromTargetPoint))
239 || !m_toSource.parseCurveToQuadraticSegment(toPoint1, toTargetPoint))
240 return false;
241
242 if (!m_consumer)
243 return true;
244
245 m_consumer->curveToQuadratic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress),
246 blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
247 m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
248 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
249 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
250 return true;
251}
252
253bool SVGPathBlender::blendCurveToQuadraticSmoothSegment(float progress)
254{
255 FloatPoint fromTargetPoint;
256 FloatPoint toTargetPoint;
257 if ((m_fromSource.hasMoreData() && !m_fromSource.parseCurveToQuadraticSmoothSegment(fromTargetPoint))
258 || !m_toSource.parseCurveToQuadraticSmoothSegment(toTargetPoint))
259 return false;
260
261 if (!m_consumer)
262 return true;
263
264 m_consumer->curveToQuadraticSmooth(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
265 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
266 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
267 return true;
268}
269
270bool SVGPathBlender::blendArcToSegment(float progress)
271{
272 float fromRx = 0;
273 float fromRy = 0;
274 float fromAngle = 0;
275 bool fromLargeArc = false;
276 bool fromSweep = false;
277 FloatPoint fromTargetPoint;
278 float toRx = 0;
279 float toRy = 0;
280 float toAngle = 0;
281 bool toLargeArc = false;
282 bool toSweep = false;
283 FloatPoint toTargetPoint;
284 if ((m_fromSource.hasMoreData() && !m_fromSource.parseArcToSegment(fromRx, fromRy, fromAngle, fromLargeArc, fromSweep, fromTargetPoint))
285 || !m_toSource.parseArcToSegment(toRx, toRy, toAngle, toLargeArc, toSweep, toTargetPoint))
286 return false;
287
288 if (!m_consumer)
289 return true;
290
291 if (m_addTypesCount) {
292 ASSERT(m_fromMode == m_toMode);
293 FloatPoint scaledToTargetPoint = toTargetPoint;
294 scaledToTargetPoint.scale(m_addTypesCount);
295 m_consumer->arcTo(fromRx + toRx * m_addTypesCount,
296 fromRy + toRy * m_addTypesCount,
297 fromAngle + toAngle * m_addTypesCount,
298 fromLargeArc || toLargeArc,
299 fromSweep || toSweep,
300 fromTargetPoint + scaledToTargetPoint,
301 m_fromMode);
302 } else {
303 m_consumer->arcTo(blend(fromRx, toRx, progress),
304 blend(fromRy, toRy, progress),
305 blend(fromAngle, toAngle, progress),
306 m_isInFirstHalfOfAnimation ? fromLargeArc : toLargeArc,
307 m_isInFirstHalfOfAnimation ? fromSweep : toSweep,
308 blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
309 m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
310 }
311 m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
312 m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
313 return true;
314}
315
316static inline PathCoordinateMode coordinateModeOfCommand(const SVGPathSegType& type)
317{
318 if (type < PathSegMoveToAbs)
319 return AbsoluteCoordinates;
320
321 // Odd number = relative command
322 if (type % 2)
323 return RelativeCoordinates;
324
325 return AbsoluteCoordinates;
326}
327
328static inline bool isSegmentEqual(const SVGPathSegType& fromType, const SVGPathSegType& toType, const PathCoordinateMode& fromMode, const PathCoordinateMode& toMode)
329{
330 if (fromType == toType && (fromType == PathSegUnknown || fromType == PathSegClosePath))
331 return true;
332
333 unsigned short from = fromType;
334 unsigned short to = toType;
335 if (fromMode == toMode)
336 return from == to;
337 if (fromMode == AbsoluteCoordinates)
338 return from == to - 1;
339 return to == from - 1;
340}
341
342bool SVGPathBlender::addAnimatedPath(unsigned repeatCount)
343{
344 SetForScope<unsigned> change(m_addTypesCount, repeatCount);
345 return blendAnimatedPath(0);
346}
347
348bool SVGPathBlender::canBlendPaths()
349{
350 float progress = 0.5;
351 bool fromSourceHadData = m_fromSource.hasMoreData();
352 while (m_toSource.hasMoreData()) {
353 SVGPathSegType fromCommand;
354 SVGPathSegType toCommand;
355 if ((fromSourceHadData && !m_fromSource.parseSVGSegmentType(fromCommand)) || !m_toSource.parseSVGSegmentType(toCommand))
356 return false;
357
358 m_toMode = coordinateModeOfCommand(toCommand);
359 m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode;
360 if (m_fromMode != m_toMode && m_addTypesCount)
361 return false;
362
363 if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode))
364 return false;
365
366 switch (toCommand) {
367 case PathSegMoveToRel:
368 case PathSegMoveToAbs:
369 if (!blendMoveToSegment(progress))
370 return false;
371 break;
372 case PathSegLineToRel:
373 case PathSegLineToAbs:
374 if (!blendLineToSegment(progress))
375 return false;
376 break;
377 case PathSegLineToHorizontalRel:
378 case PathSegLineToHorizontalAbs:
379 if (!blendLineToHorizontalSegment(progress))
380 return false;
381 break;
382 case PathSegLineToVerticalRel:
383 case PathSegLineToVerticalAbs:
384 if (!blendLineToVerticalSegment(progress))
385 return false;
386 break;
387 case PathSegClosePath:
388 break;
389 case PathSegCurveToCubicRel:
390 case PathSegCurveToCubicAbs:
391 if (!blendCurveToCubicSegment(progress))
392 return false;
393 break;
394 case PathSegCurveToCubicSmoothRel:
395 case PathSegCurveToCubicSmoothAbs:
396 if (!blendCurveToCubicSmoothSegment(progress))
397 return false;
398 break;
399 case PathSegCurveToQuadraticRel:
400 case PathSegCurveToQuadraticAbs:
401 if (!blendCurveToQuadraticSegment(progress))
402 return false;
403 break;
404 case PathSegCurveToQuadraticSmoothRel:
405 case PathSegCurveToQuadraticSmoothAbs:
406 if (!blendCurveToQuadraticSmoothSegment(progress))
407 return false;
408 break;
409 case PathSegArcRel:
410 case PathSegArcAbs:
411 if (!blendArcToSegment(progress))
412 return false;
413 break;
414 case PathSegUnknown:
415 return false;
416 }
417
418 if (!fromSourceHadData)
419 continue;
420 if (m_fromSource.hasMoreData() != m_toSource.hasMoreData())
421 return false;
422 if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData())
423 return true;
424 }
425
426 return true;
427}
428
429bool SVGPathBlender::blendAnimatedPath(float progress)
430{
431 m_isInFirstHalfOfAnimation = progress < 0.5f;
432
433 bool fromSourceHadData = m_fromSource.hasMoreData();
434 while (m_toSource.hasMoreData()) {
435 SVGPathSegType fromCommand;
436 SVGPathSegType toCommand;
437 if ((fromSourceHadData && !m_fromSource.parseSVGSegmentType(fromCommand)) || !m_toSource.parseSVGSegmentType(toCommand))
438 return false;
439
440 m_toMode = coordinateModeOfCommand(toCommand);
441 m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode;
442 if (m_fromMode != m_toMode && m_addTypesCount)
443 return false;
444
445 if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode))
446 return false;
447
448 switch (toCommand) {
449 case PathSegMoveToRel:
450 case PathSegMoveToAbs:
451 if (!blendMoveToSegment(progress))
452 return false;
453 break;
454 case PathSegLineToRel:
455 case PathSegLineToAbs:
456 if (!blendLineToSegment(progress))
457 return false;
458 break;
459 case PathSegLineToHorizontalRel:
460 case PathSegLineToHorizontalAbs:
461 if (!blendLineToHorizontalSegment(progress))
462 return false;
463 break;
464 case PathSegLineToVerticalRel:
465 case PathSegLineToVerticalAbs:
466 if (!blendLineToVerticalSegment(progress))
467 return false;
468 break;
469 case PathSegClosePath:
470 m_consumer->closePath();
471 break;
472 case PathSegCurveToCubicRel:
473 case PathSegCurveToCubicAbs:
474 if (!blendCurveToCubicSegment(progress))
475 return false;
476 break;
477 case PathSegCurveToCubicSmoothRel:
478 case PathSegCurveToCubicSmoothAbs:
479 if (!blendCurveToCubicSmoothSegment(progress))
480 return false;
481 break;
482 case PathSegCurveToQuadraticRel:
483 case PathSegCurveToQuadraticAbs:
484 if (!blendCurveToQuadraticSegment(progress))
485 return false;
486 break;
487 case PathSegCurveToQuadraticSmoothRel:
488 case PathSegCurveToQuadraticSmoothAbs:
489 if (!blendCurveToQuadraticSmoothSegment(progress))
490 return false;
491 break;
492 case PathSegArcRel:
493 case PathSegArcAbs:
494 if (!blendArcToSegment(progress))
495 return false;
496 break;
497 case PathSegUnknown:
498 return false;
499 }
500
501 if (!fromSourceHadData)
502 continue;
503 if (m_fromSource.hasMoreData() != m_toSource.hasMoreData())
504 return false;
505 if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData())
506 return true;
507 }
508
509 return true;
510}
511
512}
513