Commit 3038d348 authored by Kevin Ellis's avatar Kevin Ellis Committed by Commit Bot

Update transform list interpolation.

Update interpolation of transform lists to reflect recent changes to the spec (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).

* If the transform lists are of different lengths, but the transformations are pairwise compatible to the end of the shorter list, extend the shorter list with corresponding identity transforms.
* If the transform lists are incompatible, perform pairwise transforms for compatible entities at the start of the list and fallback to matrix interpolation for the remaining transformations.
* Fix discrete fallback.

Bug: 860391, 267348, 918643

Change-Id: I8b2d770e354f3d77dad00e088a2eafe6c1ad655a
Reviewed-on: https://chromium-review.googlesource.com/c/1372025
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Reviewed-by: default avatarStephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#619661}
parent 0005a6f4
......@@ -17,17 +17,10 @@
namespace cc {
TransformOperations::TransformOperations()
: decomposed_transform_dirty_(true) {
}
TransformOperations::TransformOperations() {}
TransformOperations::TransformOperations(const TransformOperations& other) {
operations_ = other.operations_;
decomposed_transform_dirty_ = other.decomposed_transform_dirty_;
if (!decomposed_transform_dirty_) {
decomposed_transform_.reset(
new gfx::DecomposedTransform(*other.decomposed_transform_.get()));
}
}
TransformOperations::~TransformOperations() = default;
......@@ -35,21 +28,23 @@ TransformOperations::~TransformOperations() = default;
TransformOperations& TransformOperations::operator=(
const TransformOperations& other) {
operations_ = other.operations_;
decomposed_transform_dirty_ = other.decomposed_transform_dirty_;
if (!decomposed_transform_dirty_) {
decomposed_transform_.reset(
new gfx::DecomposedTransform(*other.decomposed_transform_.get()));
}
return *this;
}
gfx::Transform TransformOperations::Apply() const {
return ApplyRemaining(0);
}
gfx::Transform TransformOperations::ApplyRemaining(size_t start) const {
gfx::Transform to_return;
for (auto& operation : operations_)
to_return.PreconcatTransform(operation.matrix);
for (size_t i = start; i < operations_.size(); i++) {
to_return.PreconcatTransform(operations_[i].matrix);
}
return to_return;
}
// TODO(crbug.com/914397): Consolidate blink and cc implementations of transform
// interpolation.
TransformOperations TransformOperations::Blend(const TransformOperations& from,
SkMScalar progress) const {
TransformOperations to_return;
......@@ -197,6 +192,23 @@ bool TransformOperations::MatchesTypes(const TransformOperations& other) const {
return true;
}
size_t TransformOperations::MatchingPrefixLength(
const TransformOperations& other) const {
size_t num_operations =
std::min(operations_.size(), other.operations_.size());
for (size_t i = 0; i < num_operations; ++i) {
if (operations_[i].type != other.operations_[i].type) {
// Remaining operations in each operations list require matrix/matrix3d
// interpolation.
return i;
}
}
// If the operations match to the length of the shorter list, then pad its
// length with the matching identity operations.
// https://drafts.csswg.org/css-transforms/#transform-function-lists
return std::max(operations_.size(), other.operations_.size());
}
bool TransformOperations::CanBlendWith(
const TransformOperations& other) const {
TransformOperations dummy;
......@@ -213,7 +225,7 @@ void TransformOperations::AppendTranslate(SkMScalar x,
to_add.translate.y = y;
to_add.translate.z = z;
operations_.push_back(to_add);
decomposed_transform_dirty_ = true;
decomposed_transforms_.clear();
}
void TransformOperations::AppendRotate(SkMScalar x,
......@@ -228,7 +240,7 @@ void TransformOperations::AppendRotate(SkMScalar x,
to_add.rotate.angle = degrees;
to_add.Bake();
operations_.push_back(to_add);
decomposed_transform_dirty_ = true;
decomposed_transforms_.clear();
}
void TransformOperations::AppendScale(SkMScalar x, SkMScalar y, SkMScalar z) {
......@@ -239,7 +251,7 @@ void TransformOperations::AppendScale(SkMScalar x, SkMScalar y, SkMScalar z) {
to_add.scale.z = z;
to_add.Bake();
operations_.push_back(to_add);
decomposed_transform_dirty_ = true;
decomposed_transforms_.clear();
}
void TransformOperations::AppendSkew(SkMScalar x, SkMScalar y) {
......@@ -249,7 +261,7 @@ void TransformOperations::AppendSkew(SkMScalar x, SkMScalar y) {
to_add.skew.y = y;
to_add.Bake();
operations_.push_back(to_add);
decomposed_transform_dirty_ = true;
decomposed_transforms_.clear();
}
void TransformOperations::AppendPerspective(SkMScalar depth) {
......@@ -258,7 +270,7 @@ void TransformOperations::AppendPerspective(SkMScalar depth) {
to_add.perspective_depth = depth;
to_add.Bake();
operations_.push_back(to_add);
decomposed_transform_dirty_ = true;
decomposed_transforms_.clear();
}
void TransformOperations::AppendMatrix(const gfx::Transform& matrix) {
......@@ -266,7 +278,7 @@ void TransformOperations::AppendMatrix(const gfx::Transform& matrix) {
to_add.matrix = matrix;
to_add.type = TransformOperation::TRANSFORM_OPERATION_MATRIX;
operations_.push_back(to_add);
decomposed_transform_dirty_ = true;
decomposed_transforms_.clear();
}
void TransformOperations::AppendIdentity() {
......@@ -275,6 +287,7 @@ void TransformOperations::AppendIdentity() {
void TransformOperations::Append(const TransformOperation& operation) {
operations_.push_back(operation);
decomposed_transforms_.clear();
}
bool TransformOperations::IsIdentity() const {
......@@ -304,42 +317,44 @@ bool TransformOperations::BlendInternal(const TransformOperations& from,
if (from_identity && to_identity)
return true;
if (MatchesTypes(from)) {
size_t num_operations =
std::max(from_identity ? 0 : from.operations_.size(),
to_identity ? 0 : operations_.size());
for (size_t i = 0; i < num_operations; ++i) {
TransformOperation blended;
if (!TransformOperation::BlendTransformOperations(
from_identity ? nullptr : &from.operations_[i],
to_identity ? nullptr : &operations_[i], progress, &blended)) {
return false;
}
result->Append(blended);
size_t matching_prefix_length = MatchingPrefixLength(from);
size_t from_size = from_identity ? 0 : from.operations_.size();
size_t to_size = to_identity ? 0 : operations_.size();
size_t num_operations = std::max(from_size, to_size);
for (size_t i = 0; i < matching_prefix_length; ++i) {
TransformOperation blended;
if (!TransformOperation::BlendTransformOperations(
i >= from_size ? nullptr : &from.operations_[i],
i >= to_size ? nullptr : &operations_[i], progress, &blended)) {
return false;
}
return true;
result->Append(blended);
}
if (!ComputeDecomposedTransform() || !from.ComputeDecomposedTransform())
return false;
gfx::DecomposedTransform to_return;
to_return = gfx::BlendDecomposedTransforms(*decomposed_transform_.get(),
*from.decomposed_transform_.get(),
progress);
result->AppendMatrix(ComposeTransform(to_return));
if (matching_prefix_length < num_operations) {
if (!ComputeDecomposedTransform(matching_prefix_length) ||
!from.ComputeDecomposedTransform(matching_prefix_length)) {
return false;
}
gfx::DecomposedTransform matrix_transform = gfx::BlendDecomposedTransforms(
*decomposed_transforms_[matching_prefix_length].get(),
*from.decomposed_transforms_[matching_prefix_length].get(), progress);
result->AppendMatrix(ComposeTransform(matrix_transform));
}
return true;
}
bool TransformOperations::ComputeDecomposedTransform() const {
if (decomposed_transform_dirty_) {
if (!decomposed_transform_)
decomposed_transform_.reset(new gfx::DecomposedTransform());
gfx::Transform transform = Apply();
if (!gfx::DecomposeTransform(decomposed_transform_.get(), transform))
bool TransformOperations::ComputeDecomposedTransform(
size_t start_offset) const {
auto it = decomposed_transforms_.find(start_offset);
if (it == decomposed_transforms_.end()) {
std::unique_ptr<gfx::DecomposedTransform> decomposed_transform =
std::make_unique<gfx::DecomposedTransform>();
gfx::Transform transform = ApplyRemaining(start_offset);
if (!gfx::DecomposeTransform(decomposed_transform.get(), transform))
return false;
decomposed_transform_dirty_ = false;
decomposed_transforms_[start_offset] = std::move(decomposed_transform);
}
return true;
}
......
......@@ -6,8 +6,10 @@
#define CC_ANIMATION_TRANSFORM_OPERATIONS_H_
#include <memory>
#include <unordered_map>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "cc/animation/animation_export.h"
......@@ -41,6 +43,10 @@ class CC_ANIMATION_EXPORT TransformOperations {
// Returns a transformation matrix representing these transform operations.
gfx::Transform Apply() const;
// Returns a transformation matrix representing the set of transform
// operations from index |start| to the end of the list.
gfx::Transform ApplyRemaining(size_t start) const;
// Given another set of transform operations and a progress in the range
// [0, 1], returns a transformation matrix representing the intermediate
// value. If this->MatchesTypes(from), then each of the operations are
......@@ -74,6 +80,12 @@ class CC_ANIMATION_EXPORT TransformOperations {
// as other and its descendants.
bool MatchesTypes(const TransformOperations& other) const;
// Returns the number of matching transform operations at the start of the
// transform lists. If one list is shorter but pairwise compatible, it will be
// extended with matching identity operators per spec
// (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).
size_t MatchingPrefixLength(const TransformOperations& other) const;
// Returns true if these operations can be blended. It will only return
// false if we must resort to matrix interpolation, and matrix interpolation
// fails (this can happen if either matrix cannot be decomposed).
......@@ -109,17 +121,19 @@ class CC_ANIMATION_EXPORT TransformOperations {
SkMScalar tolerance) const;
private:
FRIEND_TEST_ALL_PREFIXES(TransformOperationsTest, TestDecompositionCache);
bool BlendInternal(const TransformOperations& from,
SkMScalar progress,
TransformOperations* result) const;
std::vector<TransformOperation> operations_;
bool ComputeDecomposedTransform() const;
bool ComputeDecomposedTransform(size_t start_offset) const;
// For efficiency, we cache the decomposed transform.
mutable std::unique_ptr<gfx::DecomposedTransform> decomposed_transform_;
mutable bool decomposed_transform_dirty_;
// For efficiency, we cache the decomposed transforms.
mutable std::unordered_map<size_t, std::unique_ptr<gfx::DecomposedTransform>>
decomposed_transforms_;
};
} // namespace cc
......
......@@ -86,7 +86,7 @@ TEST(TransformOperationTest, TransformTypesAreUnique) {
}
}
TEST(TransformOperationTest, MatchTypesSameLength) {
TEST(TransformOperationTest, MatchingPrefixSameLength) {
TransformOperations translates;
translates.AppendTranslate(1, 0, 0);
translates.AppendTranslate(1, 0, 0);
......@@ -102,14 +102,20 @@ TEST(TransformOperationTest, MatchTypesSameLength) {
translates2.AppendTranslate(0, 2, 0);
translates2.AppendTranslate(0, 2, 0);
TransformOperations mixed;
mixed.AppendTranslate(0, 2, 0);
mixed.AppendScale(2, 1, 1);
mixed.AppendSkew(0, 2);
TransformOperations translates3 = translates2;
EXPECT_FALSE(translates.MatchesTypes(skews));
EXPECT_TRUE(translates.MatchesTypes(translates2));
EXPECT_TRUE(translates.MatchesTypes(translates3));
EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews));
EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2));
EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates3));
EXPECT_EQ(1UL, translates.MatchingPrefixLength(mixed));
}
TEST(TransformOperationTest, MatchTypesDifferentLength) {
TEST(TransformOperationTest, MatchingPrefixDifferentLength) {
TransformOperations translates;
translates.AppendTranslate(1, 0, 0);
translates.AppendTranslate(1, 0, 0);
......@@ -123,8 +129,14 @@ TEST(TransformOperationTest, MatchTypesDifferentLength) {
translates2.AppendTranslate(0, 2, 0);
translates2.AppendTranslate(0, 2, 0);
EXPECT_FALSE(translates.MatchesTypes(skews));
EXPECT_FALSE(translates.MatchesTypes(translates2));
TransformOperations none;
EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews));
// Pad the length of the shorter list provided all previous operation-
// pairs match per spec
// (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).
EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2));
EXPECT_EQ(3UL, translates.MatchingPrefixLength(none));
}
std::vector<std::unique_ptr<TransformOperations>> GetIdentityOperations() {
......@@ -181,7 +193,7 @@ std::vector<std::unique_ptr<TransformOperations>> GetIdentityOperations() {
return operations;
}
TEST(TransformOperationTest, MatchTypesOrder) {
TEST(TransformOperationTest, MatchingPrefixLengthOrder) {
TransformOperations mix_order_identity;
mix_order_identity.AppendTranslate(0, 0, 0);
mix_order_identity.AppendScale(1, 1, 1);
......@@ -197,9 +209,9 @@ TEST(TransformOperationTest, MatchTypesOrder) {
mix_order_two.AppendTranslate(1, 0, 0);
mix_order_two.AppendScale(2, 1, 3);
EXPECT_TRUE(mix_order_identity.MatchesTypes(mix_order_one));
EXPECT_FALSE(mix_order_identity.MatchesTypes(mix_order_two));
EXPECT_FALSE(mix_order_one.MatchesTypes(mix_order_two));
EXPECT_EQ(3UL, mix_order_identity.MatchingPrefixLength(mix_order_one));
EXPECT_EQ(1UL, mix_order_identity.MatchingPrefixLength(mix_order_two));
EXPECT_EQ(1UL, mix_order_one.MatchingPrefixLength(mix_order_two));
}
TEST(TransformOperationTest, NoneAlwaysMatches) {
......@@ -208,7 +220,8 @@ TEST(TransformOperationTest, NoneAlwaysMatches) {
TransformOperations none_operation;
for (size_t i = 0; i < operations.size(); ++i)
EXPECT_TRUE(operations[i]->MatchesTypes(none_operation));
EXPECT_EQ(operations[i]->size(),
operations[i]->MatchingPrefixLength(none_operation));
}
TEST(TransformOperationTest, ApplyTranslate) {
......@@ -317,6 +330,10 @@ TEST(TransformOperationTest, BlendOrder) {
SkMScalar dy2 = 20;
SkMScalar dz2 = 30;
SkMScalar sx3 = 2;
SkMScalar sy3 = 1;
SkMScalar sz3 = 1;
TransformOperations operations_from;
operations_from.AppendScale(sx1, sy1, sz1);
operations_from.AppendTranslate(dx1, dy1, dz1);
......@@ -369,16 +386,54 @@ TEST(TransformOperationTest, BlendOrder) {
ExpectTransformOperationEqual(expected_op, blended_op);
}
// Create a mismatch, forcing matrix interpolation.
operations_to.AppendMatrix(gfx::Transform());
TransformOperations base_operations_expected = operations_expected;
// Create a mismatch in number of operations. Pairwise interpolation is still
// used when the operations match up to the length of the shorter list.
operations_to.AppendScale(sx3, sy3, sz3);
gfx::Transform appended_scale;
appended_scale.Scale3d(sx3, sy3, sz3);
gfx::Transform blended_append_scale = appended_scale;
blended_append_scale.Blend(gfx::Transform(), progress);
expected.PreconcatTransform(blended_append_scale);
operations_expected.AppendScale(
gfx::Tween::FloatValueBetween(progress, 1, sx3),
gfx::Tween::FloatValueBetween(progress, 1, sy3),
gfx::Tween::FloatValueBetween(progress, 1, sz3));
blended = operations_to.Blend(operations_from, progress);
EXPECT_TRANSFORMATION_MATRIX_EQ(expected, blended.Apply());
EXPECT_TRANSFORMATION_MATRIX_EQ(operations_expected.Apply(), blended.Apply());
EXPECT_EQ(operations_expected.size(), blended.size());
for (size_t i = 0; i < operations_expected.size(); ++i) {
TransformOperation expected_op = operations_expected.at(i);
TransformOperation blended_op = blended.at(i);
SCOPED_TRACE(i);
ExpectTransformOperationEqual(expected_op, blended_op);
}
// Create a mismatch, forcing matrix interpolation for the last operator pair.
operations_from.AppendRotate(0, 0, 1, 90);
blended = operations_to.Blend(operations_from, progress);
expected = operations_to.Apply();
expected.Blend(operations_from.Apply(), progress);
gfx::Transform transform_from;
transform_from.RotateAboutZAxis(90);
gfx::Transform transform_to;
transform_to.Scale3d(sx3, sy3, sz3);
gfx::Transform blended_matrix = transform_to;
blended_matrix.Blend(transform_from, progress);
expected = blended_scale;
expected.PreconcatTransform(blended_translate);
expected.PreconcatTransform(blended_matrix);
operations_expected = TransformOperations();
operations_expected.AppendMatrix(expected);
operations_expected = base_operations_expected;
operations_expected.AppendMatrix(blended_matrix);
EXPECT_TRANSFORMATION_MATRIX_EQ(expected, blended.Apply());
EXPECT_TRANSFORMATION_MATRIX_EQ(operations_expected.Apply(), blended.Apply());
......@@ -1682,4 +1737,60 @@ TEST(TransformOperationsTest, ApproximateEquality) {
}
} // namespace
// This test is intentionally outside the anonymous namespace for visibility as
// it needs to be friend of TransformOperations.
TEST(TransformOperationsTest, TestDecompositionCache) {
TransformOperations transforms;
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a scale transform.
transforms.AppendScale(2.f, 2.f, 2.f);
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(1));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(1));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(2UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a rotation transform.
transforms.AppendRotate(1, 0, 0, 45);
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a translation transform.
transforms.AppendTranslate(1, 1, 1);
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a skew transform.
transforms.AppendSkew(1, 0);
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a perspective transform.
transforms.AppendPerspective(800);
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a matrix transform.
transforms.AppendMatrix(gfx::Transform());
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
// Reset cache when appending a generic transform operation.
transforms.Append(TransformOperation());
EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
}
} // namespace cc
......@@ -48,8 +48,8 @@ void InterpolatedTransformOperation::Apply(
const FloatSize& border_box_size) const {
TransformationMatrix from_transform;
TransformationMatrix to_transform;
from.Apply(border_box_size, from_transform);
to.Apply(border_box_size, to_transform);
from.ApplyRemaining(border_box_size, starting_index, from_transform);
to.ApplyRemaining(border_box_size, starting_index, to_transform);
to_transform.Blend(from_transform, progress);
transform.Multiply(to_transform);
......@@ -72,7 +72,7 @@ scoped_refptr<TransformOperation> InterpolatedTransformOperation::Blend(
from_operations.Operations().push_back(
const_cast<TransformOperation*>(from));
return InterpolatedTransformOperation::Create(this_operations,
from_operations, progress);
from_operations, 0, progress);
}
} // namespace blink
......@@ -43,9 +43,10 @@ class PLATFORM_EXPORT InterpolatedTransformOperation final
static scoped_refptr<InterpolatedTransformOperation> Create(
const TransformOperations& from,
const TransformOperations& to,
int starting_index,
double progress) {
return base::AdoptRef(
new InterpolatedTransformOperation(from, to, progress));
new InterpolatedTransformOperation(from, to, starting_index, progress));
}
bool CanBlendWith(const TransformOperation& other) const override {
......@@ -64,7 +65,7 @@ class PLATFORM_EXPORT InterpolatedTransformOperation final
double progress,
bool blend_to_identity = false) override;
scoped_refptr<TransformOperation> Zoom(double factor) final {
return Create(from.Zoom(factor), to.Zoom(factor), progress);
return Create(from.Zoom(factor), to.Zoom(factor), starting_index, progress);
}
bool DependsOnBoxSize() const override {
......@@ -73,11 +74,19 @@ class PLATFORM_EXPORT InterpolatedTransformOperation final
InterpolatedTransformOperation(const TransformOperations& from,
const TransformOperations& to,
int starting_index,
double progress)
: from(from), to(to), progress(progress) {}
: from(from),
to(to),
starting_index(starting_index),
progress(progress) {}
const TransformOperations from;
const TransformOperations to;
// Number of operations to skip from the start of each list. By spec,
// pairwise interpolations are performed for compatible operations at the
// start of the list and matrix interpolation for the remainder.
int starting_index;
double progress;
};
......
......@@ -38,14 +38,20 @@ scoped_refptr<TransformOperation> Matrix3DTransformOperation::Blend(
if (from && !from->IsSameType(*this))
return this;
// Convert the TransformOperations into matrices
// Convert the TransformOperations into matrices. Fail the blend operation
// if either of the matrices is non-invertible.
FloatSize size;
TransformationMatrix from_t;
TransformationMatrix to_t;
if (from)
if (from) {
from->Apply(from_t, size);
if (!from_t.IsInvertible())
return nullptr;
}
Apply(to_t, size);
if (!to_t.IsInvertible())
return nullptr;
if (blend_to_identity)
std::swap(from_t, to_t);
......
......@@ -35,10 +35,14 @@ scoped_refptr<TransformOperation> MatrixTransformOperation::Blend(
// convert the TransformOperations into matrices
TransformationMatrix from_t;
TransformationMatrix to_t(a_, b_, c_, d_, e_, f_);
if (!to_t.IsInvertible())
return nullptr;
if (from) {
const MatrixTransformOperation* m =
static_cast<const MatrixTransformOperation*>(from);
from_t.SetMatrix(m->a_, m->b_, m->c_, m->d_, m->e_, m->f_);
if (!from_t.IsInvertible())
return nullptr;
}
if (blend_to_identity)
......
......@@ -49,87 +49,117 @@ bool TransformOperations::operator==(const TransformOperations& o) const {
return true;
}
bool TransformOperations::OperationsMatch(
const TransformOperations& other) const {
wtf_size_t num_operations = Operations().size();
if (num_operations != other.Operations().size())
return false;
void TransformOperations::ApplyRemaining(const FloatSize& border_box_size,
wtf_size_t start,
TransformationMatrix& t) const {
for (wtf_size_t i = start; i < operations_.size(); i++) {
operations_[i]->Apply(t, border_box_size);
}
}
wtf_size_t TransformOperations::MatchingPrefixLength(
const TransformOperations& other) const {
wtf_size_t num_operations =
std::min(Operations().size(), other.Operations().size());
for (wtf_size_t i = 0; i < num_operations; ++i) {
if (Operations()[i]->PrimitiveType() !=
other.Operations()[i]->PrimitiveType()) {
return false;
// Remaining operations in each operations list require matrix/matrix3d
// interpolation.
return i;
}
}
return true;
// If the operations match to the length of the shorter list, then pad its
// length with the matching identity operations.
// https://drafts.csswg.org/css-transforms/#transform-function-lists
return std::max(Operations().size(), other.Operations().size());
}
TransformOperations TransformOperations::BlendByMatchingOperations(
TransformOperations TransformOperations::BlendPrefixByMatchingOperations(
const TransformOperations& from,
const double& progress) const {
wtf_size_t matching_prefix_length,
double progress,
bool* success) const {
TransformOperations result;
wtf_size_t from_size = from.Operations().size();
wtf_size_t to_size = Operations().size();
wtf_size_t size = std::max(from_size, to_size);
for (wtf_size_t i = 0; i < size; i++) {
for (wtf_size_t i = 0; i < matching_prefix_length; i++) {
scoped_refptr<TransformOperation> from_operation =
(i < from_size) ? from.Operations()[i].get() : nullptr;
scoped_refptr<TransformOperation> to_operation =
(i < to_size) ? Operations()[i].get() : nullptr;
scoped_refptr<TransformOperation> blended_operation =
to_operation
? to_operation->Blend(from_operation.get(), progress)
: (from_operation ? from_operation->Blend(nullptr, progress, true)
: nullptr);
if (blended_operation)
result.Operations().push_back(blended_operation);
else {
scoped_refptr<TransformOperation> identity_operation =
IdentityTransformOperation::Create();
if (progress > 0.5)
result.Operations().push_back(to_operation ? to_operation
: identity_operation);
else
result.Operations().push_back(from_operation ? from_operation
: identity_operation);
*success = false;
return result;
}
}
return result;
}
scoped_refptr<TransformOperation>
TransformOperations::BlendByUsingMatrixInterpolation(
TransformOperations::BlendRemainingByUsingMatrixInterpolation(
const TransformOperations& from,
wtf_size_t matching_prefix_length,
double progress) const {
if (DependsOnBoxSize() || from.DependsOnBoxSize())
return InterpolatedTransformOperation::Create(from, *this, progress);
// Not safe to use a cached transform if any of the operations are size
// dependent.
if (DependsOnBoxSize() || from.DependsOnBoxSize()) {
return InterpolatedTransformOperation::Create(
from, *this, matching_prefix_length, progress);
}
// Evaluate blended matrix here to avoid creating a nested data structure of
// unbounded depth.
TransformationMatrix from_transform;
TransformationMatrix to_transform;
from.Apply(FloatSize(), from_transform);
Apply(FloatSize(), to_transform);
from.ApplyRemaining(FloatSize(), matching_prefix_length, from_transform);
ApplyRemaining(FloatSize(), matching_prefix_length, to_transform);
// Fallback to discrete interpolation if either transform matrix is singular.
if (!(from_transform.IsInvertible() && to_transform.IsInvertible())) {
return nullptr;
}
to_transform.Blend(from_transform, progress);
return Matrix3DTransformOperation::Create(to_transform);
}
// https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms
// TODO(crbug.com/914397): Consolidate blink and cc implementations of transform
// interpolation.
TransformOperations TransformOperations::Blend(const TransformOperations& from,
double progress) const {
if (from == *this || (!from.size() && !size()))
return *this;
// If either list is empty, use blendByMatchingOperations which has special
// logic for this case.
if (!from.size() || !size() || from.OperationsMatch(*this))
return BlendByMatchingOperations(from, progress);
TransformOperations result;
result.Operations().push_back(
BlendByUsingMatrixInterpolation(from, progress));
wtf_size_t matching_prefix_length = MatchingPrefixLength(from);
wtf_size_t max_path_length =
std::max(Operations().size(), from.Operations().size());
bool success = true;
TransformOperations result = BlendPrefixByMatchingOperations(
from, matching_prefix_length, progress, &success);
if (success && matching_prefix_length < max_path_length) {
scoped_refptr<TransformOperation> matrix_op =
BlendRemainingByUsingMatrixInterpolation(from, matching_prefix_length,
progress);
if (matrix_op)
result.Operations().push_back(matrix_op);
else
success = false;
}
if (!success) {
return progress < 0.5 ? from : *this;
}
return result;
}
......
......@@ -48,11 +48,21 @@ class PLATFORM_EXPORT TransformOperations {
bool operator==(const TransformOperations& o) const;
bool operator!=(const TransformOperations& o) const { return !(*this == o); }
void Apply(const FloatSize& sz, TransformationMatrix& t) const {
// Constructs a transformation matrix from the operations. The parameter
// |border_box_size| is used when computing styles that are size-dependent.
void Apply(const FloatSize& border_box_size, TransformationMatrix& t) const {
for (auto& operation : operations_)
operation->Apply(t, sz);
operation->Apply(t, border_box_size);
}
// Constructs a transformation matrix from the operations starting from index
// |start|. This process facilitates mixing pairwise operations for a common
// prefix and matrix interpolation for the remainder. The parameter
// |border_box_size| is used when computing styles that are size-dependent.
void ApplyRemaining(const FloatSize& border_box_size,
wtf_size_t start,
TransformationMatrix& t) const;
// Return true if any of the operation types are 3D operation types (even if
// the values describe affine transforms)
bool Has3DOperation() const {
......@@ -80,7 +90,7 @@ class PLATFORM_EXPORT TransformOperations {
return false;
}
bool OperationsMatch(const TransformOperations&) const;
wtf_size_t MatchingPrefixLength(const TransformOperations&) const;
void clear() { operations_.clear(); }
......@@ -101,11 +111,17 @@ class PLATFORM_EXPORT TransformOperations {
const double& min_progress,
const double& max_progress,
FloatBox* bounds) const;
TransformOperations BlendByMatchingOperations(const TransformOperations& from,
const double& progress) const;
scoped_refptr<TransformOperation> BlendByUsingMatrixInterpolation(
TransformOperations BlendPrefixByMatchingOperations(
const TransformOperations& from,
wtf_size_t matching_prefix_length,
double progress,
bool* success) const;
scoped_refptr<TransformOperation> BlendRemainingByUsingMatrixInterpolation(
const TransformOperations& from,
wtf_size_t matching_prefix_length,
double progress) const;
TransformOperations Blend(const TransformOperations& from,
double progress) const;
TransformOperations Add(const TransformOperations& addend) const;
......
......@@ -78,6 +78,23 @@ assertComposition({
{at: 1.5, is: 'rotateX(45deg) rotateY(540deg)'},
]);
// Shorter list is extended with corresponding identity transforms for pairwise
// interpolation.
assertComposition({
property: 'transform',
underlying: 'rotateX(45deg)',
addFrom: 'none',
addTo: 'rotateY(360deg)',
}, [
{at: -0.5, is: 'rotateX(45deg) rotateY(-180deg)'},
{at: 0, is: 'rotateX(45deg) rotateY(0deg)'},
{at: 0.25, is: 'rotateX(45deg) rotateY(90deg)'},
{at: 0.5, is: 'rotateX(45deg) rotateY(180deg)'},
{at: 0.75, is: 'rotateX(45deg) rotateY(270deg)'},
{at: 1, is: 'rotateX(45deg) rotateY(360deg)'},
{at: 1.5, is: 'rotateX(45deg) rotateY(540deg)'},
]);
// Matrix decomposition cases
assertComposition({
property: 'transform',
......@@ -94,10 +111,11 @@ assertComposition({
{at: 1.5, is: 'matrix3d(2.5, 0, 0, 0, 0, 5.55112e-16, 2.5, 0, 0, -1, 2.22045e-16, 0, -50, -3.06162e-15, -50, 1)'},
]);
// Force a fallback to matrix interpolation.
assertComposition({
property: 'transform',
underlying: 'rotateX(45deg)',
addFrom: 'none',
addFrom: 'scaleX(1)',
addTo: 'rotateY(360deg)',
}, [
{at: -0.5, is: 'rotateX(45deg)'},
......@@ -108,5 +126,6 @@ assertComposition({
{at: 1, is: 'rotateX(45deg)'},
{at: 1.5, is: 'rotateX(45deg)'},
]);
</script>
</body>
......@@ -46,24 +46,24 @@ assertInterpolation({
from: 'skewX(10rad)',
to: 'skewX(20rad) scaleZ(2)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, -0.940439289306569, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)'},
{at: 0, is: 'skewX(10rad)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 1.0455608566505006, 1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, 1.8399609150333283, 1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1)'},
{at: -1, is: 'skewX(0rad) scaleZ(0)'},
{at: 0, is: 'skewX(10rad) scaleZ(1)'},
{at: 0.25, is: 'skewX(12.5rad) scaleZ(1.25)'},
{at: 0.75, is: 'skewX(17.5rad) scaleZ(1.75)'},
{at: 1, is: 'skewX(20rad) scaleZ(2)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, 3.825961060990398, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1)'},
{at: 2, is: 'skewX(30rad) scaleZ(3)'},
]);
assertInterpolation({
property: 'transform',
from: 'scaleZ(3) perspective(400px)',
to: 'scaleZ(4) skewX(1rad) perspective(500px)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, -1.5574077246549023, 1, 0, 0, 0, 0, 2, -0.002333333333333333, 0, 0, 0, 1)'},
{at: 0, is: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 0.3893519311637256, 1, 0, 0, 0, 0, 3.25, -0.0024375, 0, 0, 0, 1)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, 1.1680557934911766, 1, 0, 0, 0, 0, 3.75, -0.0021874999999999998, 0, 0, 0, 1)'},
{at: 1, is: 'matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, 0, 0, 4, -0.002, 0, 0, 0, 1)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, 3.1148154493098046, 1, 0, 0, 0, 0, 5, -0.0008333333333333337, 0, 0, 0, 1)'},
{at: -1, is: 'scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)'},
{at: 0, is: 'scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)'},
{at: 0.75, is: 'scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)'},
{at: 1, is: 'scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)'},
{at: 2, is: 'scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)'},
]);
assertInterpolation({
property: 'transform',
......
......@@ -144,24 +144,24 @@ assertInterpolation({
from: 'translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)',
to: 'translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03165032268879389, -0.0036057329645461413, 0.956, -0.002984745620652083, 12, 4, 16, 0.9956416059005948)'},
{at: 0, is: 'matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 1.4600697418639708, 1.25, 0, 0, -0.017032782247925572, 0.013463037465426202, 0.9735, -0.0023764300980638675, 7, -6, 11, 1.0004085994468193)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, 0.68136587953652, 1.75, 0, 0, -0.011032782247925572, 0.0204630374654262, 0.9804999999999999, -0.0021264300980638673, 5, -10, 9, 1.0004085994468193)'},
{at: 1, is: 'matrix3d(1, 0, 0, 0, 0, 2, 0, 0, -0.008, 0.024, 0.984, -0.002, 4, -12, 8, 1)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, -4.672223173964706, 3, 0, 0, 0.0043496773112061, 0.038394267035453865, 0.998, -0.0014847456206520829, 0, -20, 4, 0.9956416059005954)'},
{at: -1, is: 'translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)'},
{at: 0, is: 'translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)'},
{at: 0.75, is: 'translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)'},
{at: 1, is: 'translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)'},
{at: 2, is: 'translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)'},
]);
assertInterpolation({
property: 'transform',
from: 'translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)',
to: 'translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.004845360824742268, 12, 4, 16, 0.9793814432989688)'},
{at: 0, is: 'matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 0.7772447845947462, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.0018894974226804128, 7, -6, 11, 1.0019329896907216)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, -2.1864989409942237, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.0006394974226804124, 5, -10, 9, 1.0019329896907216)'},
{at: 1, is: 'matrix3d(1, 0, 0, 0, -4.370079726523038, 2, 0, 0, 0, 0, 1, 0, 4, -12, 8, 1)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, -17.782462353533823, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, 0.0026546391752577322, 0, -20, 4, 0.9793814432989691)'},
{at: -1, is: 'translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)'},
{at: 0, is: 'translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)'},
{at: 0.75, is: 'translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)'},
{at: 1, is: 'translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)'},
{at: 2, is: 'translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)'},
]);
assertInterpolation({
property: 'transform',
......
......@@ -46,24 +46,24 @@ assertInterpolation({
from: 'skewX(10rad)',
to: 'skewX(20rad) scaleZ(2)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, -0.940439289306569, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)'},
{at: 0, is: 'skewX(10rad)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 1.0455608566505006, 1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, 1.8399609150333283, 1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1)'},
{at: -1, is: 'skewX(0rad) scaleZ(0)'},
{at: 0, is: 'skewX(10rad) scaleZ(1)'},
{at: 0.25, is: 'skewX(12.5rad) scaleZ(1.25)'},
{at: 0.75, is: 'skewX(17.5rad) scaleZ(1.75)'},
{at: 1, is: 'skewX(20rad) scaleZ(2)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, 3.825961060990398, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1)'},
{at: 2, is: 'skewX(30rad) scaleZ(3)'},
]);
assertInterpolation({
property: '-webkit-transform',
from: 'scaleZ(3) perspective(400px)',
to: 'scaleZ(4) skewX(1rad) perspective(500px)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, -1.5574077246549023, 1, 0, 0, 0, 0, 2, -0.002333333333333333, 0, 0, 0, 1)'},
{at: 0, is: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 0.3893519311637256, 1, 0, 0, 0, 0, 3.25, -0.0024375, 0, 0, 0, 1)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, 1.1680557934911766, 1, 0, 0, 0, 0, 3.75, -0.0021874999999999998, 0, 0, 0, 1)'},
{at: 1, is: 'matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, 0, 0, 4, -0.002, 0, 0, 0, 1)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, 3.1148154493098046, 1, 0, 0, 0, 0, 5, -0.0008333333333333337, 0, 0, 0, 1)'},
{at: -1, is: 'scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)'},
{at: 0, is: 'scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)'},
{at: 0.75, is: 'scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)'},
{at: 1, is: 'scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)'},
{at: 2, is: 'scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)'},
]);
assertInterpolation({
property: '-webkit-transform',
......
......@@ -144,24 +144,24 @@ assertInterpolation({
from: 'translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)',
to: 'translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03165032268879389, -0.0036057329645461413, 0.956, -0.002984745620652083, 12, 4, 16, 0.9956416059005948)'},
{at: 0, is: 'matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 1.4600697418639708, 1.25, 0, 0, -0.017032782247925572, 0.013463037465426202, 0.9735, -0.0023764300980638675, 7, -6, 11, 1.0004085994468193)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, 0.68136587953652, 1.75, 0, 0, -0.011032782247925572, 0.0204630374654262, 0.9804999999999999, -0.0021264300980638673, 5, -10, 9, 1.0004085994468193)'},
{at: 1, is: 'matrix3d(1, 0, 0, 0, 0, 2, 0, 0, -0.008, 0.024, 0.984, -0.002, 4, -12, 8, 1)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, -4.672223173964706, 3, 0, 0, 0.0043496773112061, 0.038394267035453865, 0.998, -0.0014847456206520829, 0, -20, 4, 0.9956416059005954)'},
{at: -1, is: 'translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)'},
{at: 0, is: 'translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)'},
{at: 0.75, is: 'translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)'},
{at: 1, is: 'translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)'},
{at: 2, is: 'translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)'},
]);
assertInterpolation({
property: '-webkit-transform',
from: 'translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)',
to: 'translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)'
}, [
{at: -1, is: 'matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.004845360824742268, 12, 4, 16, 0.9793814432989688)'},
{at: 0, is: 'matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)'},
{at: 0.25, is: 'matrix3d(1, 0, 0, 0, 0.7772447845947462, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.0018894974226804128, 7, -6, 11, 1.0019329896907216)'},
{at: 0.75, is: 'matrix3d(1, 0, 0, 0, -2.1864989409942237, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.0006394974226804124, 5, -10, 9, 1.0019329896907216)'},
{at: 1, is: 'matrix3d(1, 0, 0, 0, -4.370079726523038, 2, 0, 0, 0, 0, 1, 0, 4, -12, 8, 1)'},
{at: 2, is: 'matrix3d(1, 0, 0, 0, -17.782462353533823, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, 0.0026546391752577322, 0, -20, 4, 0.9793814432989691)'},
{at: -1, is: 'translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)'},
{at: 0, is: 'translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)'},
{at: 0.25, is: 'translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)'},
{at: 0.75, is: 'translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)'},
{at: 1, is: 'translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)'},
{at: 2, is: 'translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)'},
]);
</script>
</body>
......@@ -26,7 +26,7 @@ rotateReplace.animate([
{rotate: '0 0 1 90deg'},
], timing);
transform.animate([
{transform: 'rotate3d(1, 0, 0, 90deg) rotate3d(0, 1, 0, -180deg)'},
{transform: 'rotate3d(0, 0, 1, 90deg)'},
{transform: 'matrix3d(-1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1)'},
{transform: 'matrix(0, 1, -1, 0, 0, 0)'},
], timing);
</script>
......@@ -20,11 +20,11 @@ function assertNoPostMultiplication(start, end) {
}
function interpolationUsesPostMultiplication(start, end) {
// This value prefix will cause post multiplication to interpolate differently than pairwise
// interpolation. Flipping both the x and y scales is equivalent to a 180 degree rotation in
// its matrix representation.
var testStart = 'scale(-1, -1) ' + start;
var testEnd = 'scale(1, 1) ' + end;
// This value suffix will cause post multiplication to interpolate differently than pairwise
// interpolation. Rotating by 370 degrees is equivalent to rotating by 10 degrees in the matrix
// representation.
var testStart = start + ' rotate(370deg)';
var testEnd = end + ' rotate(0deg)';
var animation = target.animate({transform: [toMatrix(testStart), toMatrix(testEnd)]}, 1);
animation.currentTime = 0.5;
......
......@@ -6,9 +6,9 @@ PASS none -> something: Animation between "none" and "translate(200px) rotate(72
PASS something -> none: "translate(200px) rotate(720deg)" and "none" are valid transform values
PASS something -> none: Animation between "translate(200px) rotate(720deg)" and "none" at progress 0.25
PASS Mismatched lengths (from is shorter), common part matches: "translate(100px)" and "translate(200px) rotate(720deg)" are valid transform values
FAIL Mismatched lengths (from is shorter), common part matches: Animation between "translate(100px)" and "translate(200px) rotate(720deg)" at progress 0.25 assert_equals: expected "matrix(-1, 0, 0, -1, 125, 0)" but got "matrix(1, 0, 0, 1, 125, 0)"
PASS Mismatched lengths (from is shorter), common part matches: Animation between "translate(100px)" and "translate(200px) rotate(720deg)" at progress 0.25
PASS Mismatched lengths (to is shorter), common part matches: "translate(100px) rotate(720deg)" and "translate(200px)" are valid transform values
FAIL Mismatched lengths (to is shorter), common part matches: Animation between "translate(100px) rotate(720deg)" and "translate(200px)" at progress 0.25 assert_equals: expected "matrix(-1, 0, 0, -1, 125, 0)" but got "matrix(1, 0, 0, 1, 125, 0)"
PASS Mismatched lengths (to is shorter), common part matches: Animation between "translate(100px) rotate(720deg)" and "translate(200px)" at progress 0.25
PASS Perfect match: "scale(2) rotate(360deg) translate(100px) matrix(1, 0, 0, 1, 100, 0) skew(0deg)" and "scale(3) rotate(1080deg) translate(200px) matrix(1, 0, 0, 1, 0, 200) skew(720deg)" are valid transform values
PASS Perfect match: Animation between "scale(2) rotate(360deg) translate(100px) matrix(1, 0, 0, 1, 100, 0) skew(0deg)" and "scale(3) rotate(1080deg) translate(200px) matrix(1, 0, 0, 1, 0, 200) skew(720deg)" at progress 0.25
PASS Matches on primitives: "translateX(100px) scaleX(3) translate(500px) scale(2)" and "translateY(200px) scale(5) translateX(100px) scaleY(3)" are valid transform values
......@@ -18,20 +18,20 @@ FAIL Match on rotation vector: Animation between "rotateX(90deg) translateX(100p
PASS Match on rotation due to 0deg angle: "rotateX(90deg) translateX(100px)" and "rotateY(0deg) translateY(200px)" are valid transform values
FAIL Match on rotation due to 0deg angle: Animation between "rotateX(90deg) translateX(100px)" and "rotateY(0deg) translateY(200px)" at progress 0.25 assert_equals: expected "matrix3d(1, 0, 0, 0, 0, 0.382683, 0.92388, 0, 0, -0.92388, 0.382683, 0, 75, 19.1342, 46.194, 1)" but got "matrix(1, 0, 0, 1, 75, 50)"
PASS Common prefix: "rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" are valid transform values
FAIL Common prefix: Animation between "rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" at progress 0.25 assert_equals: expected "matrix(-1.25, 0, 0, -1.25, -175, 0)" but got "matrix(1.25, 0, 0, 1.25, 175, 0)"
PASS Common prefix: Animation between "rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" at progress 0.25
PASS Complete mismatch (except length): "scale(2) rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" are valid transform values
PASS Complete mismatch (except length): Animation between "scale(2) rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" at progress 0.25
PASS Complete mismatch including length: "scale(2) rotate(0deg)" and "rotate(720deg) scale(2) translate(200px)" are valid transform values
PASS Complete mismatch including length: Animation between "scale(2) rotate(0deg)" and "rotate(720deg) scale(2) translate(200px)" at progress 0.25
PASS Mismatched lengths (from is shorter), partial match: "rotate(0deg) scaleX(1)" and "rotate(720deg) translateX(0px) scaleX(2)" are valid transform values
FAIL Mismatched lengths (from is shorter), partial match: Animation between "rotate(0deg) scaleX(1)" and "rotate(720deg) translateX(0px) scaleX(2)" at progress 0.25 assert_equals: expected "matrix(-1.25, 0, 0, -1, 0, 0)" but got "matrix(1.25, 0, 0, 1, 0, 0)"
PASS Mismatched lengths (from is shorter), partial match: Animation between "rotate(0deg) scaleX(1)" and "rotate(720deg) translateX(0px) scaleX(2)" at progress 0.25
PASS Mismatched lengths (to is shorter), partial match: "rotate(720deg) translateX(0px) scaleX(2)" and "rotate(0deg) scaleX(1)" are valid transform values
FAIL Mismatched lengths (to is shorter), partial match: Animation between "rotate(720deg) translateX(0px) scaleX(2)" and "rotate(0deg) scaleX(1)" at progress 0.25 assert_equals: expected "matrix(-1.75, 0, 0, -1, 0, 0)" but got "matrix(1.75, 0, 0, 1, 0, 0)"
PASS Mismatched lengths (to is shorter), partial match: Animation between "rotate(720deg) translateX(0px) scaleX(2)" and "rotate(0deg) scaleX(1)" at progress 0.25
PASS Mismatched lengths (from is shorter), partial match on primitive: "scaleX(-3) scaleY(2)" and "scaleY(-3) translateX(0px) scaleX(2)" are valid transform values
FAIL Mismatched lengths (from is shorter), partial match on primitive: Animation between "scaleX(-3) scaleY(2)" and "scaleY(-3) translateX(0px) scaleX(2)" at progress 0.25 assert_equals: expected "matrix(-2.5, 0, 0, 0, 0, 0)" but got "matrix(-1.75, 0, 0, 0.75, 0, 0)"
PASS Mismatched lengths (from is shorter), partial match on primitive: Animation between "scaleX(-3) scaleY(2)" and "scaleY(-3) translateX(0px) scaleX(2)" at progress 0.25
PASS Mismatched lengths (to is shorter), partial match on primitive: "scaleY(-3) translateX(0px) scaleX(2)" and "scaleX(-3) scaleY(2)" are valid transform values
FAIL Mismatched lengths (to is shorter), partial match on primitive: Animation between "scaleY(-3) translateX(0px) scaleX(2)" and "scaleX(-3) scaleY(2)" at progress 0.25 assert_equals: expected "matrix(0, 0, 0, -2.5, 0, 0)" but got "matrix(0.75, 0, 0, -1.75, 0, 0)"
PASS Mismatched lengths (to is shorter), partial match on primitive: Animation between "scaleY(-3) translateX(0px) scaleX(2)" and "scaleX(-3) scaleY(2)" at progress 0.25
PASS Common prefix on primitive: "scaleY(-3) translateX(0px)" and "scaleX(-3) scaleY(2)" are valid transform values
FAIL Common prefix on primitive: Animation between "scaleY(-3) translateX(0px)" and "scaleX(-3) scaleY(2)" at progress 0.25 assert_equals: expected "matrix(0, 0, 0, -2.5, 0, 0)" but got "matrix(0, 0, 0, -1.75, 0, 0)"
PASS Common prefix on primitive: Animation between "scaleY(-3) translateX(0px)" and "scaleX(-3) scaleY(2)" at progress 0.25
Harness: the test ran to completion.
This is a testharness.js-based test.
Found 701 tests; 645 PASS, 56 FAIL, 0 TIMEOUT, 0 NOTRUN.
Found 701 tests; 648 PASS, 53 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS Setup
PASS align-content (type: discrete) has testInterpolation function
PASS align-content uses discrete animation when animating between "flex-start" and "flex-end" with linear easing
......@@ -641,8 +641,8 @@ PASS transform: scale
PASS transform: skew
PASS transform: rotate and translate
PASS transform: translate and rotate
FAIL transform: extend shorter list (from) assert_approx_equals: expected matrix(-1, 0, 0, -1, -50, 0) but got matrix(1, 0, 0, 1, 50, 0): The value should be matrix(-1, 0, 0, -1, -50, 0) at 500ms but got matrix(1, 0, 0, 1, 50, 0) expected -1 +/- 0.0001 but got 1
FAIL transform: extend shorter list (to) assert_approx_equals: expected matrix(-1, 0, 0, -1, -50, 0) but got matrix(1, 0, 0, 1, 50, 0): The value should be matrix(-1, 0, 0, -1, -50, 0) at 500ms but got matrix(1, 0, 0, 1, 50, 0) expected -1 +/- 0.0001 but got 1
PASS transform: extend shorter list (from)
PASS transform: extend shorter list (to)
PASS transform: mismatch order of translate and rotate
PASS transform: matrix
PASS transform: rotate3d
......@@ -650,7 +650,7 @@ PASS transform: matrix3d
PASS transform: mismatched 3D transforms
PASS transform: rotateY
PASS transform: non-invertible matrices
FAIL transform: non-invertible matrices in matched transform lists assert_approx_equals: expected matrix(0, -1, 1, 0, 250, 0) but got matrix(0.705995, -0.708217, 0.708217, 0.705995, 274.95, 0): The value should be matrix(0, -1, 1, 0, 250, 0) at 499ms but got matrix(0.705995, -0.708217, 0.708217, 0.705995, 274.95, 0) expected 0 +/- 0.0001 but got 0.705995
PASS transform: non-invertible matrices in matched transform lists
PASS transform: non-invertible matrices in mismatched transform lists
PASS transform: perspective
PASS transform-box (type: discrete) has testInterpolation function
......
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