PRBool nsSMILAnimationFunction::WillReplace() const { /* * In IsAdditive() we don't consider to-animation to be additive as it is * a special case that is dealt with differently in the compositing method but * here we return false for to animation as it builds on the underlying value * unless its a frozen to animation. */ return !mErrorFlags && (!(IsAdditive() || IsToAnimation()) || (IsToAnimation() && mIsFrozen && !mHasChanged)); }
bool nsSMILAnimationFunction::WillReplace() const { /* * In IsAdditive() we don't consider to-animation to be additive as it is * a special case that is dealt with differently in the compositing method. * Here, however, we return FALSE for to-animation (i.e. it will NOT replace * the underlying value) as it builds on the underlying value. */ return !mErrorFlags && !(IsAdditive() || IsToAnimation()); }
/** * Performs checks for the keyTimes attribute required by the SMIL spec but * which depend on other attributes and therefore needs to be updated as * dependent attributes are set. */ void nsSMILAnimationFunction::CheckKeyTimes(PRUint32 aNumValues) { if (!HasAttr(nsGkAtoms::keyTimes)) return; // attribute is ignored for calcMode = paced if (GetCalcMode() == CALC_PACED) { SetKeyTimesErrorFlag(PR_FALSE); return; } if (mKeyTimes.Length() < 1) { // keyTimes isn't set or failed preliminary checks SetKeyTimesErrorFlag(PR_TRUE); return; } // no. keyTimes == no. values if ((mKeyTimes.Length() != aNumValues && !IsToAnimation()) || (IsToAnimation() && mKeyTimes.Length() != 2)) { SetKeyTimesErrorFlag(PR_TRUE); return; } // special handling if there is only one keyTime. The spec doesn't say what to // do in this case so we allow the keyTime to be either 0 or 1. if (mKeyTimes.Length() == 1) { double time = mKeyTimes[0]; SetKeyTimesErrorFlag(!(time == 0.0 || time == 1.0)); return; } // According to the spec, the first value should be 0 and for linear or spline // calcMode's the last value should be 1, but then an example is give with // a spline calcMode and keyTimes "0.0; 0.7". So we don't bother checking // the end-values here but just allow bad specs. SetKeyTimesErrorFlag(PR_FALSE); }
nsresult nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult) { if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { const nsSMILValue& lastValue = aValues[aValues.Length() - 1]; // If the target attribute type doesn't support addition, Add will // fail and we leave aResult untouched. aResult.Add(lastValue, mRepeatIteration); } return NS_OK; }
void nsSMILAnimationFunction::CheckKeySplines(PRUint32 aNumValues) { // attribute is ignored if calc mode is not spline if (GetCalcMode() != CALC_SPLINE) { SetKeySplinesErrorFlag(PR_FALSE); return; } // calc mode is spline but the attribute is not set if (!HasAttr(nsGkAtoms::keySplines)) { SetKeySplinesErrorFlag(PR_FALSE); return; } if (mKeySplines.Length() < 1) { // keyTimes isn't set or failed preliminary checks SetKeySplinesErrorFlag(PR_TRUE); return; } // ignore splines if there's only one value if (aNumValues == 1 && !IsToAnimation()) { SetKeySplinesErrorFlag(PR_FALSE); return; } // no. keySpline specs == no. values - 1 PRUint32 splineSpecs = mKeySplines.Length(); if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) || (IsToAnimation() && splineSpecs != 1)) { SetKeySplinesErrorFlag(PR_TRUE); return; } SetKeySplinesErrorFlag(PR_FALSE); }
/** * Performs checks for the keyTimes attribute required by the SMIL spec but * which depend on other attributes and therefore needs to be updated as * dependent attributes are set. */ void nsSMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues) { if (!HasAttr(nsGkAtoms::keyTimes)) return; nsSMILCalcMode calcMode = GetCalcMode(); // attribute is ignored for calcMode = paced if (calcMode == CALC_PACED) { SetKeyTimesErrorFlag(false); return; } uint32_t numKeyTimes = mKeyTimes.Length(); if (numKeyTimes < 1) { // keyTimes isn't set or failed preliminary checks SetKeyTimesErrorFlag(true); return; } // no. keyTimes == no. values // For to-animation the number of values is considered to be 2. bool matchingNumOfValues = numKeyTimes == (IsToAnimation() ? 2 : aNumValues); if (!matchingNumOfValues) { SetKeyTimesErrorFlag(true); return; } // first value must be 0 if (mKeyTimes[0] != 0.0) { SetKeyTimesErrorFlag(true); return; } // last value must be 1 for linear or spline calcModes if (calcMode != CALC_DISCRETE && numKeyTimes > 1 && mKeyTimes[numKeyTimes - 1] != 1.0) { SetKeyTimesErrorFlag(true); return; } SetKeyTimesErrorFlag(false); }
nsresult nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult, nsSMILValue& aBaseValue) { nsresult rv = NS_OK; const nsSMILTime& dur = mSimpleDuration.GetMillis(); // Sanity Checks NS_ABORT_IF_FALSE(mSampleTime >= 0.0f, "Sample time should not be negative"); NS_ABORT_IF_FALSE(dur >= 0.0f, "Simple duration should not be negative"); if (mSampleTime >= dur || mSampleTime < 0.0f) { NS_ERROR("Animation sampled outside interval"); return NS_ERROR_FAILURE; } if ((!IsToAnimation() && aValues.Length() < 2) || (IsToAnimation() && aValues.Length() != 1)) { NS_ERROR("Unexpected number of values"); return NS_ERROR_FAILURE; } // End Sanity Checks double fTime = double(mSampleTime); double fDur = double(dur); // Get the normalised progress through the simple duration double simpleProgress = (fDur > 0.0) ? fTime / fDur : 0.0; // Handle bad keytimes (where first != 0 and/or last != 1) // See http://brian.sol1.net/svg/range-for-keytimes for more info. if (HasAttr(nsGkAtoms::keyTimes) && GetCalcMode() != CALC_PACED) { double first = mKeyTimes[0]; if (first > 0.0 && simpleProgress < first) { if (!IsToAnimation()) aResult = aValues[0]; return rv; } double last = mKeyTimes[mKeyTimes.Length() - 1]; if (last < 1.0 && simpleProgress >= last) { if (IsToAnimation()) aResult = aValues[0]; else aResult = aValues[aValues.Length() - 1]; return rv; } } if (GetCalcMode() != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nsnull; const nsSMILValue* to = nsnull; double intervalProgress; if (IsToAnimation()) { from = &aBaseValue; to = &aValues[0]; if (GetCalcMode() == CALC_PACED) { // Note: key[Times/Splines/Points] are ignored for calcMode="paced" intervalProgress = simpleProgress; } else { ScaleSimpleProgress(simpleProgress); intervalProgress = simpleProgress; ScaleIntervalProgress(intervalProgress, 0, 1); } } else { if (GetCalcMode() == CALC_PACED) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was // presumably due to the values being non-additive) } else { // GetCalcMode() == CALC_LINEAR or GetCalcMode() == CALC_SPLINE ScaleSimpleProgress(simpleProgress); PRUint32 index = (PRUint32)floor(simpleProgress * (aValues.Length() - 1)); from = &aValues[index]; to = &aValues[index + 1]; intervalProgress = simpleProgress * (aValues.Length() - 1) - index; ScaleIntervalProgress(intervalProgress, index, aValues.Length() - 1); } } if (NS_SUCCEEDED(rv)) { NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation"); NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation"); NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f, "Interval progress should be in the range [0, 1)"); rv = from->Interpolate(*to, intervalProgress, aResult); } } // Discrete-CalcMode case // Note: If interpolation failed (isn't supported for this type), the SVG // spec says to force discrete mode. if (GetCalcMode() == CALC_DISCRETE || NS_FAILED(rv)) { if (IsToAnimation()) { // SMIL 3, 12.6.4: Since a to animation has only 1 value, a discrete to // animation will simply set the to value for the simple duration. aResult = aValues[0]; } else { PRUint32 index = (PRUint32) floor(simpleProgress * (aValues.Length())); aResult = aValues[index]; } rv = NS_OK; } return rv; }
void nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr, nsSMILValue& aResult) { mHasChanged = PR_FALSE; // Skip animations that are inactive or in error if (!IsActiveOrFrozen() || mErrorFlags != 0) return; // Get the animation values nsSMILValueArray values; nsresult rv = GetValues(aSMILAttr, values); if (NS_FAILED(rv)) return; // Check that we have the right number of keySplines and keyTimes CheckValueListDependentAttrs(values.Length()); if (mErrorFlags != 0) return; // If this interval is active, we must have a non-negative mSampleTime NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive, "Negative sample time for active animation"); NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mSimpleDuration.IsIndefinite() || mLastValue, "Unresolved simple duration for active or frozen animation"); nsSMILValue result(aResult.mType); if (mSimpleDuration.IsIndefinite() || (values.Length() == 1 && TreatSingleValueAsStatic())) { // Indefinite duration or only one value set: Always set the first value result = values[0]; } else if (mLastValue) { // Sampling last value const nsSMILValue& last = values[values.Length() - 1]; result = last; // See comment in AccumulateResult: to-animation does not accumulate if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { // If the target attribute type doesn't support addition Add will // fail leaving result = last result.Add(last, mRepeatIteration); } } else if (!mFrozenValue.IsNull() && !mHasChanged) { // Frozen to animation result = mFrozenValue; } else { // Interpolation if (NS_FAILED(InterpolateResult(values, result, aResult))) return; if (NS_FAILED(AccumulateResult(values, result))) return; if (IsToAnimation() && mIsFrozen) { mFrozenValue = result; } } // If additive animation isn't required or isn't supported, set the value. if (!IsAdditive() || NS_FAILED(aResult.SandwichAdd(result))) { aResult.Swap(result); // Note: The old value of aResult is now in |result|, and it will get // cleaned up when |result| goes out of scope, when this function returns. } }
nsresult nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult, nsSMILValue& aBaseValue) { // Sanity check animation values if ((!IsToAnimation() && aValues.Length() < 2) || (IsToAnimation() && aValues.Length() != 1)) { NS_ERROR("Unexpected number of values"); return NS_ERROR_FAILURE; } if (IsToAnimation() && aBaseValue.IsNull()) { return NS_ERROR_FAILURE; } // Get the normalised progress through the simple duration. // // If we have an indefinite simple duration, just set the progress to be // 0 which will give us the expected behaviour of the animation being fixed at // its starting point. double simpleProgress = 0.0; if (mSimpleDuration.IsDefinite()) { nsSMILTime dur = mSimpleDuration.GetMillis(); NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative"); NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative"); if (mSampleTime >= dur || mSampleTime < 0) { NS_ERROR("Animation sampled outside interval"); return NS_ERROR_FAILURE; } if (dur > 0) { simpleProgress = (double)mSampleTime / dur; } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0) } nsresult rv = NS_OK; nsSMILCalcMode calcMode = GetCalcMode(); if (calcMode != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nullptr; const nsSMILValue* to = nullptr; // Init to -1 to make sure that if we ever forget to set this, the // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail. double intervalProgress = -1.f; if (IsToAnimation()) { from = &aBaseValue; to = &aValues[0]; if (calcMode == CALC_PACED) { // Note: key[Times/Splines/Points] are ignored for calcMode="paced" intervalProgress = simpleProgress; } else { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0); } } else if (calcMode == CALC_PACED) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was // presumably due to the values being non-additive) } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); uint32_t index = (uint32_t)floor(scaledSimpleProgress * (aValues.Length() - 1)); from = &aValues[index]; to = &aValues[index + 1]; intervalProgress = scaledSimpleProgress * (aValues.Length() - 1) - index; intervalProgress = ScaleIntervalProgress(intervalProgress, index); } if (NS_SUCCEEDED(rv)) { NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation"); NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation"); NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f, "Interval progress should be in the range [0, 1)"); rv = from->Interpolate(*to, intervalProgress, aResult); } } // Discrete-CalcMode case // Note: If interpolation failed (isn't supported for this type), the SVG // spec says to force discrete mode. if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, CALC_DISCRETE); // Floating-point errors can mean that, for example, a sample time of 29s in // a 100s duration animation gives us a simple progress of 0.28999999999 // instead of the 0.29 we'd expect. Normally this isn't a noticeable // problem, but when we have sudden jumps in animation values (such as is // the case here with discrete animation) we can get unexpected results. // // To counteract this, before we perform a floor() on the animation // progress, we add a tiny fudge factor to push us into the correct interval // in cases where floating-point errors might cause us to fall short. static const double kFloatingPointFudgeFactor = 1.0e-16; if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) { scaledSimpleProgress += kFloatingPointFudgeFactor; } if (IsToAnimation()) { // We don't follow SMIL 3, 12.6.4, where discrete to animations // are the same as <set> animations. Instead, we treat it as a // discrete animation with two values (the underlying value and // the to="" value), and honor keyTimes="" as well. uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2); aResult = index == 0 ? aBaseValue : aValues[0]; } else { uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length()); aResult = aValues[index]; } rv = NS_OK; } return rv; }
void nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr, nsSMILValue& aResult) { mHasChanged = false; mPrevSampleWasSingleValueAnimation = false; mWasSkippedInPrevSample = false; // Skip animations that are inactive or in error if (!IsActiveOrFrozen() || mErrorFlags != 0) return; // Get the animation values nsSMILValueArray values; nsresult rv = GetValues(aSMILAttr, values); if (NS_FAILED(rv)) return; // Check that we have the right number of keySplines and keyTimes CheckValueListDependentAttrs(values.Length()); if (mErrorFlags != 0) return; // If this interval is active, we must have a non-negative mSampleTime NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive, "Negative sample time for active animation"); NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mLastValue, "Unresolved simple duration for active or frozen animation"); // If we want to add but don't have a base value then just fail outright. // This can happen when we skipped getting the base value because there's an // animation function in the sandwich that should replace it but that function // failed unexpectedly. bool isAdditive = IsAdditive(); if (isAdditive && aResult.IsNull()) return; nsSMILValue result; if (values.Length() == 1 && !IsToAnimation()) { // Single-valued animation result = values[0]; mPrevSampleWasSingleValueAnimation = true; } else if (mLastValue) { // Sampling last value const nsSMILValue& last = values[values.Length() - 1]; result = last; // See comment in AccumulateResult: to-animation does not accumulate if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { // If the target attribute type doesn't support addition Add will // fail leaving result = last result.Add(last, mRepeatIteration); } } else { // Interpolation if (NS_FAILED(InterpolateResult(values, result, aResult))) return; if (NS_FAILED(AccumulateResult(values, result))) return; } // If additive animation isn't required or isn't supported, set the value. if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) { aResult.Swap(result); // Note: The old value of aResult is now in |result|, and it will get // cleaned up when |result| goes out of scope, when this function returns. } }