LayerAnimation.cpp   [plain text]


/*
 * Copyright (C) 2011, 2012 Research In Motion Limited. All rights reserved.
 * Copyright (C) 2007, 2008, 2009 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.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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 "LayerAnimation.h"

#include "IdentityTransformOperation.h"
#include "LayerCompositingThread.h"
#include "TransformationMatrix.h"
#include "UnitBezier.h"

#include <algorithm>

namespace WebCore {

using namespace std;

// FIXME: Some functions below are copied from AnimationBase and KeyframeAnimation.
// We need to refactor these code to increase code reuse.
// https://bugs.webkit.org/show_bug.cgi?id=82293

// The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds. The longer the
// animation, the more precision we need in the timing function result to avoid ugly discontinuities.
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)
{
    // Convert from input time to parametric value in curve, then from
    // that to output time.
    UnitBezier bezier(p1x, p1y, p2x, p2y);
    return bezier.solve(t, solveEpsilon(duration));
}

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

static const TimingFunction* timingFunctionForAnimationValue(const AnimationValue* animValue, const LayerAnimation* anim)
{
    if (animValue->timingFunction())
        return animValue->timingFunction();
    if (anim->timingFunction())
        return anim->timingFunction();

    return CubicBezierTimingFunction::defaultTimingFunction();
}

static double progress(double elapsedTime, const LayerAnimation* layerAnimation, double scale, double offset, const TimingFunction* tf)
{
    double dur = layerAnimation->duration();
    if (layerAnimation->iterationCount() > 0)
        dur *= layerAnimation->iterationCount();

    if (!layerAnimation->duration())
        return 1.0;
    if (layerAnimation->iterationCount() > 0 && elapsedTime >= dur)
        return (layerAnimation->iterationCount() % 2) ? 1.0 : 0.0;

    // Compute the fractional time, taking into account direction.
    // There is no need to worry about iterations, we assume that we would have
    // short circuited above if we were done.
    double fractionalTime = elapsedTime / layerAnimation->duration();
    int integralTime = static_cast<int>(fractionalTime);
    fractionalTime -= integralTime;

    if ((layerAnimation->direction() == Animation::AnimationDirectionAlternate) && (integralTime & 1))
        fractionalTime = 1 - fractionalTime;

    if (scale != 1 || offset)
        fractionalTime = (fractionalTime - offset) * scale;

    if (!tf)
        tf = layerAnimation->timingFunction();

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

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

    return fractionalTime;
}

static void fetchIntervalEndpoints(double elapsedTime, const LayerAnimation* layerAnimation, const AnimationValue*& fromValue, const AnimationValue*& toValue, double& prog)
{
    // Find the first key.
    if (layerAnimation->duration() && layerAnimation->iterationCount() != Animation::IterationCountInfinite)
        elapsedTime = min(elapsedTime, layerAnimation->duration() * layerAnimation->iterationCount());

    double fractionalTime = layerAnimation->duration() ? (elapsedTime / layerAnimation->duration()) : 1;

    // FIXME: startTime can be before the current animation "frame" time. This is to sync with the frame time
    // concept in AnimationTimeController. So we need to somehow sync the two. Until then, the possible
    // error is small and will probably not be noticeable. Until we fix this, remove the assert.
    // https://bugs.webkit.org/show_bug.cgi?id=52037
    // ASSERT(fractionalTime >= 0);
    if (fractionalTime < 0)
        fractionalTime = 0;

    // FIXME: share this code with AnimationBase::progress().
    int iteration = static_cast<int>(fractionalTime);
    if (layerAnimation->iterationCount() != Animation::IterationCountInfinite)
        iteration = min(iteration, layerAnimation->iterationCount() - 1);

    fractionalTime -= iteration;

    bool reversing = (layerAnimation->direction() == Animation::AnimationDirectionAlternate) && (iteration & 1);
    if (reversing)
        fractionalTime = 1 - fractionalTime;

    size_t numKeyframes = layerAnimation->valueCount();
    if (!numKeyframes)
        return;

    ASSERT(!layerAnimation->valueAt(0)->keyTime());
    ASSERT(layerAnimation->valueAt(layerAnimation->valueCount() - 1)->keyTime() == 1);

    int prevIndex = -1;
    int nextIndex = -1;

    // FIXME: with a lot of keys, this linear search will be slow. We could binary search.
    for (size_t i = 0; i < numKeyframes; ++i) {
        const AnimationValue* currKeyframe = layerAnimation->valueAt(i);

        if (fractionalTime < currKeyframe->keyTime()) {
            nextIndex = i;
            break;
        }

        prevIndex = i;
    }

    double scale = 1;
    double offset = 0;

    if (prevIndex == -1)
        prevIndex = 0;

    if (nextIndex == -1)
        nextIndex = layerAnimation->valueCount() - 1;

    const AnimationValue* prevKeyframe = layerAnimation->valueAt(prevIndex);
    const AnimationValue* nextKeyframe = layerAnimation->valueAt(nextIndex);

    fromValue = prevKeyframe;
    toValue = nextKeyframe;

    offset = prevKeyframe->keyTime();
    scale = 1.0 / (nextKeyframe->keyTime() - prevKeyframe->keyTime());

    const TimingFunction* timingFunction = timingFunctionForAnimationValue(prevKeyframe, layerAnimation);

    prog = progress(elapsedTime, layerAnimation, scale, offset, timingFunction);
}

void LayerAnimation::apply(LayerCompositingThread* layer, double elapsedTime)
{
    const AnimationValue* from = 0;
    const AnimationValue* to = 0;
    double progress = 0.0;

    fetchIntervalEndpoints(elapsedTime, this, from, to, progress);

    switch (property()) {
    case AnimatedPropertyWebkitTransform:
        layer->setTransform(blendTransform(static_cast<const TransformAnimationValue*>(from)->value(), static_cast<const TransformAnimationValue*>(to)->value(), progress));
        break;
    case AnimatedPropertyOpacity:
        layer->setOpacity(blendOpacity(static_cast<const FloatAnimationValue*>(from)->value(), static_cast<const FloatAnimationValue*>(to)->value(), progress));
        break;
    case AnimatedPropertyBackgroundColor:
    case AnimatedPropertyWebkitFilter:
    case AnimatedPropertyInvalid:
        ASSERT_NOT_REACHED();
        break;
    }
}

TransformationMatrix LayerAnimation::blendTransform(const TransformOperations* from, const TransformOperations* to, double progress) const
{
    TransformationMatrix t;

    if (m_transformFunctionListValid) {
        // A trick to avoid touching the refcount of shared TransformOperations on the wrong thread.
        // Since TransforOperation is not ThreadSafeRefCounted, we are only allowed to touch the ref
        // count of shared operations when the WebKit thread and compositing thread are in sync.
        Vector<TransformOperation*> result;
        Vector<RefPtr<TransformOperation> > owned;

        unsigned fromSize = from->operations().size();
        unsigned toSize = to->operations().size();
        unsigned size = max(fromSize, toSize);
        for (unsigned i = 0; i < size; i++) {
            TransformOperation* fromOp = (i < fromSize) ? from->operations()[i].get() : 0;
            TransformOperation* toOp = (i < toSize) ? to->operations()[i].get() : 0;
            RefPtr<TransformOperation> blendedOp = toOp ? toOp->blend(fromOp, progress) : (fromOp ? fromOp->blend(0, progress, true) : PassRefPtr<TransformOperation>(0));
            if (blendedOp) {
                result.append(blendedOp.get());
                owned.append(blendedOp);
            } else {
                RefPtr<TransformOperation> identityOp = IdentityTransformOperation::create();
                owned.append(identityOp);
                if (progress > 0.5)
                    result.append(toOp ? toOp : identityOp.get());
                else
                    result.append(fromOp ? fromOp : identityOp.get());
            }
        }

        IntSize sz = boxSize();
        for (unsigned i = 0; i < result.size(); ++i)
            result[i]->apply(t, sz);
    } else {
        // Convert the TransformOperations into matrices.
        TransformationMatrix fromT;
        from->apply(boxSize(), fromT);
        to->apply(boxSize(), t);

        t.blend(fromT, progress);
    }

    return t;
}

float LayerAnimation::blendOpacity(float from, float to, double progress) const
{
    float opacity = from + (to - from) * progress;

    return max(0.0f, min(opacity, 1.0f));
}

void LayerAnimation::validateTransformLists()
{
    m_transformFunctionListValid = false;

    if (m_values.size() < 2 || property() != AnimatedPropertyWebkitTransform)
        return;

    // Empty transforms match anything, so find the first non-empty entry as the reference.
    size_t numKeyframes = m_values.size();
    size_t firstNonEmptyTransformKeyframeIndex = numKeyframes;

    for (size_t i = 0; i < numKeyframes; ++i) {
        const TransformAnimationValue* currentKeyframe = static_cast<const TransformAnimationValue*>(m_values.at(i));
        if (currentKeyframe->value()->size()) {
            firstNonEmptyTransformKeyframeIndex = i;
            break;
        }
    }

    if (firstNonEmptyTransformKeyframeIndex == numKeyframes)
        return;

    const TransformOperations* firstVal = static_cast<const TransformAnimationValue*>(m_values.at(firstNonEmptyTransformKeyframeIndex))->value();

    // See if the keyframes are valid.
    for (size_t i = firstNonEmptyTransformKeyframeIndex + 1; i < numKeyframes; ++i) {
        const TransformAnimationValue* currentKeyframe = static_cast<const TransformAnimationValue*>(m_values.at(i));
        const TransformOperations* val = currentKeyframe->value();

        // A null transform matches anything.
        if (val->operations().isEmpty())
            continue;

        // If the sizes of the function lists don't match, the lists don't match.
        if (firstVal->operations().size() != val->operations().size())
            return;

        // If the types of each function are not the same, the lists don't match.
        for (size_t j = 0; j < firstVal->operations().size(); ++j) {
            if (!firstVal->operations()[j]->isSameType(*val->operations()[j]))
                return;
        }
    }

    // Keyframes are valid.
    m_transformFunctionListValid = true;
}

}