blob: 717de9d67805ba2448f9c34684be09b086ec473d [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 2010, 2011. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/svg/SVGPathBlender.h"
#include "core/svg/SVGPathByteStreamSource.h"
#include "core/svg/SVGPathConsumer.h"
#include "core/svg/SVGPathData.h"
#include "platform/animation/AnimationUtilities.h"
namespace blink {
enum FloatBlendMode {
BlendHorizontal,
BlendVertical
};
class SVGPathBlender::BlendState {
public:
BlendState(float progress, unsigned addTypesCount = 0)
: m_progress(progress)
, m_addTypesCount(addTypesCount)
, m_isInFirstHalfOfAnimation(progress < 0.5f)
, m_typesAreEqual(false)
, m_fromIsAbsolute(false)
{
}
bool blendSegments(const PathSegmentData& fromSeg, const PathSegmentData& toSeg, PathSegmentData&);
private:
float blendAnimatedDimensonalFloat(float, float, FloatBlendMode);
FloatPoint blendAnimatedFloatPointSameCoordinates(const FloatPoint& from, const FloatPoint& to);
FloatPoint blendAnimatedFloatPoint(const FloatPoint& from, const FloatPoint& to);
bool canBlend(const PathSegmentData& fromSeg, const PathSegmentData& toSeg);
FloatPoint m_fromSubPathPoint;
FloatPoint m_fromCurrentPoint;
FloatPoint m_toSubPathPoint;
FloatPoint m_toCurrentPoint;
float m_progress;
unsigned m_addTypesCount;
bool m_isInFirstHalfOfAnimation;
// This is per-segment blend state corresponding to the 'from' and 'to'
// segments currently being blended, and only used within blendSegments().
bool m_typesAreEqual;
bool m_fromIsAbsolute;
};
// Helper functions
static inline FloatPoint blendFloatPoint(const FloatPoint& a, const FloatPoint& b, float progress)
{
return FloatPoint(blend(a.x(), b.x(), progress), blend(a.y(), b.y(), progress));
}
float SVGPathBlender::BlendState::blendAnimatedDimensonalFloat(float from, float to, FloatBlendMode blendMode)
{
if (m_addTypesCount) {
ASSERT(m_typesAreEqual);
return from + to * m_addTypesCount;
}
if (m_typesAreEqual)
return blend(from, to, m_progress);
float fromValue = blendMode == BlendHorizontal ? m_fromCurrentPoint.x() : m_fromCurrentPoint.y();
float toValue = blendMode == BlendHorizontal ? m_toCurrentPoint.x() : m_toCurrentPoint.y();
// Transform toY to the coordinate mode of fromY
float animValue = blend(from, m_fromIsAbsolute ? to + toValue : to - toValue, m_progress);
// If we're in the first half of the animation, we should use the type of the from segment.
if (m_isInFirstHalfOfAnimation)
return animValue;
// Transform the animated point to the coordinate mode, needed for the current progress.
float currentValue = blend(fromValue, toValue, m_progress);
return !m_fromIsAbsolute ? animValue + currentValue : animValue - currentValue;
}
FloatPoint SVGPathBlender::BlendState::blendAnimatedFloatPointSameCoordinates(const FloatPoint& fromPoint, const FloatPoint& toPoint)
{
if (m_addTypesCount) {
FloatPoint repeatedToPoint = toPoint;
repeatedToPoint.scale(m_addTypesCount, m_addTypesCount);
return fromPoint + repeatedToPoint;
}
return blendFloatPoint(fromPoint, toPoint, m_progress);
}
FloatPoint SVGPathBlender::BlendState::blendAnimatedFloatPoint(const FloatPoint& fromPoint, const FloatPoint& toPoint)
{
if (m_typesAreEqual)
return blendAnimatedFloatPointSameCoordinates(fromPoint, toPoint);
// Transform toPoint to the coordinate mode of fromPoint
FloatPoint animatedPoint = toPoint;
if (m_fromIsAbsolute)
animatedPoint += m_toCurrentPoint;
else
animatedPoint.move(-m_toCurrentPoint.x(), -m_toCurrentPoint.y());
animatedPoint = blendFloatPoint(fromPoint, animatedPoint, m_progress);
// If we're in the first half of the animation, we should use the type of the from segment.
if (m_isInFirstHalfOfAnimation)
return animatedPoint;
// Transform the animated point to the coordinate mode, needed for the current progress.
FloatPoint currentPoint = blendFloatPoint(m_fromCurrentPoint, m_toCurrentPoint, m_progress);
if (!m_fromIsAbsolute)
return animatedPoint + currentPoint;
animatedPoint.move(-currentPoint.x(), -currentPoint.y());
return animatedPoint;
}
bool SVGPathBlender::BlendState::canBlend(const PathSegmentData& fromSeg, const PathSegmentData& toSeg)
{
// Update state first because we'll need it if we return true below.
m_typesAreEqual = fromSeg.command == toSeg.command;
m_fromIsAbsolute = isAbsolutePathSegType(fromSeg.command);
// If the types are equal, they'll blend regardless of parameters.
if (m_typesAreEqual)
return true;
// Addition require segments with the same type.
if (m_addTypesCount)
return false;
// Allow the segments to differ in "relativeness".
return toAbsolutePathSegType(fromSeg.command) == toAbsolutePathSegType(toSeg.command);
}
static void updateCurrentPoint(FloatPoint& subPathPoint, FloatPoint& currentPoint, const PathSegmentData& segment)
{
switch (segment.command) {
case PathSegMoveToRel:
currentPoint += segment.targetPoint;
subPathPoint = currentPoint;
break;
case PathSegLineToRel:
case PathSegCurveToCubicRel:
case PathSegCurveToQuadraticRel:
case PathSegArcRel:
case PathSegLineToHorizontalRel:
case PathSegLineToVerticalRel:
case PathSegCurveToCubicSmoothRel:
case PathSegCurveToQuadraticSmoothRel:
currentPoint += segment.targetPoint;
break;
case PathSegMoveToAbs:
currentPoint = segment.targetPoint;
subPathPoint = currentPoint;
break;
case PathSegLineToAbs:
case PathSegCurveToCubicAbs:
case PathSegCurveToQuadraticAbs:
case PathSegArcAbs:
case PathSegCurveToCubicSmoothAbs:
case PathSegCurveToQuadraticSmoothAbs:
currentPoint = segment.targetPoint;
break;
case PathSegLineToHorizontalAbs:
currentPoint.setX(segment.targetPoint.x());
break;
case PathSegLineToVerticalAbs:
currentPoint.setY(segment.targetPoint.y());
break;
case PathSegClosePath:
currentPoint = subPathPoint;
break;
default:
ASSERT_NOT_REACHED();
}
}
bool SVGPathBlender::BlendState::blendSegments(const PathSegmentData& fromSeg, const PathSegmentData& toSeg, PathSegmentData& blendedSegment)
{
if (!canBlend(fromSeg, toSeg))
return false;
blendedSegment.command = m_isInFirstHalfOfAnimation ? fromSeg.command : toSeg.command;
switch (toSeg.command) {
case PathSegCurveToCubicRel:
case PathSegCurveToCubicAbs:
blendedSegment.point1 = blendAnimatedFloatPoint(fromSeg.point1, toSeg.point1);
/* fall through */
case PathSegCurveToCubicSmoothRel:
case PathSegCurveToCubicSmoothAbs:
blendedSegment.point2 = blendAnimatedFloatPoint(fromSeg.point2, toSeg.point2);
/* fall through */
case PathSegMoveToRel:
case PathSegMoveToAbs:
case PathSegLineToRel:
case PathSegLineToAbs:
case PathSegCurveToQuadraticSmoothRel:
case PathSegCurveToQuadraticSmoothAbs:
blendedSegment.targetPoint = blendAnimatedFloatPoint(fromSeg.targetPoint, toSeg.targetPoint);
break;
case PathSegLineToHorizontalRel:
case PathSegLineToHorizontalAbs:
blendedSegment.targetPoint.setX(blendAnimatedDimensonalFloat(fromSeg.targetPoint.x(), toSeg.targetPoint.x(), BlendHorizontal));
break;
case PathSegLineToVerticalRel:
case PathSegLineToVerticalAbs:
blendedSegment.targetPoint.setY(blendAnimatedDimensonalFloat(fromSeg.targetPoint.y(), toSeg.targetPoint.y(), BlendVertical));
break;
case PathSegClosePath:
break;
case PathSegCurveToQuadraticRel:
case PathSegCurveToQuadraticAbs:
blendedSegment.targetPoint = blendAnimatedFloatPoint(fromSeg.targetPoint, toSeg.targetPoint);
blendedSegment.point1 = blendAnimatedFloatPoint(fromSeg.point1, toSeg.point1);
break;
case PathSegArcRel:
case PathSegArcAbs:
blendedSegment.targetPoint = blendAnimatedFloatPoint(fromSeg.targetPoint, toSeg.targetPoint);
blendedSegment.point1 = blendAnimatedFloatPointSameCoordinates(fromSeg.arcRadii(), toSeg.arcRadii());
blendedSegment.point2 = blendAnimatedFloatPointSameCoordinates(fromSeg.point2, toSeg.point2);
if (m_addTypesCount) {
blendedSegment.arcLarge = fromSeg.arcLarge || toSeg.arcLarge;
blendedSegment.arcSweep = fromSeg.arcSweep || toSeg.arcSweep;
} else {
blendedSegment.arcLarge = m_isInFirstHalfOfAnimation ? fromSeg.arcLarge : toSeg.arcLarge;
blendedSegment.arcSweep = m_isInFirstHalfOfAnimation ? fromSeg.arcSweep : toSeg.arcSweep;
}
break;
default:
ASSERT_NOT_REACHED();
}
updateCurrentPoint(m_fromSubPathPoint, m_fromCurrentPoint, fromSeg);
updateCurrentPoint(m_toSubPathPoint, m_toCurrentPoint, toSeg);
return true;
}
SVGPathBlender::SVGPathBlender(SVGPathByteStreamSource* fromSource, SVGPathByteStreamSource* toSource, SVGPathConsumer* consumer)
: m_fromSource(fromSource)
, m_toSource(toSource)
, m_consumer(consumer)
{
ASSERT(m_fromSource);
ASSERT(m_toSource);
ASSERT(m_consumer);
}
bool SVGPathBlender::addAnimatedPath(unsigned repeatCount)
{
BlendState blendState(0, repeatCount);
return blendAnimatedPath(blendState);
}
bool SVGPathBlender::blendAnimatedPath(float progress)
{
BlendState blendState(progress);
return blendAnimatedPath(blendState);
}
bool SVGPathBlender::blendAnimatedPath(BlendState& blendState)
{
bool fromSourceIsEmpty = !m_fromSource->hasMoreData();
while (m_toSource->hasMoreData()) {
PathSegmentData toSeg = m_toSource->parseSegment();
if (toSeg.command == PathSegUnknown)
return false;
PathSegmentData fromSeg;
fromSeg.command = toSeg.command;
if (m_fromSource->hasMoreData()) {
fromSeg = m_fromSource->parseSegment();
if (fromSeg.command == PathSegUnknown)
return false;
}
PathSegmentData blendedSeg;
if (!blendState.blendSegments(fromSeg, toSeg, blendedSeg))
return false;
m_consumer->emitSegment(blendedSeg);
if (fromSourceIsEmpty)
continue;
if (m_fromSource->hasMoreData() != m_toSource->hasMoreData())
return false;
}
return true;
}
} // namespace blink