| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/animation/transform_operations.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| |
| #include "cc/base/math_util.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/geometry/angle_conversions.h" |
| #include "ui/gfx/geometry/box_f.h" |
| #include "ui/gfx/geometry/vector3d_f.h" |
| #include "ui/gfx/transform_util.h" |
| |
| namespace cc { |
| |
| TransformOperations::TransformOperations() {} |
| |
| TransformOperations::TransformOperations(const TransformOperations& other) { |
| operations_ = other.operations_; |
| } |
| |
| TransformOperations::~TransformOperations() = default; |
| |
| TransformOperations& TransformOperations::operator=( |
| const TransformOperations& other) { |
| operations_ = other.operations_; |
| return *this; |
| } |
| |
| gfx::Transform TransformOperations::Apply() const { |
| return ApplyRemaining(0); |
| } |
| |
| gfx::Transform TransformOperations::ApplyRemaining(size_t start) const { |
| gfx::Transform to_return; |
| 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; |
| if (!BlendInternal(from, progress, &to_return)) { |
| // If the matrices cannot be blended, fallback to discrete animation logic. |
| // See https://drafts.csswg.org/css-transforms/#matrix-interpolation |
| to_return = progress < 0.5 ? from : *this; |
| } |
| return to_return; |
| } |
| |
| bool TransformOperations::BlendedBoundsForBox(const gfx::BoxF& box, |
| const TransformOperations& from, |
| SkMScalar min_progress, |
| SkMScalar max_progress, |
| gfx::BoxF* bounds) const { |
| *bounds = box; |
| |
| bool from_identity = from.IsIdentity(); |
| bool to_identity = IsIdentity(); |
| if (from_identity && to_identity) |
| return true; |
| |
| if (!MatchesTypes(from)) |
| return false; |
| |
| size_t num_operations = std::max(from_identity ? 0 : from.operations_.size(), |
| to_identity ? 0 : operations_.size()); |
| |
| // Because we are squashing all of the matrices together when applying |
| // them to the animation, we must apply them in reverse order when |
| // not squashing them. |
| for (size_t i = 0; i < num_operations; ++i) { |
| size_t operation_index = num_operations - 1 - i; |
| gfx::BoxF bounds_for_operation; |
| const TransformOperation* from_op = |
| from_identity ? nullptr : &from.operations_[operation_index]; |
| const TransformOperation* to_op = |
| to_identity ? nullptr : &operations_[operation_index]; |
| if (!TransformOperation::BlendedBoundsForBox(*bounds, from_op, to_op, |
| min_progress, max_progress, |
| &bounds_for_operation)) { |
| return false; |
| } |
| *bounds = bounds_for_operation; |
| } |
| |
| return true; |
| } |
| |
| bool TransformOperations::PreservesAxisAlignment() const { |
| for (auto& operation : operations_) { |
| switch (operation.type) { |
| case TransformOperation::TRANSFORM_OPERATION_IDENTITY: |
| case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: |
| case TransformOperation::TRANSFORM_OPERATION_SCALE: |
| continue; |
| case TransformOperation::TRANSFORM_OPERATION_MATRIX: |
| if (!operation.matrix.IsIdentity() && |
| !operation.matrix.IsScaleOrTranslation()) |
| return false; |
| continue; |
| case TransformOperation::TRANSFORM_OPERATION_ROTATE: |
| case TransformOperation::TRANSFORM_OPERATION_SKEW: |
| case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool TransformOperations::IsTranslation() const { |
| for (auto& operation : operations_) { |
| switch (operation.type) { |
| case TransformOperation::TRANSFORM_OPERATION_IDENTITY: |
| case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: |
| continue; |
| case TransformOperation::TRANSFORM_OPERATION_MATRIX: |
| if (!operation.matrix.IsIdentityOrTranslation()) |
| return false; |
| continue; |
| case TransformOperation::TRANSFORM_OPERATION_ROTATE: |
| case TransformOperation::TRANSFORM_OPERATION_SCALE: |
| case TransformOperation::TRANSFORM_OPERATION_SKEW: |
| case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static SkMScalar TanDegrees(double degrees) { |
| return SkDoubleToMScalar(std::tan(gfx::DegToRad(degrees))); |
| } |
| |
| bool TransformOperations::ScaleComponent(SkMScalar* scale) const { |
| SkMScalar operations_scale = 1.f; |
| for (auto& operation : operations_) { |
| switch (operation.type) { |
| case TransformOperation::TRANSFORM_OPERATION_IDENTITY: |
| case TransformOperation::TRANSFORM_OPERATION_TRANSLATE: |
| case TransformOperation::TRANSFORM_OPERATION_ROTATE: |
| continue; |
| case TransformOperation::TRANSFORM_OPERATION_MATRIX: { |
| if (operation.matrix.HasPerspective()) |
| return false; |
| gfx::Vector2dF scale_components = |
| MathUtil::ComputeTransform2dScaleComponents(operation.matrix, 1.f); |
| operations_scale *= |
| std::max(scale_components.x(), scale_components.y()); |
| break; |
| } |
| case TransformOperation::TRANSFORM_OPERATION_SKEW: { |
| SkMScalar x_component = TanDegrees(operation.skew.x); |
| SkMScalar y_component = TanDegrees(operation.skew.y); |
| SkMScalar x_scale = std::sqrt(x_component * x_component + 1); |
| SkMScalar y_scale = std::sqrt(y_component * y_component + 1); |
| operations_scale *= std::max(x_scale, y_scale); |
| break; |
| } |
| case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: |
| return false; |
| case TransformOperation::TRANSFORM_OPERATION_SCALE: |
| operations_scale *= std::max( |
| std::abs(operation.scale.x), |
| std::max(std::abs(operation.scale.y), std::abs(operation.scale.z))); |
| } |
| } |
| *scale = operations_scale; |
| return true; |
| } |
| |
| bool TransformOperations::MatchesTypes(const TransformOperations& other) const { |
| if (operations_.size() == 0 || other.operations_.size() == 0) |
| return true; |
| |
| if (operations_.size() != other.operations_.size()) |
| return false; |
| |
| for (size_t i = 0; i < operations_.size(); ++i) { |
| if (operations_[i].type != other.operations_[i].type) |
| return false; |
| } |
| |
| 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; |
| return BlendInternal(other, 0.5, &dummy); |
| } |
| |
| void TransformOperations::AppendTranslate(SkMScalar x, |
| SkMScalar y, |
| SkMScalar z) { |
| TransformOperation to_add; |
| to_add.matrix.Translate3d(x, y, z); |
| to_add.type = TransformOperation::TRANSFORM_OPERATION_TRANSLATE; |
| to_add.translate.x = x; |
| to_add.translate.y = y; |
| to_add.translate.z = z; |
| operations_.push_back(to_add); |
| decomposed_transforms_.clear(); |
| } |
| |
| void TransformOperations::AppendRotate(SkMScalar x, |
| SkMScalar y, |
| SkMScalar z, |
| SkMScalar degrees) { |
| TransformOperation to_add; |
| to_add.type = TransformOperation::TRANSFORM_OPERATION_ROTATE; |
| to_add.rotate.axis.x = x; |
| to_add.rotate.axis.y = y; |
| to_add.rotate.axis.z = z; |
| to_add.rotate.angle = degrees; |
| to_add.Bake(); |
| operations_.push_back(to_add); |
| decomposed_transforms_.clear(); |
| } |
| |
| void TransformOperations::AppendScale(SkMScalar x, SkMScalar y, SkMScalar z) { |
| TransformOperation to_add; |
| to_add.type = TransformOperation::TRANSFORM_OPERATION_SCALE; |
| to_add.scale.x = x; |
| to_add.scale.y = y; |
| to_add.scale.z = z; |
| to_add.Bake(); |
| operations_.push_back(to_add); |
| decomposed_transforms_.clear(); |
| } |
| |
| void TransformOperations::AppendSkew(SkMScalar x, SkMScalar y) { |
| TransformOperation to_add; |
| to_add.type = TransformOperation::TRANSFORM_OPERATION_SKEW; |
| to_add.skew.x = x; |
| to_add.skew.y = y; |
| to_add.Bake(); |
| operations_.push_back(to_add); |
| decomposed_transforms_.clear(); |
| } |
| |
| void TransformOperations::AppendPerspective(SkMScalar depth) { |
| TransformOperation to_add; |
| to_add.type = TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE; |
| to_add.perspective_depth = depth; |
| to_add.Bake(); |
| operations_.push_back(to_add); |
| decomposed_transforms_.clear(); |
| } |
| |
| void TransformOperations::AppendMatrix(const gfx::Transform& matrix) { |
| TransformOperation to_add; |
| to_add.matrix = matrix; |
| to_add.type = TransformOperation::TRANSFORM_OPERATION_MATRIX; |
| operations_.push_back(to_add); |
| decomposed_transforms_.clear(); |
| } |
| |
| void TransformOperations::AppendIdentity() { |
| operations_.push_back(TransformOperation()); |
| } |
| |
| void TransformOperations::Append(const TransformOperation& operation) { |
| operations_.push_back(operation); |
| decomposed_transforms_.clear(); |
| } |
| |
| bool TransformOperations::IsIdentity() const { |
| for (auto& operation : operations_) { |
| if (!operation.IsIdentity()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool TransformOperations::ApproximatelyEqual(const TransformOperations& other, |
| SkMScalar tolerance) const { |
| if (size() != other.size()) |
| return false; |
| for (size_t i = 0; i < operations_.size(); ++i) { |
| if (!operations_[i].ApproximatelyEqual(other.operations_[i], tolerance)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool TransformOperations::BlendInternal(const TransformOperations& from, |
| SkMScalar progress, |
| TransformOperations* result) const { |
| bool from_identity = from.IsIdentity(); |
| bool to_identity = IsIdentity(); |
| if (from_identity && to_identity) |
| return true; |
| |
| 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; |
| } |
| result->Append(blended); |
| } |
| |
| 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( |
| 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_transforms_[start_offset] = std::move(decomposed_transform); |
| } |
| return true; |
| } |
| |
| } // namespace cc |