// 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 "ui/gfx/transform_operations.h" #include #include #include #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 gfx { TransformOperations::TransformOperations() {} TransformOperations::TransformOperations(const TransformOperations& other) { operations_ = other.operations_; } TransformOperations::~TransformOperations() = default; TransformOperations& TransformOperations::operator=( const TransformOperations& other) { operations_ = other.operations_; return *this; } Transform TransformOperations::Apply() const { return ApplyRemaining(0); } Transform TransformOperations::ApplyRemaining(size_t start) const { 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, SkScalar 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 BoxF& box, const TransformOperations& from, SkScalar min_progress, SkScalar max_progress, 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; 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_SKEWX: case TransformOperation::TRANSFORM_OPERATION_SKEWY: 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_SKEWX: case TransformOperation::TRANSFORM_OPERATION_SKEWY: case TransformOperation::TRANSFORM_OPERATION_SKEW: case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE: return false; } } return true; } static SkScalar TanDegrees2(double degrees) { return SkDoubleToScalar(std::tan(DegToRad(degrees))); } bool TransformOperations::ScaleComponent(SkScalar* scale) const { SkScalar 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; Vector2dF scale_components = ComputeTransform2dScaleComponents(operation.matrix, 1.f); operations_scale *= std::max(scale_components.x(), scale_components.y()); break; } case TransformOperation::TRANSFORM_OPERATION_SKEWX: case TransformOperation::TRANSFORM_OPERATION_SKEWY: case TransformOperation::TRANSFORM_OPERATION_SKEW: { SkScalar x_component = TanDegrees2(operation.skew.x); SkScalar y_component = TanDegrees2(operation.skew.y); SkScalar x_scale = std::sqrt(x_component * x_component + 1); SkScalar 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(SkScalar x, SkScalar y, SkScalar 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(SkScalar x, SkScalar y, SkScalar z, SkScalar 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(SkScalar x, SkScalar y, SkScalar 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::AppendSkewX(SkScalar x) { TransformOperation to_add; to_add.type = TransformOperation::TRANSFORM_OPERATION_SKEWX; to_add.skew.x = x; to_add.skew.y = 0; to_add.Bake(); operations_.push_back(to_add); decomposed_transforms_.clear(); } void TransformOperations::AppendSkewY(SkScalar y) { TransformOperation to_add; to_add.type = TransformOperation::TRANSFORM_OPERATION_SKEWY; to_add.skew.x = 0; to_add.skew.y = y; to_add.Bake(); operations_.push_back(to_add); decomposed_transforms_.clear(); } void TransformOperations::AppendSkew(SkScalar x, SkScalar 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(SkScalar 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 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, SkScalar 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, SkScalar 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; } DecomposedTransform matrix_transform = 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 decomposed_transform = std::make_unique(); Transform transform = ApplyRemaining(start_offset); if (!DecomposeTransform(decomposed_transform.get(), transform)) return false; decomposed_transforms_[start_offset] = std::move(decomposed_transform); } return true; } } // namespace gfx