Commit cc208c1a authored by Stephen McGruer's avatar Stephen McGruer Committed by Commit Bot

Implement accumulatative composition for filter

This CL implements the accumulative operation described in
https://github.com/w3c/fxtf-drafts/issues/371.

Bug: 788440
Change-Id: I0ecf0a29110650ef5166b92bc0b8f1396c4b18dd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1830568
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Commit-Queue: Alan Cutter <alancutter@chromium.org>
Reviewed-by: default avatarAlan Cutter <alancutter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#701075}
parent 177a25e5
......@@ -235,8 +235,8 @@ void CSSFilterListInterpolationType::Composite(
double underlying_fraction,
const InterpolationValue& value,
double interpolation_fraction) const {
// We do our actual compositing behavior in |MakeAdditive|; see the
// documentation on that method.
// We do our compositing behavior in |PreInterpolationCompositeIfNeeded|; see
// the documentation on that method.
underlying_value_owner.Set(*this, value);
}
......@@ -258,36 +258,86 @@ void CSSFilterListInterpolationType::ApplyStandardPropertyValue(
SetFilterList(CssProperty(), *state.Style(), std::move(filter_operations));
}
InterpolationValue CSSFilterListInterpolationType::MakeAdditive(
InterpolationValue
CSSFilterListInterpolationType::PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const {
const InterpolationValue& underlying,
EffectModel::CompositeOperation composite) const {
DCHECK(!value.non_interpolable_value);
DCHECK(!underlying.non_interpolable_value);
// By default, the interpolation stack attempts to optimize composition by
// doing it after interpolation. This does not work in the case of filter
// lists, as they have a composition behavior of concatenation. To work around
// that, we hackily perform our composition in MakeAdditive (which runs before
// interpolation), and then make Composite a simple replacement of the
// underlying value (which we have already incorporated here).
// that, we perform our composition in PreInterpolationCompositeIfNeeded
// (which runs before interpolation), and then make Composite a simple
// replacement with the resultant value.
// The underlying value can be nullptr, most commonly if it contains a url().
// TODO(crbug.com/1009229): Properly handle url() in filter composite.
if (!underlying.interpolable_value)
return nullptr;
const InterpolableList& interpolable_list =
ToInterpolableList(*value.interpolable_value);
auto interpolable_list = std::unique_ptr<InterpolableList>(
ToInterpolableList(value.interpolable_value.release()));
const InterpolableList& underlying_list =
ToInterpolableList(*underlying.interpolable_value);
if (composite == EffectModel::CompositeOperation::kCompositeAdd) {
return PerformAdditiveComposition(std::move(interpolable_list),
underlying_list);
}
DCHECK_EQ(composite, EffectModel::CompositeOperation::kCompositeAccumulate);
return PerformAccumulativeComposition(std::move(interpolable_list),
underlying_list);
}
InterpolationValue CSSFilterListInterpolationType::PerformAdditiveComposition(
std::unique_ptr<InterpolableList> interpolable_list,
const InterpolableList& underlying_list) const {
// Per the spec, addition of filter lists is defined as concatenation.
// https://drafts.fxtf.org/filter-effects-1/#addition
auto composited_list = std::make_unique<InterpolableList>(
underlying_list.length() + interpolable_list.length());
underlying_list.length() + interpolable_list->length());
for (wtf_size_t i = 0; i < composited_list->length(); i++) {
if (i < underlying_list.length()) {
composited_list->Set(i, underlying_list.Get(i)->Clone());
} else {
composited_list->Set(
i, interpolable_list.Get(i - underlying_list.length())->Clone());
i, interpolable_list->Get(i - underlying_list.length())->Clone());
}
}
return InterpolationValue(std::move(composited_list));
}
InterpolationValue
CSSFilterListInterpolationType::PerformAccumulativeComposition(
std::unique_ptr<InterpolableList> interpolable_list,
const InterpolableList& underlying_list) const {
// Per the spec, accumulation of filter lists operates on pairwise addition of
// the underlying components.
// https://drafts.fxtf.org/filter-effects-1/#accumulation
wtf_size_t length = interpolable_list->length();
wtf_size_t underlying_length = underlying_list.length();
// If any of the types don't match, fallback to replace behavior.
for (wtf_size_t i = 0; i < underlying_length && i < length; i++) {
if (To<InterpolableFilter>(underlying_list.Get(i))->GetType() !=
To<InterpolableFilter>(interpolable_list->Get(i))->GetType())
return InterpolationValue(std::move(interpolable_list));
}
// Otherwise, arithmetically combine the matching prefix of the lists then
// concatenate the remainder of the longer one.
wtf_size_t max_length = std::max(length, underlying_length);
auto composited_list = std::make_unique<InterpolableList>(max_length);
for (wtf_size_t i = 0; i < max_length; i++) {
if (i < underlying_length) {
composited_list->Set(i, underlying_list.Get(i)->Clone());
if (i < length)
composited_list->GetMutable(i)->Add(*interpolable_list->Get(i));
} else {
composited_list->Set(i, interpolable_list->Get(i)->Clone());
}
}
......
......@@ -26,9 +26,10 @@ class CSSFilterListInterpolationType : public CSSInterpolationType {
void ApplyStandardPropertyValue(const InterpolableValue&,
const NonInterpolableValue*,
StyleResolverState&) const final;
InterpolationValue MakeAdditive(
InterpolationValue PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const final;
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const final;
private:
InterpolationValue MaybeConvertNeutral(const InterpolationValue& underlying,
......@@ -40,6 +41,16 @@ class CSSFilterListInterpolationType : public CSSInterpolationType {
InterpolationValue MaybeConvertValue(const CSSValue&,
const StyleResolverState*,
ConversionCheckers&) const final;
// Helper methods to perform either additive or accumulative composition, as
// defined in https://drafts.fxtf.org/filter-effects-1/#addition and
// https://drafts.fxtf.org/filter-effects-1/#accumulation
InterpolationValue PerformAdditiveComposition(
std::unique_ptr<InterpolableList> interpolable_list,
const InterpolableList& underlying_list) const;
InterpolationValue PerformAccumulativeComposition(
std::unique_ptr<InterpolableList> interpolable_list,
const InterpolableList& underlying_list) const;
};
} // namespace blink
......
......@@ -141,7 +141,8 @@ InterpolationValue CSSInterpolationType::MaybeConvertSingle(
keyframe, environment, underlying, conversion_checkers);
if (result && keyframe.Composite() !=
EffectModel::CompositeOperation::kCompositeReplace) {
return MakeAdditive(std::move(result), underlying);
return PreInterpolationCompositeIfNeeded(std::move(result), underlying,
keyframe.Composite());
}
return result;
}
......
......@@ -63,9 +63,18 @@ class CORE_EXPORT CSSInterpolationType : public InterpolationType {
const InterpolationValue& underlying,
ConversionCheckers&) const final;
virtual InterpolationValue MakeAdditive(
// The interpolation stack has an optimization where we perform compositing
// after interpolation. This is against spec, but it works for simple addition
// cases and halves the amount of computation needed. Some types require
// compositing before interpolation (e.g. if their composition operator is a
// concatenation), however, and for those we define this method that is called
// pre-interpolation.
// TODO(crbug.com/1009230): Revisit the post-interpolation composite
// optimization.
virtual InterpolationValue PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const {
const InterpolationValue& underlying,
EffectModel::CompositeOperation composite) const {
return value;
}
......
......@@ -212,9 +212,11 @@ InterpolationValue CSSRotateInterpolationType::MaybeConvertValue(
OptionalRotation(StyleBuilderConverter::ConvertRotation(value)));
}
InterpolationValue CSSRotateInterpolationType::MakeAdditive(
InterpolationValue
CSSRotateInterpolationType::PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const {
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const {
value.non_interpolable_value = CSSRotateNonInterpolableValue::CreateAdditive(
ToCSSRotateNonInterpolableValue(*value.non_interpolable_value));
return value;
......
......@@ -39,9 +39,10 @@ class CSSRotateInterpolationType : public CSSInterpolationType {
InterpolationValue MaybeConvertValue(const CSSValue&,
const StyleResolverState*,
ConversionCheckers&) const final;
InterpolationValue MakeAdditive(
InterpolationValue PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const final;
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const final;
};
} // namespace blink
......
......@@ -193,9 +193,10 @@ InterpolationValue CSSScaleInterpolationType::MaybeConvertValue(
}
}
InterpolationValue CSSScaleInterpolationType::MakeAdditive(
InterpolationValue CSSScaleInterpolationType::PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const {
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const {
value.non_interpolable_value = CSSScaleNonInterpolableValue::CreateAdditive(
ToCSSScaleNonInterpolableValue(*value.non_interpolable_value));
return value;
......
......@@ -36,9 +36,10 @@ class CSSScaleInterpolationType : public CSSInterpolationType {
InterpolationValue MaybeConvertValue(const CSSValue&,
const StyleResolverState*,
ConversionCheckers&) const final;
InterpolationValue MakeAdditive(
InterpolationValue PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const final;
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const final;
PairwiseInterpolationValue MaybeMergeSingles(
InterpolationValue&&,
......
......@@ -250,9 +250,11 @@ InterpolationValue CSSTransformInterpolationType::MaybeConvertValue(
return ConvertTransform(std::move(transform));
}
InterpolationValue CSSTransformInterpolationType::MakeAdditive(
InterpolationValue
CSSTransformInterpolationType::PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const {
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const {
value.non_interpolable_value =
CSSTransformNonInterpolableValue::CreateAdditive(
ToCSSTransformNonInterpolableValue(*value.non_interpolable_value));
......
......@@ -39,9 +39,10 @@ class CSSTransformInterpolationType : public CSSInterpolationType {
InterpolationValue MaybeConvertValue(const CSSValue&,
const StyleResolverState*,
ConversionCheckers&) const final;
InterpolationValue MakeAdditive(
InterpolationValue PreInterpolationCompositeIfNeeded(
InterpolationValue value,
const InterpolationValue& underlying) const final;
const InterpolationValue& underlying,
EffectModel::CompositeOperation) const final;
};
} // namespace blink
......
......@@ -208,6 +208,25 @@ FilterOperation* InterpolableFilter::CreateFilterOperation(
}
}
void InterpolableFilter::Add(const InterpolableValue& other) {
value_->Add(*To<InterpolableFilter>(other).value_);
// The following types have an initial value of 1, so addition for them is
// one-based: result = value_ + other.value_ - 1
switch (type_) {
case FilterOperation::BRIGHTNESS:
case FilterOperation::CONTRAST:
case FilterOperation::GRAYSCALE:
case FilterOperation::INVERT:
case FilterOperation::OPACITY:
case FilterOperation::SATURATE:
case FilterOperation::SEPIA:
value_->Add(*std::make_unique<InterpolableNumber>(-1));
break;
default:
break;
}
}
void InterpolableFilter::AssertCanInterpolateWith(
const InterpolableValue& other) const {
const InterpolableFilter& other_filter = To<InterpolableFilter>(other);
......
......@@ -50,9 +50,7 @@ class CORE_EXPORT InterpolableFilter final : public InterpolableValue {
return false;
}
void Scale(double scale) final { NOTREACHED(); }
void ScaleAndAdd(double scale, const InterpolableValue& other) final {
NOTREACHED();
}
void Add(const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
private:
......
......@@ -306,6 +306,23 @@ void InterpolableLength::Scale(double scale) {
expression_, NumberNode(scale), CSSMathOperator::kMultiply));
}
void InterpolableLength::Add(const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
if (IsLengthArray() && other_length.IsLengthArray()) {
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
length_array_.values[i] =
length_array_.values[i] + other_length.length_array_.values[i];
}
length_array_.type_flags |= other_length.length_array_.type_flags;
return;
}
CSSMathExpressionNode* result =
CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), &other_length.AsExpression(), CSSMathOperator::kAdd);
SetExpression(*result);
}
void InterpolableLength::ScaleAndAdd(double scale,
const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
......
......@@ -66,6 +66,8 @@ class CORE_EXPORT InterpolableLength final : public InterpolableValue {
return false;
}
void Scale(double scale) final;
void Add(const InterpolableValue& other) final;
// We override this to avoid two passes in the case of LengthArrays.
void ScaleAndAdd(double scale, const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
......
......@@ -166,14 +166,13 @@ void InterpolableShadow::Scale(double scale) {
color_->Scale(scale);
}
void InterpolableShadow::ScaleAndAdd(double scale,
const InterpolableValue& other) {
void InterpolableShadow::Add(const InterpolableValue& other) {
const InterpolableShadow& other_shadow = To<InterpolableShadow>(other);
x_->ScaleAndAdd(scale, *other_shadow.x_);
y_->ScaleAndAdd(scale, *other_shadow.y_);
blur_->ScaleAndAdd(scale, *other_shadow.blur_);
spread_->ScaleAndAdd(scale, *other_shadow.spread_);
color_->ScaleAndAdd(scale, *other_shadow.color_);
x_->Add(*other_shadow.x_);
y_->Add(*other_shadow.y_);
blur_->Add(*other_shadow.blur_);
spread_->Add(*other_shadow.spread_);
color_->Add(*other_shadow.color_);
}
void InterpolableShadow::AssertCanInterpolateWith(
......
......@@ -65,7 +65,7 @@ class InterpolableShadow : public InterpolableValue {
return false;
}
void Scale(double scale) final;
void ScaleAndAdd(double scale, const InterpolableValue& other) final;
void Add(const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
private:
......
......@@ -79,9 +79,15 @@ void InterpolableList::Scale(double scale) {
values_[i]->Scale(scale);
}
void InterpolableNumber::ScaleAndAdd(double scale,
const InterpolableValue& other) {
value_ = value_ * scale + ToInterpolableNumber(other).value_;
void InterpolableNumber::Add(const InterpolableValue& other) {
value_ += ToInterpolableNumber(other).value_;
}
void InterpolableList::Add(const InterpolableValue& other) {
const InterpolableList& other_list = ToInterpolableList(other);
DCHECK_EQ(other_list.length(), length());
for (wtf_size_t i = 0; i < length(); i++)
values_[i]->Add(*other_list.values_[i]);
}
void InterpolableList::ScaleAndAdd(double scale,
......
......@@ -44,7 +44,13 @@ class CORE_EXPORT InterpolableValue {
// TODO(alancutter): Remove Equals().
virtual bool Equals(const InterpolableValue&) const = 0;
virtual void Scale(double scale) = 0;
virtual void ScaleAndAdd(double scale, const InterpolableValue& other) = 0;
virtual void Add(const InterpolableValue& other) = 0;
// The default implementation should be sufficient for most types, but
// subclasses can override this to be more efficient if they chose.
virtual void ScaleAndAdd(double scale, const InterpolableValue& other) {
Scale(scale);
Add(other);
}
virtual void AssertCanInterpolateWith(
const InterpolableValue& other) const = 0;
......@@ -81,7 +87,7 @@ class CORE_EXPORT InterpolableNumber final : public InterpolableValue {
bool IsNumber() const final { return true; }
bool Equals(const InterpolableValue& other) const final;
void Scale(double scale) final;
void ScaleAndAdd(double scale, const InterpolableValue& other) final;
void Add(const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
private:
......@@ -130,6 +136,8 @@ class CORE_EXPORT InterpolableList : public InterpolableValue {
bool IsList() const final { return true; }
bool Equals(const InterpolableValue& other) const final;
void Scale(double scale) final;
void Add(const InterpolableValue& other) final;
// We override this to avoid two passes on the list from the base version.
void ScaleAndAdd(double scale, const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
......
......@@ -3,6 +3,7 @@
<body>
<script src="../interpolation/resources/interpolation-test.js"></script>
<script>
// Basic additive composition; the lists should be concatenated.
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px)',
......@@ -158,5 +159,284 @@ assertComposition({
{at: 1, is: 'blur(10px) url(#b) brightness(0)'},
{at: 1.5, is: 'blur(10px) url(#b) brightness(0)'},
]);
// --------------- Accumulation tests. ---------------------
// blur; simple addition.
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px)',
accumulateFrom: 'blur(40px)',
accumulateTo: 'blur(90px)',
}, [
{at: -0.5, is: 'blur(25px)'},
{at: 0, is: 'blur(50px)'},
{at: 0.25, is: 'blur(62.5px)'},
{at: 0.5, is: 'blur(75px)'},
{at: 0.75, is: 'blur(87.5px)'},
{at: 1, is: 'blur(100px)'},
{at: 1.5, is: 'blur(125px)'},
]);
// brightness; 1-based addition.
assertComposition({
property: 'backdrop-filter',
underlying: 'brightness(0.25)',
accumulateFrom: 'brightness(0.5)',
accumulateTo: 'brightness(1.5)',
}, [
{at: -0.5, is: 'brightness(0)'},
{at: 0, is: 'brightness(0)'},
{at: 0.25, is: 'brightness(0)'},
{at: 0.5, is: 'brightness(0.25)'},
{at: 0.75, is: 'brightness(0.5)'},
{at: 1, is: 'brightness(0.75)'},
{at: 1.5, is: 'brightness(1.25)'},
]);
// contrast; 1-based addition.
assertComposition({
property: 'backdrop-filter',
underlying: 'contrast(0.25)',
accumulateFrom: 'contrast(0.5)',
accumulateTo: 'contrast(1.5)',
}, [
{at: -0.5, is: 'contrast(0)'},
{at: 0, is: 'contrast(0)'},
{at: 0.25, is: 'contrast(0)'},
{at: 0.5, is: 'contrast(0.25)'},
{at: 0.75, is: 'contrast(0.5)'},
{at: 1, is: 'contrast(0.75)'},
{at: 1.5, is: 'contrast(1.25)'},
]);
// drop-shadow; addition of lengths plus color addition
assertComposition({
property: 'backdrop-filter',
underlying: 'drop-shadow(10px 5px 0px rgb(255, 0, 0))',
accumulateFrom: 'drop-shadow(0px 10px 10px rgb(0, 255, 0))',
accumulateTo: 'drop-shadow(50px 30px 10px rgb(0, 0, 255))',
}, [
{at: -0.5, is: 'drop-shadow(-15px 5px 10px rgb(255, 255, 0))'},
{at: 0, is: 'drop-shadow(10px 15px 10px rgb(255, 255, 0))'},
{at: 0.25, is: 'drop-shadow(22.5px 20px 10px rgb(255, 191, 64))'},
{at: 0.5, is: 'drop-shadow(35px 25px 10px rgb(255, 128, 128))'},
{at: 0.75, is: 'drop-shadow(47.5px 30px 10px rgb(255, 64, 191))'},
{at: 1, is: 'drop-shadow(60px 35px 10px rgb(255, 0, 255))'},
{at: 1.5, is: 'drop-shadow(85px 45px 10px rgb(255, 0, 255))'},
]);
// grayscale; 1-based addition.
assertComposition({
property: 'backdrop-filter',
underlying: 'grayscale(0.25)',
accumulateFrom: 'grayscale(0.5)',
accumulateTo: 'grayscale(1.5)', // clamped to 1
}, [
{at: -0.5, is: 'grayscale(0)'},
{at: 0, is: 'grayscale(0)'},
{at: 0.25, is: 'grayscale(0)'},
{at: 0.5, is: 'grayscale(0)'},
{at: 0.75, is: 'grayscale(0.125)'},
{at: 1, is: 'grayscale(0.25)'},
{at: 1.5, is: 'grayscale(0.5)'},
]);
// hue-rotate; simple addition
assertComposition({
property: 'backdrop-filter',
underlying: 'hue-rotate(45deg)',
accumulateFrom: 'hue-rotate(140deg)',
accumulateTo: 'hue-rotate(400deg)',
}, [
{at: -0.5, is: 'hue-rotate(55deg)'},
{at: 0, is: 'hue-rotate(185deg)'},
{at: 0.25, is: 'hue-rotate(250deg)'},
{at: 0.5, is: 'hue-rotate(315deg)'},
{at: 0.75, is: 'hue-rotate(380deg)'},
{at: 1, is: 'hue-rotate(445deg)'},
{at: 1.5, is: 'hue-rotate(575deg)'},
]);
// invert; 1-based addition.
assertComposition({
property: 'backdrop-filter',
underlying: 'invert(0.25)',
accumulateFrom: 'invert(0.5)',
accumulateTo: 'invert(1.5)', // clamped to 1
}, [
{at: -0.5, is: 'invert(0)'},
{at: 0, is: 'invert(0)'},
{at: 0.25, is: 'invert(0)'},
{at: 0.5, is: 'invert(0)'},
{at: 0.75, is: 'invert(0.125)'},
{at: 1, is: 'invert(0.25)'},
{at: 1.5, is: 'invert(0.5)'},
]);
// opacity; 1-based addition
assertComposition({
property: 'backdrop-filter',
underlying: 'opacity(0.25)',
accumulateFrom: 'opacity(0.5)',
accumulateTo: 'opacity(1.5)', // clamped to 1
}, [
{at: -0.5, is: 'opacity(0)'},
{at: 0, is: 'opacity(0)'},
{at: 0.25, is: 'opacity(0)'},
{at: 0.5, is: 'opacity(0)'},
{at: 0.75, is: 'opacity(0.125)'},
{at: 1, is: 'opacity(0.25)'},
{at: 1.5, is: 'opacity(0.5)'},
]);
// saturate; 1-based addition
assertComposition({
property: 'backdrop-filter',
underlying: 'saturate(0.25)',
accumulateFrom: 'saturate(0.5)',
accumulateTo: 'saturate(1.5)',
}, [
{at: -0.5, is: 'saturate(0)'},
{at: 0, is: 'saturate(0)'},
{at: 0.25, is: 'saturate(0)'},
{at: 0.5, is: 'saturate(0.25)'},
{at: 0.75, is: 'saturate(0.5)'},
{at: 1, is: 'saturate(0.75)'},
{at: 1.5, is: 'saturate(1.25)'},
]);
// sepia; 1-based addition
assertComposition({
property: 'backdrop-filter',
underlying: 'sepia(0.25)',
accumulateFrom: 'sepia(0.5)',
accumulateTo: 'sepia(1.5)', // clamped to 1
}, [
{at: -0.5, is: 'sepia(0)'},
{at: 0, is: 'sepia(0)'},
{at: 0.25, is: 'sepia(0)'},
{at: 0.5, is: 'sepia(0)'},
{at: 0.75, is: 'sepia(0.125)'},
{at: 1, is: 'sepia(0.25)'},
{at: 1.5, is: 'sepia(0.5)'},
]);
// url; cannot be accumulated
assertComposition({
property: 'backdrop-filter',
underlying: 'url(#a)',
accumulateFrom: 'url(#b)',
accumulateTo: 'url(#c)',
}, [
{at: -0.5, is: 'url(#b)'},
{at: 0, is: 'url(#b)'},
{at: 0.25, is: 'url(#b)'},
{at: 0.5, is: 'url(#c)'},
{at: 0.75, is: 'url(#c)'},
{at: 1, is: 'url(#c)'},
{at: 1.5, is: 'url(#c)'},
]);
// Test auto-extension of the underlying list.
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px)',
accumulateFrom: 'blur(40px) saturate(1)',
accumulateTo: 'blur(90px) saturate(0)',
}, [
{at: -0.5, is: 'blur(25px) saturate(1.5)'},
{at: 0, is: 'blur(50px) saturate(1)'},
{at: 0.25, is: 'blur(62.5px) saturate(0.75)'},
{at: 0.5, is: 'blur(75px) saturate(0.5)'},
{at: 0.75, is: 'blur(87.5px) saturate(0.25)'},
{at: 1, is: 'blur(100px) saturate(0)'},
{at: 1.5, is: 'blur(125px) saturate(0)'},
]);
// Test auto-extension of the composited-onto list.
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px) saturate(0.75)',
accumulateFrom: 'blur(40px)',
accumulateTo: 'blur(90px)',
}, [
{at: -0.5, is: 'blur(25px) saturate(0.75)'},
{at: 0, is: 'blur(50px) saturate(0.75)'},
{at: 0.25, is: 'blur(62.5px) saturate(0.75)'},
{at: 0.5, is: 'blur(75px) saturate(0.75)'},
{at: 0.75, is: 'blur(87.5px) saturate(0.75)'},
{at: 1, is: 'blur(100px) saturate(0.75)'},
{at: 1.5, is: 'blur(125px) saturate(0.75)'},
]);
// Mismatching type for underlying; it just gets replaced.
assertComposition({
property: 'backdrop-filter',
underlying: 'contrast(0.75)',
accumulateFrom: 'blur(40px)',
accumulateTo: 'blur(80px)',
}, [
{at: -0.5, is: 'blur(20px)'},
{at: 0, is: 'blur(40px)'},
{at: 0.25, is: 'blur(50px)'},
{at: 0.5, is: 'blur(60px)'},
{at: 0.75, is: 'blur(70px)'},
{at: 1, is: 'blur(80px)'},
{at: 1.5, is: 'blur(100px)'},
]);
// Underlying only type-matches one side of the interpolation; it should be
// accumulated onto that side, but the entire animation will be discrete due to
// the mis-matching types.
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px)',
accumulateFrom: 'blur(40px)',
accumulateTo: 'saturate(1)',
}, [
{at: -0.5, is: 'blur(50px)'},
{at: 0, is: 'blur(50px)'},
{at: 0.25, is: 'blur(50px)'},
{at: 0.5, is: 'saturate(1)'},
{at: 0.75, is: 'saturate(1)'},
{at: 1, is: 'saturate(1)'},
{at: 1.5, is: 'saturate(1)'},
]);
// Test a case where only one side is accumulative and the other is replace.
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px)',
accumulateFrom: 'blur(30px)',
replaceTo: 'blur(100px)',
}, [
{at: -0.5, is: 'blur(10px)'},
{at: 0, is: 'blur(40px)'},
{at: 0.25, is: 'blur(55px)'},
{at: 0.5, is: 'blur(70px)'},
{at: 0.75, is: 'blur(85px)'},
{at: 1, is: 'blur(100px)'},
{at: 1.5, is: 'blur(130px)'},
]);
// Test a case where only one side is accumulative and the other is add.
// This basically looks like:
// accumulateSide = blur(Apx) neutral-blur
// addSide = blur(10px) blur(Bpx)
assertComposition({
property: 'backdrop-filter',
underlying: 'blur(10px)',
accumulateFrom: 'blur(40px)',
addTo: 'blur(100px)',
}, [
{at: -0.5, is: 'blur(70px) blur(0px)'},
{at: 0, is: 'blur(50px) blur(0px)'},
{at: 0.25, is: 'blur(40px) blur(25px)'},
{at: 0.5, is: 'blur(30px) blur(50px)'},
{at: 0.75, is: 'blur(20px) blur(75px)'},
{at: 1, is: 'blur(10px) blur(100px)'},
{at: 1.5, is: 'blur(0px) blur(150px)'},
]);
</script>
</body>
......@@ -42,12 +42,15 @@
* This works in the same way as assertInterpolation with expectations auto
* generated according to each interpolation method's handling of values
* that don't interpolate.
* - assertComposition({property, underlying, [addFrom], [addTo], [replaceFrom], [replaceTo]}, [{at: fraction, is: value}])
* - assertComposition(
* { property, underlying, [accumulateFrom], [accumulateTo],
* [addFrom], [addTo], [replaceFrom], [replaceTo] },
* [{at: fraction, is: value}])
* Similar to assertInterpolation() instead using only the Web Animations API
* to animate composite specified keyframes (add or replace) on top of
* an underlying value.
* Exactly one of (addFrom, replaceFrom) must be specified.
* Exactly one of (addTo, replaceTo) must be specified.
* to animate composite specified keyframes (accumulate, add or replace) on
* top of an underlying value.
* Exactly one of (accumulateFrom, addFrom, replaceFrom) must be specified.
* Exactly one of (accumulateTo, addTo, replaceTo) must be specified.
* - afterTest(callback)
* Calls callback after all the tests have executed.
*
......@@ -372,16 +375,19 @@ assertInterpolation({
var options = compositionTest.options;
var property = options.property;
var underlying = options.underlying;
var from = options.addFrom || options.replaceFrom;
var to = options.addTo || options.replaceTo;
var fromComposite = 'addFrom' in options ? 'add' : 'replace';
var toComposite = 'addTo' in options ? 'add' : 'replace';
if ('addFrom' in options === 'replaceFrom' in options
|| 'addTo' in options === 'replaceTo' in options) {
var from = options.accumulateFrom || options.addFrom || options.replaceFrom;
var to = options.accumulateTo || options.addTo || options.replaceTo;
var fromComposite = 'accumulateFrom' in options ? 'accumulate' : 'addFrom' in options ? 'add' : 'replace';
var toComposite = 'accumulateTo' in options ? 'accumulate' : 'addTo' in options ? 'add' : 'replace';
const invalidFrom = 'addFrom' in options === 'replaceFrom' in options
&& 'addFrom' in options === 'accumulateFrom' in options;
const invalidTo = 'addTo' in options === 'replaceTo' in options
&& 'addTo' in options === 'accumulateTo' in options;
if (invalidFrom || invalidTo) {
test(function() {
assert_true('addFrom' in options !== 'replaceFrom' in options, 'addFrom xor replaceFrom must be specified');
assert_true('addTo' in options !== 'replaceTo' in options, 'addTo xor replaceTo must be specified');
}, `Composition tests must use addFrom xor replaceFrom, and addTo xor replaceTo`);
assert_false(invalidFrom, 'Exactly one of accumulateFrom, addFrom, or replaceFrom must be specified');
assert_false(invalidTo, 'Exactly one of accumulateTo, addTo, or replaceTo must be specified');
}, `Composition tests must have valid setup`);
}
validateTestInputs(property, from, to, underlying);
......
This is a testharness.js-based test.
Found 563 tests; 530 PASS, 33 FAIL, 0 TIMEOUT, 0 NOTRUN.
Found 563 tests; 532 PASS, 31 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS Setup
PASS align-content (type: discrete) has testAccumulation function
PASS align-content: "flex-end" onto "flex-start"
......@@ -201,8 +201,8 @@ PASS fill-rule (type: discrete) has testAccumulation function
PASS fill-rule: "nonzero" onto "evenodd"
PASS fill-rule: "evenodd" onto "nonzero"
PASS filter (type: filterList) has testAccumulation function
FAIL filter: same ordered filter functions assert_equals: The value should be blur(30px) brightness(0) at 0ms expected "blur(30px) brightness(0)" but got "blur(10px) brightness(0.3) blur(20px) brightness(0.1)"
FAIL filter: mismatched ordered filter functions assert_equals: The value should be brightness(1.2) blur(20px) at 0ms expected "brightness(1.2) blur(20px)" but got "blur(10px) brightness(1.3) brightness(1.2) blur(20px)"
PASS filter: same ordered filter functions
PASS filter: mismatched ordered filter functions
PASS flex-basis (type: lengthPercentageOrCalc) has testAccumulation function
PASS flex-basis: length
PASS flex-basis: length of rem
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment