blob: bea5fb33494cdb4ffad8dc3b40025d395420a2b8 [file] [log] [blame]
/*
* Copyright (C) 2013 Google 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
* OWNER OR 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 "core/animation/KeyframeAnimationEffect.h"
#include "wtf/MathExtras.h"
#include "wtf/text/StringHash.h"
namespace {
using namespace WebCore;
class ReplaceCompositableValue : public AnimationEffect::CompositableValue {
public:
static PassRefPtr<ReplaceCompositableValue> create(const AnimatableValue* value)
{
return adoptRef(new ReplaceCompositableValue(value));
}
virtual PassRefPtr<AnimatableValue> compositeOnto(const AnimatableValue* underlyingValue) const
{
return PassRefPtr<AnimatableValue>(m_value);
}
private:
ReplaceCompositableValue(const AnimatableValue* value)
: m_value(const_cast<AnimatableValue*>(value))
{
}
RefPtr<AnimatableValue> m_value;
};
class AddCompositableValue : public AnimationEffect::CompositableValue {
public:
static PassRefPtr<AddCompositableValue> create(const AnimatableValue* value)
{
return adoptRef(new AddCompositableValue(value));
}
virtual PassRefPtr<AnimatableValue> compositeOnto(const AnimatableValue* underlyingValue) const
{
return AnimatableValue::add(underlyingValue, m_value.get());
}
private:
AddCompositableValue(const AnimatableValue* value)
: m_value(const_cast<AnimatableValue*>(value))
{
}
RefPtr<AnimatableValue> m_value;
};
class BlendedCompositableValue : public AnimationEffect::CompositableValue {
public:
static PassRefPtr<BlendedCompositableValue> create(const AnimationEffect::CompositableValue* before, const AnimationEffect::CompositableValue* after, double fraction)
{
return adoptRef(new BlendedCompositableValue(before, after, fraction));
}
virtual PassRefPtr<AnimatableValue> compositeOnto(const AnimatableValue* underlyingValue) const
{
return AnimatableValue::interpolate(m_before->compositeOnto(underlyingValue).get(), m_after->compositeOnto(underlyingValue).get(), m_fraction);
}
private:
BlendedCompositableValue(const AnimationEffect::CompositableValue* before, const AnimationEffect::CompositableValue* after, double fraction)
: m_before(const_cast<AnimationEffect::CompositableValue*>(before))
, m_after(const_cast<AnimationEffect::CompositableValue*>(after))
, m_fraction(fraction)
{ }
RefPtr<AnimationEffect::CompositableValue> m_before;
RefPtr<AnimationEffect::CompositableValue> m_after;
double m_fraction;
};
bool compareOffsets(const RefPtr<Keyframe>& a, const RefPtr<Keyframe>& b)
{
return a->offset() < b->offset();
}
} // namespace
namespace WebCore {
Keyframe::Keyframe()
: m_offset(std::numeric_limits<double>::quiet_NaN())
, m_composite(AnimationEffect::CompositeReplace)
{ }
void Keyframe::setPropertyValue(CSSPropertyID property, const AnimatableValue* value)
{
m_propertyValues.add(property, const_cast<AnimatableValue*>(value));
}
const AnimatableValue* Keyframe::propertyValue(CSSPropertyID property) const
{
ASSERT(m_propertyValues.contains(property));
return m_propertyValues.get(property);
}
PropertySet Keyframe::properties() const
{
// This is only used when setting up the keyframe groups, so there's no
// need to cache the result.
PropertySet properties;
for (PropertyValueMap::const_iterator iter = m_propertyValues.begin(); iter != m_propertyValues.end(); ++iter)
properties.add(*iter.keys());
return properties;
}
KeyframeAnimationEffect::KeyframeAnimationEffect(const KeyframeVector& keyframes)
: m_keyframes(keyframes)
{
}
PassOwnPtr<AnimationEffect::CompositableValueMap> KeyframeAnimationEffect::sample(int iteration, double fraction) const
{
const_cast<KeyframeAnimationEffect*>(this)->ensureKeyframeGroups();
OwnPtr<CompositableValueMap> map = adoptPtr(new CompositableValueMap());
for (KeyframeGroupMap::const_iterator iter = m_keyframeGroups->begin(); iter != m_keyframeGroups->end(); ++iter)
map->add(iter->key, iter->value->sample(iteration, fraction));
return map.release();
}
KeyframeAnimationEffect::KeyframeVector KeyframeAnimationEffect::normalizedKeyframes() const
{
KeyframeVector keyframes = m_keyframes;
// Set offsets at 0.0 and 1.0 at ends if unset.
if (keyframes.size() >= 2) {
Keyframe* firstKeyframe = keyframes.first().get();
if (std::isnan(firstKeyframe->offset()))
firstKeyframe->setOffset(0.0);
}
if (keyframes.size() >= 1) {
Keyframe* lastKeyframe = keyframes.last().get();
if (lastKeyframe && std::isnan(lastKeyframe->offset()))
lastKeyframe->setOffset(1.0);
}
// FIXME: Distribute offsets where missing.
for (KeyframeVector::iterator iter = keyframes.begin(); iter != keyframes.end(); ++iter)
ASSERT(!std::isnan((*iter)->offset()));
// Sort by offset.
std::stable_sort(keyframes.begin(), keyframes.end(), compareOffsets);
return keyframes;
}
void KeyframeAnimationEffect::ensureKeyframeGroups()
{
if (m_keyframeGroups)
return;
m_keyframeGroups = adoptPtr(new KeyframeGroupMap);
const KeyframeVector& keyframes = normalizedKeyframes();
for (KeyframeVector::const_iterator keyframeIter = keyframes.begin(); keyframeIter != keyframes.end(); ++keyframeIter) {
const Keyframe* keyframe = keyframeIter->get();
PropertySet keyframeProperties = keyframe->properties();
for (PropertySet::const_iterator propertyIter = keyframeProperties.begin(); propertyIter != keyframeProperties.end(); ++propertyIter) {
CSSPropertyID property = *propertyIter;
KeyframeGroupMap::iterator groupIter = m_keyframeGroups->find(property);
if (groupIter == m_keyframeGroups->end()) {
KeyframeGroupMap::AddResult result = m_keyframeGroups->add(property, adoptPtr(new PropertySpecificKeyframeGroup));
ASSERT(result.isNewEntry);
groupIter = result.iterator;
}
groupIter->value->appendKeyframe(adoptPtr(
new PropertySpecificKeyframe(keyframe->offset(), keyframe->propertyValue(property), keyframe->composite())));
}
}
// Add synthetic keyframes.
for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_keyframeGroups->end(); ++iter) {
iter->value->addSyntheticKeyframeIfRequired();
iter->value->removeRedundantKeyframes();
}
}
KeyframeAnimationEffect::PropertySpecificKeyframe::PropertySpecificKeyframe(double offset, const AnimatableValue* value, CompositeOperation composite)
: m_offset(offset)
, m_value(composite == AnimationEffect::CompositeReplace ?
static_cast<PassRefPtr<CompositableValue> >(ReplaceCompositableValue::create(value)) :
static_cast<PassRefPtr<CompositableValue> >(AddCompositableValue::create(value)))
{
}
KeyframeAnimationEffect::PropertySpecificKeyframe::PropertySpecificKeyframe(double offset, PassRefPtr<CompositableValue> value)
: m_offset(offset)
, m_value(value)
{
ASSERT(!std::isnan(m_offset));
}
PassOwnPtr<KeyframeAnimationEffect::PropertySpecificKeyframe> KeyframeAnimationEffect::PropertySpecificKeyframe::cloneWithOffset(double offset) const
{
return adoptPtr(new PropertySpecificKeyframe(offset, PassRefPtr<CompositableValue>(m_value)));
}
void KeyframeAnimationEffect::PropertySpecificKeyframeGroup::appendKeyframe(PassOwnPtr<PropertySpecificKeyframe> keyframe)
{
ASSERT(m_keyframes.isEmpty() || m_keyframes.last()->offset() <= keyframe->offset());
m_keyframes.append(keyframe);
}
void KeyframeAnimationEffect::PropertySpecificKeyframeGroup::removeRedundantKeyframes()
{
// As an optimization, removes keyframes in the following categories, as
// they will never be used by sample().
// - End keyframes with the same offset as their neighbor
// - Interior keyframes with the same offset as both their neighbors
// Note that synthetic keyframes must be added before this method is
// called.
ASSERT(m_keyframes.size() >= 2);
for (int i = m_keyframes.size() - 1; i >= 0; --i) {
double offset = m_keyframes[i]->offset();
bool hasSameOffsetAsPreviousNeighbor = !i || m_keyframes[i - 1]->offset() == offset;
bool hasSameOffsetAsNextNeighbor = i == m_keyframes.size() - 1 || m_keyframes[i + 1]->offset() == offset;
if (hasSameOffsetAsPreviousNeighbor && hasSameOffsetAsNextNeighbor)
m_keyframes.remove(i);
}
ASSERT(m_keyframes.size() >= 2);
}
void KeyframeAnimationEffect::PropertySpecificKeyframeGroup::addSyntheticKeyframeIfRequired()
{
ASSERT(!m_keyframes.isEmpty());
double offset = m_keyframes.first()->offset();
bool allOffsetsEqual = true;
for (PropertySpecificKeyframeVector::const_iterator iter = m_keyframes.begin() + 1; iter != m_keyframes.end(); ++iter) {
if ((*iter)->offset() != offset) {
allOffsetsEqual = false;
break;
}
}
if (!allOffsetsEqual)
return;
if (!offset)
appendKeyframe(m_keyframes.first()->cloneWithOffset(1.0));
else
m_keyframes.insert(0, adoptPtr(new PropertySpecificKeyframe(0.0, AnimatableValue::neutralValue(), CompositeAdd)));
}
PassRefPtr<AnimationEffect::CompositableValue> KeyframeAnimationEffect::PropertySpecificKeyframeGroup::sample(int iteration, double offset) const
{
// FIXME: Implement accumulation.
ASSERT_UNUSED(iteration, !iteration);
double minimumOffset = m_keyframes.first()->offset();
double maximumOffset = m_keyframes.last()->offset();
ASSERT(minimumOffset != maximumOffset);
PropertySpecificKeyframeVector::const_iterator before;
PropertySpecificKeyframeVector::const_iterator after;
// Note that this algorithm is simpler than that in the spec because we
// have removed keyframes with equal offsets in
// removeRedundantKeyframes().
if (offset < minimumOffset) {
before = m_keyframes.begin();
after = before + 1;
ASSERT((*before)->offset() > offset);
ASSERT((*after)->offset() > offset);
} else if (offset >= maximumOffset) {
after = m_keyframes.end() - 1;
before = after - 1;
ASSERT((*before)->offset() < offset);
ASSERT((*after)->offset() <= offset);
} else {
// FIXME: This is inefficient for large numbers of keyframes. Consider
// using binary search.
after = m_keyframes.begin();
while ((*after)->offset() <= offset)
++after;
before = after - 1;
ASSERT((*before)->offset() <= offset);
ASSERT((*after)->offset() > offset);
}
if ((*before)->offset() == offset)
return const_cast<CompositableValue*>((*before)->value());
if ((*after)->offset() == offset)
return const_cast<CompositableValue*>((*after)->value());
return BlendedCompositableValue::create((*before)->value(), (*after)->value(),
(offset - (*before)->offset()) / ((*after)->offset() - (*before)->offset()));
}
} // namespace