GraphicsLayerAnimation.cpp   [plain text]


/*
 Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)

 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 "config.h"

#include "GraphicsLayerAnimation.h"

#include "LayoutSize.h"
#include "UnitBezier.h"
#include <wtf/CurrentTime.h>

namespace WebCore {

#if ENABLE(CSS_FILTERS)
static inline PassRefPtr<FilterOperation> blendFunc(FilterOperation* fromOp, FilterOperation* toOp, double progress, const FloatSize& size, bool blendToPassthrough = false)
{
    ASSERT(toOp);
    if (toOp->blendingNeedsRendererSize())
        return toOp->blend(fromOp, progress, LayoutSize(size), blendToPassthrough);

    return toOp->blend(fromOp, progress, blendToPassthrough);
}


static FilterOperations applyFilterAnimation(const FilterOperations& from, const FilterOperations& to, double progress, const FloatSize& boxSize)
{
    // First frame of an animation.
    if (!progress)
        return from;

    // Last frame of an animation.
    if (progress == 1)
        return to;

    if (!from.isEmpty() && !to.isEmpty() && !from.operationsMatch(to))
        return to;

    FilterOperations result;

    size_t fromSize = from.operations().size();
    size_t toSize = to.operations().size();
    size_t size = std::max(fromSize, toSize);
    for (size_t i = 0; i < size; i++) {
        RefPtr<FilterOperation> fromOp = (i < fromSize) ? from.operations()[i].get() : 0;
        RefPtr<FilterOperation> toOp = (i < toSize) ? to.operations()[i].get() : 0;
        RefPtr<FilterOperation> blendedOp = toOp ? blendFunc(fromOp.get(), toOp.get(), progress, boxSize) : (fromOp ? blendFunc(0, fromOp.get(), progress, boxSize, true) : 0);
        if (blendedOp)
            result.operations().append(blendedOp);
        else {
            RefPtr<FilterOperation> identityOp = PassthroughFilterOperation::create();
            if (progress > 0.5)
                result.operations().append(toOp ? toOp : identityOp);
            else
                result.operations().append(fromOp ? fromOp : identityOp);
        }
    }

    return result;
}
#endif

static bool shouldReverseAnimationValue(Animation::AnimationDirection direction, int loopCount)
{
    if (((direction == Animation::AnimationDirectionAlternate) && (loopCount & 1))
        || ((direction == Animation::AnimationDirectionAlternateReverse) && !(loopCount & 1))
        || direction == Animation::AnimationDirectionReverse)
        return true;
    return false;
}

static double normalizedAnimationValue(double runningTime, double duration, Animation::AnimationDirection direction, double iterationCount)
{
    if (!duration)
        return 0;

    const int loopCount = runningTime / duration;
    const double lastFullLoop = duration * double(loopCount);
    const double remainder = runningTime - lastFullLoop;
    // Ignore remainder when we've reached the end of animation.
    const double normalized = (loopCount == iterationCount) ? 1.0 : (remainder / duration);

    return shouldReverseAnimationValue(direction, loopCount) ? 1 - normalized : normalized;
}

static double normalizedAnimationValueForFillsForwards(double iterationCount, Animation::AnimationDirection direction)
{
    if (direction == Animation::AnimationDirectionNormal)
        return 1;
    if (direction == Animation::AnimationDirectionReverse)
        return 0;
    return shouldReverseAnimationValue(direction, iterationCount) ? 1 : 0;
}

static float applyOpacityAnimation(float fromOpacity, float toOpacity, double progress)
{
    // Optimization: special case the edge values (0 and 1).
    if (progress == 1.0)
        return toOpacity;

    if (!progress)
        return fromOpacity;

    return fromOpacity + progress * (toOpacity - fromOpacity);
}

static inline double solveEpsilon(double duration)
{
    return 1.0 / (200.0 * duration);
}

static inline double solveCubicBezierFunction(double p1x, double p1y, double p2x, double p2y, double t, double duration)
{
    return UnitBezier(p1x, p1y, p2x, p2y).solve(t, solveEpsilon(duration));
}

static inline double solveStepsFunction(int numSteps, bool stepAtStart, double t)
{
    if (stepAtStart)
        return std::min(1.0, (floor(numSteps * t) + 1) / numSteps);
    return floor(numSteps * t) / numSteps;
}

static inline float applyTimingFunction(const TimingFunction* timingFunction, float progress, double duration)
{
    if (!timingFunction)
        return progress;

    if (timingFunction->isCubicBezierTimingFunction()) {
        const CubicBezierTimingFunction* ctf = static_cast<const CubicBezierTimingFunction*>(timingFunction);
        return solveCubicBezierFunction(ctf->x1(), ctf->y1(), ctf->x2(), ctf->y2(), progress, duration);
    }

    if (timingFunction->isStepsTimingFunction()) {
        const StepsTimingFunction* stf = static_cast<const StepsTimingFunction*>(timingFunction);
        return solveStepsFunction(stf->numberOfSteps(), stf->stepAtStart(), double(progress));
    }

    return progress;
}

static TransformationMatrix applyTransformAnimation(const TransformOperations& from, const TransformOperations& to, double progress, const FloatSize& boxSize, bool listsMatch)
{
    TransformationMatrix matrix;

    // First frame of an animation.
    if (!progress) {
        from.apply(boxSize, matrix);
        return matrix;
    }

    // Last frame of an animation.
    if (progress == 1) {
        to.apply(boxSize, matrix);
        return matrix;
    }

    // If we have incompatible operation lists, we blend the resulting matrices.
    if (!listsMatch) {
        TransformationMatrix fromMatrix;
        to.apply(boxSize, matrix);
        from.apply(boxSize, fromMatrix);
        matrix.blend(fromMatrix, progress);
        return matrix;
    }

    // Animation to "-webkit-transform: none".
    if (!to.size()) {
        TransformOperations blended(from);
        for (size_t i = 0; i < blended.operations().size(); ++i)
            blended.operations()[i]->blend(0, progress, true)->apply(matrix, boxSize);
        return matrix;
    }

    // Animation from "-webkit-transform: none".
    if (!from.size()) {
        TransformOperations blended(to);
        for (size_t i = 0; i < blended.operations().size(); ++i)
            blended.operations()[i]->blend(0, 1. - progress, true)->apply(matrix, boxSize);
        return matrix;
    }

    // Normal animation with a matching operation list.
    TransformOperations blended(to);
    for (size_t i = 0; i < blended.operations().size(); ++i)
        blended.operations()[i]->blend(from.at(i), progress, !from.at(i))->apply(matrix, boxSize);
    return matrix;
}

static const TimingFunction* timingFunctionForAnimationValue(const AnimationValue& animValue, const Animation* anim)
{
    if (animValue.timingFunction())
        return animValue.timingFunction();
    if (anim->timingFunction())
        return anim->timingFunction().get();

    return CubicBezierTimingFunction::defaultTimingFunction();
}

GraphicsLayerAnimation::GraphicsLayerAnimation(const String& name, const KeyframeValueList& keyframes, const FloatSize& boxSize, const Animation* animation, double startTime, bool listsMatch)
    : m_keyframes(keyframes)
    , m_boxSize(boxSize)
    , m_animation(Animation::create(*animation))
    , m_name(name)
    , m_listsMatch(listsMatch)
    , m_startTime(startTime)
    , m_pauseTime(0)
    , m_totalRunningTime(0)
    , m_lastRefreshedTime(m_startTime)
    , m_state(PlayingState)
{
}

void GraphicsLayerAnimation::applyInternal(Client* client, const AnimationValue& from, const AnimationValue& to, float progress)
{
    switch (m_keyframes.property()) {
    case AnimatedPropertyOpacity:
        client->setAnimatedOpacity(applyOpacityAnimation((static_cast<const FloatAnimationValue&>(from).value()), (static_cast<const FloatAnimationValue&>(to).value()), progress));
        return;
    case AnimatedPropertyWebkitTransform:
        client->setAnimatedTransform(applyTransformAnimation(static_cast<const TransformAnimationValue&>(from).value(), static_cast<const TransformAnimationValue&>(to).value(), progress, m_boxSize, m_listsMatch));
        return;
#if ENABLE(CSS_FILTERS)
    case AnimatedPropertyWebkitFilter:
        client->setAnimatedFilters(applyFilterAnimation(static_cast<const FilterAnimationValue&>(from).value(), static_cast<const FilterAnimationValue&>(to).value(), progress, m_boxSize));
        return;
#endif
    default:
        ASSERT_NOT_REACHED();
    }
}

bool GraphicsLayerAnimation::isActive() const
{
    if (state() != StoppedState)
        return true;

    return m_animation->fillsForwards();
}

bool GraphicsLayerAnimations::hasActiveAnimationsOfType(AnimatedPropertyID type) const
{
    for (size_t i = 0; i < m_animations.size(); ++i) {
        if (m_animations[i].isActive() && m_animations[i].property() == type)
            return true;
    }
    return false;
}

bool GraphicsLayerAnimations::hasRunningAnimations() const
{
    for (size_t i = 0; i < m_animations.size(); ++i) {
        if (m_animations[i].state() == GraphicsLayerAnimation::PlayingState)
            return true;
    }

    return false;
}

void GraphicsLayerAnimation::apply(Client* client)
{
    if (!isActive())
        return;

    double totalRunningTime = computeTotalRunningTime();
    double normalizedValue = normalizedAnimationValue(totalRunningTime, m_animation->duration(), m_animation->direction(), m_animation->iterationCount());

    if (m_animation->iterationCount() != Animation::IterationCountInfinite && totalRunningTime >= m_animation->duration() * m_animation->iterationCount()) {
        setState(StoppedState);
        if (m_animation->fillsForwards())
            normalizedValue = normalizedAnimationValueForFillsForwards(m_animation->iterationCount(), m_animation->direction());
    }

    if (!normalizedValue) {
        applyInternal(client, m_keyframes.at(0), m_keyframes.at(1), 0);
        return;
    }

    if (normalizedValue == 1.0) {
        applyInternal(client, m_keyframes.at(m_keyframes.size() - 2), m_keyframes.at(m_keyframes.size() - 1), 1);
        return;
    }
    if (m_keyframes.size() == 2) {
        const TimingFunction* timingFunction = timingFunctionForAnimationValue(m_keyframes.at(0), m_animation.get());
        normalizedValue = applyTimingFunction(timingFunction, normalizedValue, m_animation->duration());
        applyInternal(client, m_keyframes.at(0), m_keyframes.at(1), normalizedValue);
        return;
    }

    for (size_t i = 0; i < m_keyframes.size() - 1; ++i) {
        const AnimationValue& from = m_keyframes.at(i);
        const AnimationValue& to = m_keyframes.at(i + 1);
        if (from.keyTime() > normalizedValue || to.keyTime() < normalizedValue)
            continue;

        normalizedValue = (normalizedValue - from.keyTime()) / (to.keyTime() - from.keyTime());
        const TimingFunction* timingFunction = timingFunctionForAnimationValue(from, m_animation.get());
        normalizedValue = applyTimingFunction(timingFunction, normalizedValue, m_animation->duration());
        applyInternal(client, from, to, normalizedValue);
        break;
    }
}

double GraphicsLayerAnimation::computeTotalRunningTime()
{
    if (state() == PausedState)
        return m_pauseTime;

    double oldLastRefreshedTime = m_lastRefreshedTime;
    m_lastRefreshedTime = monotonicallyIncreasingTime();
    m_totalRunningTime += m_lastRefreshedTime - oldLastRefreshedTime;
    return m_totalRunningTime;
}

void GraphicsLayerAnimation::pause(double time)
{
    setState(PausedState);
    m_pauseTime = time;
}

void GraphicsLayerAnimation::resume()
{
    setState(PlayingState);
    m_totalRunningTime = m_pauseTime;
    m_lastRefreshedTime = monotonicallyIncreasingTime();
}

void GraphicsLayerAnimations::add(const GraphicsLayerAnimation& animation)
{
    // Remove the old state if we are resuming a paused animation.
    remove(animation.name(), animation.property());

    m_animations.append(animation);
}

void GraphicsLayerAnimations::pause(const String& name, double offset)
{
    for (size_t i = 0; i < m_animations.size(); ++i) {
        if (m_animations[i].name() == name)
            m_animations[i].pause(offset);
    }
}

void GraphicsLayerAnimations::suspend(double offset)
{
    for (size_t i = 0; i < m_animations.size(); ++i)
        m_animations[i].pause(offset);
}

void GraphicsLayerAnimations::resume()
{
    for (size_t i = 0; i < m_animations.size(); ++i)
        m_animations[i].resume();
}

void GraphicsLayerAnimations::remove(const String& name)
{
    for (int i = m_animations.size() - 1; i >= 0; --i) {
        if (m_animations[i].name() == name)
            m_animations.remove(i);
    }
}

void GraphicsLayerAnimations::remove(const String& name, AnimatedPropertyID property)
{
    for (int i = m_animations.size() - 1; i >= 0; --i) {
        if (m_animations[i].name() == name && m_animations[i].property() == property)
            m_animations.remove(i);
    }
}

void GraphicsLayerAnimations::apply(GraphicsLayerAnimation::Client* client)
{
    for (size_t i = 0; i < m_animations.size(); ++i)
        m_animations[i].apply(client);
}

GraphicsLayerAnimations GraphicsLayerAnimations::getActiveAnimations() const
{
    GraphicsLayerAnimations active;
    for (size_t i = 0; i < m_animations.size(); ++i) {
        if (m_animations[i].isActive())
            active.add(m_animations[i]);
    }
    return active;
}
}