// Copyright 2014 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/layers/layer_utils.h" #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" #include "cc/animation/transform_operations.h" #include "cc/layers/layer_impl.h" #include "cc/test/animation_test_common.h" #include "cc/test/fake_impl_task_runner_provider.h" #include "cc/test/fake_layer_tree_host_impl.h" #include "cc/test/test_shared_bitmap_manager.h" #include "cc/test/test_task_graph_runner.h" #include "cc/trees/layer_tree_impl.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/geometry/box_f.h" #include "ui/gfx/test/gfx_util.h" namespace cc { namespace { float diagonal(float width, float height) { return std::sqrt(width * width + height * height); } class LayerUtilsGetAnimationBoundsTest : public testing::Test { public: LayerUtilsGetAnimationBoundsTest() : host_impl_(&task_runner_provider_, &shared_bitmap_manager_, &task_graph_runner_), root_(CreateTwoForkTree(&host_impl_)), parent1_(root_->children()[0]), parent2_(root_->children()[1]), child1_(parent1_->children()[0]), child2_(parent2_->children()[0]), grand_child_(child2_->children()[0]), great_grand_child_(grand_child_->children()[0]) { timeline_ = AnimationTimeline::Create(AnimationIdProvider::NextTimelineId()); host_impl_.animation_host()->AddAnimationTimeline(timeline_); } LayerImpl* root() { return root_; } LayerImpl* parent1() { return parent1_; } LayerImpl* child1() { return child1_; } LayerImpl* parent2() { return parent2_; } LayerImpl* child2() { return child2_; } LayerImpl* grand_child() { return grand_child_; } LayerImpl* great_grand_child() { return great_grand_child_; } scoped_refptr timeline() { return timeline_; } FakeLayerTreeHostImpl& host_impl() { return host_impl_; } private: static LayerImpl* CreateTwoForkTree(LayerTreeHostImpl* host_impl) { scoped_ptr root = LayerImpl::Create(host_impl->active_tree(), 1); LayerImpl* root_ptr = root.get(); root->AddChild(LayerImpl::Create(host_impl->active_tree(), 2)); root->children()[0]->AddChild( LayerImpl::Create(host_impl->active_tree(), 3)); root->AddChild(LayerImpl::Create(host_impl->active_tree(), 4)); root->children()[1]->AddChild( LayerImpl::Create(host_impl->active_tree(), 5)); root->children()[1]->children()[0]->AddChild( LayerImpl::Create(host_impl->active_tree(), 6)); root->children()[1]->children()[0]->children()[0]->AddChild( LayerImpl::Create(host_impl->active_tree(), 7)); host_impl->active_tree()->SetRootLayer(std::move(root)); return root_ptr; } FakeImplTaskRunnerProvider task_runner_provider_; TestSharedBitmapManager shared_bitmap_manager_; TestTaskGraphRunner task_graph_runner_; FakeLayerTreeHostImpl host_impl_; LayerImpl* root_; LayerImpl* parent1_; LayerImpl* parent2_; LayerImpl* child1_; LayerImpl* child2_; LayerImpl* grand_child_; LayerImpl* great_grand_child_; scoped_refptr timeline_; }; TEST_F(LayerUtilsGetAnimationBoundsTest, ScaleRoot) { double duration = 1.0; TransformOperations start; start.AppendScale(1.f, 1.f, 1.f); TransformOperations end; end.AppendScale(2.f, 2.f, 1.f); AddAnimatedTransformToLayerWithPlayer(root()->id(), timeline(), duration, start, end); root()->SetPosition(gfx::PointF()); parent1()->SetPosition(gfx::PointF()); parent1()->SetBounds(gfx::Size(350, 200)); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(gfx::Size(100, 200)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(150.f, 50.f, 0.f, 350.f, 450.f, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, TranslateParentLayer) { double duration = 1.0; TransformOperations start; start.AppendTranslate(0.f, 0.f, 0.f); TransformOperations end; end.AppendTranslate(50.f, 50.f, 0.f); AddAnimatedTransformToLayerWithPlayer(parent1()->id(), timeline(), duration, start, end); parent1()->SetBounds(gfx::Size(350, 200)); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(gfx::Size(100, 200)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(150.f, 50.f, 0.f, 150.f, 250.f, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, TranslateChildLayer) { double duration = 1.0; TransformOperations start; start.AppendTranslate(0.f, 0.f, 0.f); TransformOperations end; end.AppendTranslate(50.f, 50.f, 0.f); AddAnimatedTransformToLayerWithPlayer(child1()->id(), timeline(), duration, start, end); parent1()->SetBounds(gfx::Size(350, 200)); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(gfx::Size(100, 200)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(150.f, 50.f, 0.f, 150.f, 250.f, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, TranslateBothLayers) { double duration = 1.0; TransformOperations start; start.AppendTranslate(0.f, 0.f, 0.f); TransformOperations child_end; child_end.AppendTranslate(50.f, 0.f, 0.f); AddAnimatedTransformToLayerWithPlayer(parent1()->id(), timeline(), duration, start, child_end); TransformOperations grand_child_end; grand_child_end.AppendTranslate(0.f, 50.f, 0.f); AddAnimatedTransformToLayerWithPlayer(child1()->id(), timeline(), duration, start, grand_child_end); parent1()->SetBounds(gfx::Size(350, 200)); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(gfx::Size(100, 200)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(150.f, 50.f, 0.f, 150.f, 250.f, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXNoPerspective) { double duration = 1.0; TransformOperations start; start.AppendRotate(1.f, 0.f, 0.f, 0.f); TransformOperations end; end.AppendRotate(1.f, 0.f, 0.f, 90.f); AddAnimatedTransformToLayerWithPlayer(child1()->id(), timeline(), duration, start, end); parent1()->SetBounds(gfx::Size(350, 200)); gfx::Size bounds(100, 100); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(bounds); child1()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(150.f, 50.f, -50.f, 100.f, 100.f, 100.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXWithPerspective) { double duration = 1.0; TransformOperations start; start.AppendRotate(1.f, 0.f, 0.f, 0.f); TransformOperations end; end.AppendRotate(1.f, 0.f, 0.f, 90.f); AddAnimatedTransformToLayerWithPlayer(child1()->id(), timeline(), duration, start, end); // Make the anchor point not the default 0.5 value and line up with the // child center to make the math easier. parent1()->SetTransformOrigin( gfx::Point3F(0.375f * 400.f, 0.375f * 400.f, 0.f)); parent1()->SetBounds(gfx::Size(400, 400)); gfx::Transform perspective; perspective.ApplyPerspectiveDepth(100.f); parent1()->SetTransform(perspective); gfx::Size bounds(100, 100); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(100.f, 100.f)); child1()->SetBounds(bounds); child1()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(50.f, 50.f, -33.333336f, 200.f, 200.f, 133.333344f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXWithPerspectiveOnSameLayer) { // This test is used to check whether GetAnimationBounds correctly ignores the // local transform when there is an animation applied to the layer / node. // The intended behavior is that the animation should overwrite the transform. double duration = 1.0; TransformOperations start; start.AppendRotate(1.f, 0.f, 0.f, 0.f); TransformOperations end; end.AppendRotate(1.f, 0.f, 0.f, 90.f); AddAnimatedTransformToLayerWithPlayer(parent1()->id(), timeline(), duration, start, end); // Make the anchor point not the default 0.5 value and line up // with the child center to make the math easier. parent1()->SetTransformOrigin( gfx::Point3F(0.375f * 400.f, 0.375f * 400.f, 0.f)); parent1()->SetBounds(gfx::Size(400, 400)); gfx::Transform perspective; perspective.ApplyPerspectiveDepth(100.f); parent1()->SetTransform(perspective); gfx::Size bounds(100, 100); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(100.f, 100.f)); child1()->SetBounds(bounds); child1()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); gfx::BoxF expected(100.f, 100.f, -50.f, 100.f, 100.f, 100.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateZ) { double duration = 1.0; TransformOperations start; start.AppendRotate(0.f, 0.f, 1.f, 0.f); TransformOperations end; end.AppendRotate(0.f, 0.f, 1.f, 90.f); AddAnimatedTransformToLayerWithPlayer(child1()->id(), timeline(), duration, start, end); parent1()->SetBounds(gfx::Size(350, 200)); gfx::Size bounds(100, 100); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(bounds); child1()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_TRUE(success); float diag = diagonal(bounds.width(), bounds.height()); gfx::BoxF expected(150.f + 0.5f * (bounds.width() - diag), 50.f + 0.5f * (bounds.height() - diag), 0.f, diag, diag, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, MismatchedTransforms) { double duration = 1.0; TransformOperations start; start.AppendTranslate(5, 6, 7); TransformOperations end; end.AppendRotate(0.f, 0.f, 1.f, 90.f); AddAnimatedTransformToLayerWithPlayer(child1()->id(), timeline(), duration, start, end); parent1()->SetBounds(gfx::Size(350, 200)); gfx::Size bounds(100, 100); child1()->SetDrawsContent(true); child1()->SetPosition(gfx::PointF(150.f, 50.f)); child1()->SetBounds(bounds); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*child1(), &box); EXPECT_FALSE(success); } TEST_F(LayerUtilsGetAnimationBoundsTest, TransformUnderAncestorsWithPositionOr2DTransform) { // Tree topology: // +root (Bounds) // +--parent2 (Position) // +----child2 (2d transform) // +------grand_child (Transform animation) // +--------great_grand_child (DrawsContent) // This test is used to check if GetAnimationBounds correctly skips layers and // take layers which do not own a transform_node into consideration. // Under this topology, only root and grand_child own transform_nodes. double duration = 1.0; TransformOperations start; start.AppendTranslate(0.f, 0.f, 0.f); TransformOperations great_grand_child_end; great_grand_child_end.AppendTranslate(50.f, 0.f, 0.f); AddAnimatedTransformToLayerWithPlayer(grand_child()->id(), timeline(), duration, start, great_grand_child_end); gfx::Transform translate_2d_transform; translate_2d_transform.Translate(80.f, 60.f); root()->SetBounds(gfx::Size(350, 200)); parent2()->SetPosition(gfx::PointF(40.f, 45.f)); child2()->SetTransform(translate_2d_transform); great_grand_child()->SetDrawsContent(true); great_grand_child()->SetPosition(gfx::PointF(150.f, 50.f)); great_grand_child()->SetBounds(gfx::Size(100, 200)); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box); EXPECT_TRUE(success); gfx::BoxF expected(270.f, 155.f, 0.f, 150.f, 200.f, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateZUnderAncestorsWithPositionOr2DTransform) { double duration = 1.0; TransformOperations start; start.AppendRotate(0.f, 0.f, 1.f, 0.f); TransformOperations great_grand_child_end; great_grand_child_end.AppendRotate(0.f, 0.f, 1.f, 90.f); AddAnimatedTransformToLayerWithPlayer(grand_child()->id(), timeline(), duration, start, great_grand_child_end); gfx::Transform translate_2d_transform; translate_2d_transform.Translate(80.f, 60.f); root()->SetBounds(gfx::Size(350, 200)); parent2()->SetPosition(gfx::PointF(40.f, 45.f)); child2()->SetTransform(translate_2d_transform); gfx::Size bounds(100, 100); grand_child()->SetPosition(gfx::PointF(150.f, 50.f)); grand_child()->SetBounds(bounds); grand_child()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0)); great_grand_child()->SetPosition(gfx::PointF(25.f, 25.f)); great_grand_child()->SetBounds(gfx::Size(50.f, 50.f)); great_grand_child()->SetDrawsContent(true); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box); EXPECT_TRUE(success); float diag = diagonal(50.f, 50.f); gfx::BoxF expected(320.f - 0.5f * diag, 205.f - 0.5f * diag, 0.f, diag, diag, 0.f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXWithPerspectiveUnderAncestorsWithPositionOr2DTransform) { // Tree topology: // +root (Bounds) // +--parent2 (Position) // +----child2 (2d transform) // +------grand_child (Perspective) // +--------great_grand_child (RotateX, DrawsContent) // Due to the RotateX animation, great_grand_child also owns a transform_node double duration = 1.0; TransformOperations start; start.AppendRotate(1.f, 0.f, 0.f, 0.f); TransformOperations great_grand_child_end; great_grand_child_end.AppendRotate(1.f, 0.f, 0.f, 90.f); AddAnimatedTransformToLayerWithPlayer(great_grand_child()->id(), timeline(), duration, start, great_grand_child_end); gfx::Transform translate_2d_transform; translate_2d_transform.Translate(80.f, 60.f); root()->SetBounds(gfx::Size(350, 200)); parent2()->SetPosition(gfx::PointF(40.f, 45.f)); child2()->SetTransform(translate_2d_transform); gfx::Transform perspective; perspective.ApplyPerspectiveDepth(100.f); gfx::Size bounds(100.f, 100.f); grand_child()->SetPosition(gfx::PointF(150.f, 50.f)); grand_child()->SetBounds(bounds); grand_child()->SetTransform(perspective); grand_child()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.5f, bounds.height() * 0.5f, 0)); great_grand_child()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.25f, bounds.height() * 0.25f, 0)); great_grand_child()->SetPosition(gfx::PointF(25.f, 25.f)); great_grand_child()->SetBounds(gfx::Size(50.f, 50.f)); great_grand_child()->SetDrawsContent(true); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box); EXPECT_TRUE(success); // Translate and position offset to the root: // 40 + 150 + 80 + 25 = 295 // 45 + 60 +50 + 25 = 180 // 0 // Animation Bounds Before Perspective: // 0, 0, -25, 50, 50, 50 // Apply Perspective: // When RotateX theta: // y // ^ // d1 | // | / | // | / | // |/ | // /| | // / | | // / | | // theta | // z<----------------------X // d2 // // the diag is 25 * 2 // // box w: 2 * 25 * 100 / (100 - 25 * sin(theta)), max = 200 / 3 = 66.67 // h: same as w, 66.67 // d1: 25 * 100 * sin(theta) / (100 + 25 * sin(theta)), max = 100 / 5 // = 20 // d2: 25 * 100 * sin(theta) / (100 - 25 * sin(theta)), max = 100 / 3 // = 33.33 // d = d1 + d2 = 53.33 // // Position Fix: // 295 - (66.67 - 50) / 2 = 286.67 // 180 - (66.67 - 50) / 2 = 171.67 // 0 - 20 = -20 gfx::BoxF expected(286.666687f, 171.666672f, -20.f, 66.666656f, 66.666672f, 53.333336f); EXPECT_BOXF_EQ(expected, box); } TEST_F(LayerUtilsGetAnimationBoundsTest, RotateXNoPerspectiveUnderAncestorsWithPositionOr2DTransform) { double duration = 1.0; TransformOperations start; start.AppendRotate(1.f, 0.f, 0.f, 0.f); TransformOperations rotate_x_end; rotate_x_end.AppendRotate(1.f, 0.f, 0.f, 90.f); AddAnimatedTransformToLayerWithPlayer(great_grand_child()->id(), timeline(), duration, start, rotate_x_end); gfx::Transform translate_2d_transform; translate_2d_transform.Translate(80.f, 60.f); root()->SetBounds(gfx::Size(350, 200)); parent2()->SetPosition(gfx::PointF(40.f, 45.f)); child2()->SetTransform(translate_2d_transform); gfx::Size bounds(100.f, 100.f); grand_child()->SetPosition(gfx::PointF(150.f, 50.f)); grand_child()->SetBounds(bounds); great_grand_child()->SetTransformOrigin( gfx::Point3F(bounds.width() * 0.25f, bounds.height() * 0.25f, 0)); great_grand_child()->SetPosition(gfx::PointF(25.f, 25.f)); great_grand_child()->SetBounds( gfx::Size(bounds.width() * 0.5f, bounds.height() * 0.5f)); great_grand_child()->SetDrawsContent(true); host_impl().active_tree()->BuildPropertyTreesForTesting(); gfx::BoxF box; bool success = LayerUtils::GetAnimationBounds(*great_grand_child(), &box); EXPECT_TRUE(success); // Same as RotateXWithPerspectiveUnderAncestorsWithPositionOr2DTransform test, // except for the perspective calculations. gfx::BoxF expected(295.f, 180.f, -25.f, 50.f, 50.f, 50.f); EXPECT_BOXF_EQ(expected, box); } } // namespace } // namespace cc