Bug 1244590 - Part 7: Calculate paced spacing. r=birtles

Calculate the paced spacing for each Keyframes by the context element. Use the
algorithm in the spec.

MozReview-Commit-ID: HFWQwoKhKWt

--HG--
extra : rebase_source : 40112eb1f722179cfc0a4ccbd188e22bc7af9b9b
This commit is contained in:
Boris Chiou 2016-05-27 18:09:06 +08:00
parent 95267f5a26
commit 34e916b2cb
3 changed files with 313 additions and 21 deletions

View File

@ -475,7 +475,7 @@ KeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
// the specified spacing mode when we generate computed animation property
// values from the keyframes since both operations require a style context
// and need to be performed whenever the style context changes.
KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::distribute);
KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
if (mAnimation && mAnimation->IsRelevant()) {
nsNodeUtils::AnimationChanged(mAnimation);
@ -526,6 +526,12 @@ KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext)
KeyframeUtils::GetComputedKeyframeValues(mKeyframes, mTarget->mElement,
aStyleContext);
if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::paced,
mEffectOptions.mPacedProperty,
computedValues);
}
properties =
KeyframeUtils::GetAnimationPropertiesFromKeyframes(mKeyframes,
computedValues,

View File

@ -30,6 +30,10 @@ namespace mozilla {
//
// ------------------------------------------------------------------
// This is used while calculating paced spacing. If the keyframe is not pacable,
// we set its cumulative distance to kNotPaceable, so we can use this to check.
const double kNotPaceable = -1.0;
// For the aAllowList parameter of AppendStringOrStringSequence and
// GetPropertyValuesPairs.
enum class ListAllowance { eDisallow, eAllow };
@ -392,7 +396,19 @@ RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
nsIDocument* aDocument);
static void
DistributeRange(const Range<Keyframe>& aKeyframes);
DistributeRange(const Range<Keyframe>& aSpacingRange,
const Range<Keyframe>& aRangeToAdjust);
static void
DistributeRange(const Range<Keyframe>& aSpacingRange);
static void
PaceRange(const Range<Keyframe>& aKeyframes,
const Range<double>& aCumulativeDistances);
static nsTArray<double>
GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
nsCSSProperty aProperty);
// ------------------------------------------------------------------
//
@ -460,12 +476,26 @@ KeyframeUtils::GetKeyframesFromObject(JSContext* aCx,
/* static */ void
KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
SpacingMode aSpacingMode)
SpacingMode aSpacingMode,
nsCSSProperty aProperty,
nsTArray<ComputedKeyframeValues>& aComputedValues)
{
if (aKeyframes.IsEmpty()) {
return;
}
nsTArray<double> cumulativeDistances;
if (aSpacingMode == SpacingMode::paced) {
MOZ_ASSERT(IsAnimatableProperty(aProperty),
"Paced property should be animatable");
cumulativeDistances = GetCumulativeDistances(aComputedValues, aProperty);
// Reset the computed offsets if using paced spacing.
for (Keyframe& keyframe : aKeyframes) {
keyframe.mComputedOffset = Keyframe::kComputedOffsetNotSet;
}
}
// If the first keyframe has an unspecified offset, fill it in with 0%.
// If there is only a single keyframe, then it gets 100%.
if (aKeyframes.Length() > 1) {
@ -479,28 +509,87 @@ KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
// Fill in remaining missing offsets.
const Keyframe* const last = aKeyframes.cend() - 1;
RangedPtr<Keyframe> keyframeA(aKeyframes.begin(), aKeyframes.Length());
const RangedPtr<Keyframe> begin(aKeyframes.begin(), aKeyframes.Length());
RangedPtr<Keyframe> keyframeA = begin;
while (keyframeA != last) {
// Find keyframe A and keyframe B *between* which we will apply spacing.
RangedPtr<Keyframe> keyframeB = keyframeA + 1;
while (keyframeB.get()->mOffset.isNothing() && keyframeB != last) {
while (keyframeB->mOffset.isNothing() && keyframeB != last) {
++keyframeB;
}
keyframeB.get()->mComputedOffset = keyframeB.get()->mOffset.valueOr(1.0);
keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
// Fill computed offsets in (keyframe A, keyframe B).
if (aSpacingMode == SpacingMode::distribute) {
// Bug 1276573: Use the new constructor accepting two RangedPtr<T>
// arguments, so we can make the code simpler.
DistributeRange(Range<Keyframe>(keyframeA.get(),
keyframeB - keyframeA + 1));
} else {
// TODO
MOZ_ASSERT(false, "not implement yet");
}
// a) Find Paced A (first paceable keyframe) and
// Paced B (last paceable keyframe) in [keyframe A, keyframe B].
RangedPtr<Keyframe> pacedA = keyframeA;
while (pacedA < keyframeB &&
cumulativeDistances[pacedA - begin] == kNotPaceable) {
++pacedA;
}
RangedPtr<Keyframe> pacedB = keyframeB;
while (pacedB > keyframeA &&
cumulativeDistances[pacedB - begin] == kNotPaceable) {
--pacedB;
}
// As spec says, if there is no paceable keyframe
// in [keyframe A, keyframe B], we let Paced A and Paced B refer to
// keyframe B.
if (pacedA > pacedB) {
pacedA = pacedB = keyframeB;
}
// b) Apply distributing offsets in (keyframe A, Paced A] and
// [Paced B, keyframe B).
DistributeRange(Range<Keyframe>(keyframeA.get(),
keyframeB - keyframeA + 1),
Range<Keyframe>((keyframeA + 1).get(),
pacedA - keyframeA));
DistributeRange(Range<Keyframe>(keyframeA.get(),
keyframeB - keyframeA + 1),
Range<Keyframe>(pacedB.get(),
keyframeB - pacedB));
// c) Apply paced offsets to each paceable keyframe in (Paced A, Paced B).
// We pass the range [Paced A, Paced B] since PaceRange needs the end
// points of the range in order to calculate the correct offset.
PaceRange(Range<Keyframe>(pacedA.get(), pacedB - pacedA + 1),
Range<double>(&cumulativeDistances[pacedA - begin],
pacedB - pacedA + 1));
// d) Fill in any computed offsets in (Paced A, Paced B) that are still
// not set (e.g. because the keyframe was not paceable, or because the
// cumulative distance between paceable properties was zero).
for (RangedPtr<Keyframe> frame = pacedA + 1; frame < pacedB; ++frame) {
if (frame->mComputedOffset != Keyframe::kComputedOffsetNotSet) {
continue;
}
RangedPtr<Keyframe> start = frame - 1;
RangedPtr<Keyframe> end = frame + 1;
while (end < pacedB &&
end->mComputedOffset == Keyframe::kComputedOffsetNotSet) {
++end;
}
DistributeRange(Range<Keyframe>(start.get(), end - start + 1));
frame = end;
}
}
keyframeA = keyframeB;
}
}
/* static */ void
KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes)
{
nsTArray<ComputedKeyframeValues> emptyArray;
ApplySpacing(aKeyframes, SpacingMode::distribute, eCSSProperty_UNKNOWN,
emptyArray);
}
/* static */ nsTArray<ComputedKeyframeValues>
KeyframeUtils::GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
dom::Element* aElement,
@ -1358,22 +1447,203 @@ RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
}
/**
* Evenly distribute the computed offsets in (A, B). We should pass the
* range keyframes in [A, B] and use A, B to calculate computed offsets in
* (A, B).
* Evenly distribute the computed offsets in (A, B).
* We pass the range keyframes in [A, B] and use A, B to calculate distributing
* computed offsets in (A, B). The second range, aRangeToAdjust, is passed, so
* we can know which keyframe we want to apply to. aRangeToAdjust should be in
* the range of aSpacingRange.
*
* @param aKeyframes The sequence of keyframes between whose endpoints we should
* apply distribute spacing.
* @param aSpacingRange The sequence of keyframes between whose endpoints we
* should apply distribute spacing.
* @param aRangeToAdjust The range of keyframes we want to apply to.
*/
static void
DistributeRange(const Range<Keyframe>& aKeyframes)
DistributeRange(const Range<Keyframe>& aSpacingRange,
const Range<Keyframe>& aRangeToAdjust)
{
const size_t n = aKeyframes.length() - 1;
const double startOffset = aKeyframes[0].mComputedOffset;
const double diffOffset = aKeyframes[n].mComputedOffset - startOffset;
for (size_t i = 1; i < n; ++i) {
aKeyframes[i].mComputedOffset = startOffset + double(i) / n * diffOffset;
MOZ_ASSERT(aRangeToAdjust.start() >= aSpacingRange.start() &&
aRangeToAdjust.end() <= aSpacingRange.end(),
"Out of range");
const size_t n = aSpacingRange.length() - 1;
const double startOffset = aSpacingRange[0].mComputedOffset;
const double diffOffset = aSpacingRange[n].mComputedOffset - startOffset;
for (auto iter = aRangeToAdjust.start();
iter != aRangeToAdjust.end();
++iter) {
size_t index = iter - aSpacingRange.start();
iter->mComputedOffset = startOffset + double(index) / n * diffOffset;
}
}
/**
* Overload of DistributeRange to apply distribute spacing to all keyframes in
* between the endpoints of the given range.
*
* @param aSpacingRange The sequence of keyframes between whose endpoints we
* should apply distribute spacing.
*/
static void
DistributeRange(const Range<Keyframe>& aSpacingRange)
{
// We don't need to apply distribute spacing to keyframe A and keyframe B.
DistributeRange(aSpacingRange,
Range<Keyframe>((aSpacingRange.start() + 1).get(),
aSpacingRange.end() - aSpacingRange.start()
- 2));
}
/**
* Apply paced spacing to all paceable keyframes in between the endpoints of the
* given range.
*
* @param aKeyframes The range of keyframes between whose endpoints we should
* apply paced spacing. Both endpoints should be paceable, i.e. the
* corresponding elements in |aCumulativeDist| should not be kNotPaceable.
* Within this function, we refer to the start and end points of this range
* as Paced A and Paced B respectively in keeping with the notation used in
* the spec.
* @param aCumulativeDistances The sequence of cumulative distances of the paced
* property as returned by GetCumulativeDistances(). This acts as a
* parallel range to |aKeyframes|.
*/
static void
PaceRange(const Range<Keyframe>& aKeyframes,
const Range<double>& aCumulativeDistances)
{
MOZ_ASSERT(aKeyframes.length() == aCumulativeDistances.length(),
"Range length mismatch");
const size_t len = aKeyframes.length();
// If there is nothing between the end points, there is nothing to space.
if (len < 3) {
return;
}
const double distA = *(aCumulativeDistances.start());
const double distB = *(aCumulativeDistances.end() - 1);
MOZ_ASSERT(distA != kNotPaceable && distB != kNotPaceable,
"Both Paced A and Paced B should be paceable");
// If the total distance is zero, we should fall back to distribute spacing.
// The caller will fill-in any keyframes without a computed offset using
// distribute spacing so we can just return here.
if (distA == distB) {
return;
}
const RangedPtr<Keyframe> pacedA = aKeyframes.start();
const RangedPtr<Keyframe> pacedB = aKeyframes.end() - 1;
MOZ_ASSERT(pacedA->mComputedOffset != Keyframe::kComputedOffsetNotSet &&
pacedB->mComputedOffset != Keyframe::kComputedOffsetNotSet,
"Both Paced A and Paced B should have valid computed offsets");
// Apply computed offset.
const double offsetA = pacedA->mComputedOffset;
const double diffOffset = pacedB->mComputedOffset - offsetA;
const double initialDist = distA;
const double totalDist = distB - initialDist;
for (auto iter = pacedA + 1; iter != pacedB; ++iter) {
size_t k = iter - aKeyframes.start();
if (aCumulativeDistances[k] == kNotPaceable) {
continue;
}
double dist = aCumulativeDistances[k] - initialDist;
iter->mComputedOffset = offsetA + diffOffset * dist / totalDist;
}
}
/**
* Get cumulative distances for the paced property.
*
* @param aValues The computed values returned by GetComputedKeyframeValues.
* @param aPacedProperty The paced property.
* @return The cumulative distances for the paced property. The length will be
* the same as aValues.
*/
static nsTArray<double>
GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
nsCSSProperty aPacedProperty)
{
// a) If aPacedProperty is a shorthand property, get its components.
// Otherwise, just add the longhand property into the set.
size_t pacedPropertyCount = 0;
nsCSSPropertySet pacedPropertySet;
bool isShorthand = nsCSSProps::IsShorthand(aPacedProperty);
if (isShorthand) {
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPacedProperty,
CSSEnabledState::eForAllContent) {
pacedPropertySet.AddProperty(*p);
++pacedPropertyCount;
}
} else {
pacedPropertySet.AddProperty(aPacedProperty);
pacedPropertyCount = 1;
}
// b) Search each component (shorthand) or the longhand property, and
// calculate the cumulative distances of paceable keyframe pairs.
const size_t len = aValues.Length();
nsTArray<double> cumulativeDistances(len);
// cumulativeDistances is a parallel array to |aValues|, so set its length to
// the length of |aValues|.
cumulativeDistances.SetLength(len);
ComputedKeyframeValues prevPacedValues;
size_t preIdx = 0;
for (size_t i = 0; i < len; ++i) {
// Find computed values of the paced property.
ComputedKeyframeValues pacedValues;
for (const PropertyStyleAnimationValuePair& pair : aValues[i]) {
if (pacedPropertySet.HasProperty(pair.mProperty)) {
pacedValues.AppendElement(pair);
}
}
// Check we have values for all the paceable longhand components.
if (pacedValues.Length() != pacedPropertyCount) {
// This keyframe is not paceable, assign kNotPaceable and skip it.
cumulativeDistances[i] = kNotPaceable;
continue;
}
if (prevPacedValues.IsEmpty()) {
// This is the first paceable keyframe so its cumulative distance is 0.0.
cumulativeDistances[i] = 0.0;
} else {
double dist = 0.0;
if (isShorthand) {
// Apply the distance by the square root of the sum of squares of
// longhand component distances.
for (size_t propIdx = 0; propIdx < pacedPropertyCount; ++propIdx) {
nsCSSProperty prop = prevPacedValues[propIdx].mProperty;
MOZ_ASSERT(pacedValues[propIdx].mProperty == prop,
"Property mismatch");
double componentDistance = 0.0;
if (StyleAnimationValue::ComputeDistance(
prop,
prevPacedValues[propIdx].mValue,
pacedValues[propIdx].mValue,
componentDistance)) {
dist += componentDistance * componentDistance;
}
}
dist = sqrt(dist);
} else {
// If the property is longhand, we just use the 1st value.
// If ComputeDistance() fails, |dist| will remain zero so there will be
// no distance between the previous paced value and this value.
StyleAnimationValue::ComputeDistance(aPacedProperty,
prevPacedValues[0].mValue,
pacedValues[0].mValue,
dist);
}
cumulativeDistances[i] = cumulativeDistances[preIdx] + dist;
}
prevPacedValues.SwapElements(pacedValues);
preIdx = i;
}
return cumulativeDistances;
}
} // namespace mozilla

View File

@ -86,9 +86,25 @@ public:
*
* @param aKeyframes The set of keyframes to adjust.
* @param aSpacingMode The spacing mode to apply.
* @param aProperty The paced property. Only used when |aSpacingMode| is
* SpacingMode::paced. In all other cases it is ignored and hence may be
* any value, e.g. eCSSProperty_UNKNOWN.
* @param aComputedValues The set of computed keyframe values as returned by
* GetComputedKeyframeValues. Only used when |aSpacingMode| is
* SpacingMode::paced. In all other cases this parameter is unused and may
* be any value including an empty array.
*/
static void ApplySpacing(nsTArray<Keyframe>& aKeyframes,
SpacingMode aSpacingMode);
SpacingMode aSpacingMode,
nsCSSProperty aProperty,
nsTArray<ComputedKeyframeValues>& aComputedValues);
/**
* Wrapper for ApplySpacing to simplify using distribute spacing.
*
* @param aKeyframes The set of keyframes to adjust.
*/
static void ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes);
/**
* Converts an array of Keyframe objects into an array of AnimationProperty