blob: f40c28eb6cb7792919661eaf07f43a4d26a1bd03 [file] [log] [blame]
// Copyright 2016 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 "platform/graphics/paint/GeometryMapper.h"
#include "platform/geometry/GeometryTestHelpers.h"
#include "platform/geometry/LayoutRect.h"
#include "platform/graphics/paint/ClipPaintPropertyNode.h"
#include "platform/graphics/paint/EffectPaintPropertyNode.h"
#include "platform/graphics/paint/ScrollPaintPropertyNode.h"
#include "platform/graphics/paint/TransformPaintPropertyNode.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class GeometryMapperTest : public ::testing::Test {
public:
RefPtr<TransformPaintPropertyNode> rootTransformNode;
RefPtr<ClipPaintPropertyNode> rootClipNode;
RefPtr<EffectPaintPropertyNode> rootEffectNode;
RefPtr<ScrollPaintPropertyNode> rootScrollNode;
std::unique_ptr<GeometryMapper> geometryMapper;
PropertyTreeState rootPropertyTreeState() {
PropertyTreeState state(rootTransformNode.get(), rootClipNode.get(),
rootEffectNode.get(), rootScrollNode.get());
return state;
}
PrecomputedDataForAncestor& getPrecomputedDataForAncestor(
const PropertyTreeState& propertyTreeState) {
return geometryMapper->getPrecomputedDataForAncestor(propertyTreeState);
}
const TransformPaintPropertyNode* leastCommonAncestor(
const TransformPaintPropertyNode* a,
const TransformPaintPropertyNode* b) {
return GeometryMapper::leastCommonAncestor(a, b);
}
private:
void SetUp() override {
rootTransformNode = TransformPaintPropertyNode::create(
nullptr, TransformationMatrix(), FloatPoint3D());
rootClipNode = ClipPaintPropertyNode::create(
nullptr, rootTransformNode,
FloatRoundedRect(LayoutRect::infiniteIntRect()));
rootEffectNode = EffectPaintPropertyNode::create(
nullptr, rootTransformNode, rootClipNode, CompositorFilterOperations(),
1.0);
rootScrollNode = ScrollPaintPropertyNode::create(
nullptr, rootTransformNode, IntSize(), IntSize(), false, false);
geometryMapper = makeUnique<GeometryMapper>();
}
void TearDown() override { geometryMapper.reset(); }
};
const static float kTestEpsilon = 1e-6;
#define EXPECT_RECT_EQ(expected, actual) \
do { \
const FloatRect& actualRect = actual; \
EXPECT_TRUE(GeometryTest::ApproximatelyEqual(expected.x(), actualRect.x(), \
kTestEpsilon)) \
<< "actual: " << actualRect.x() << ", expected: " << expected.x(); \
EXPECT_TRUE(GeometryTest::ApproximatelyEqual(expected.y(), actualRect.y(), \
kTestEpsilon)) \
<< "actual: " << actualRect.y() << ", expected: " << expected.y(); \
EXPECT_TRUE(GeometryTest::ApproximatelyEqual( \
expected.width(), actualRect.width(), kTestEpsilon)) \
<< "actual: " << actualRect.width() \
<< ", expected: " << expected.width(); \
EXPECT_TRUE(GeometryTest::ApproximatelyEqual( \
expected.height(), actualRect.height(), kTestEpsilon)) \
<< "actual: " << actualRect.height() \
<< ", expected: " << expected.height(); \
} while (false)
#define CHECK_MAPPINGS(inputRect, expectedVisualRect, expectedTransformedRect, \
expectedTransformToAncestor, \
expectedClipInAncestorSpace, localPropertyTreeState, \
ancestorPropertyTreeState) \
do { \
bool success = false; \
EXPECT_RECT_EQ(expectedVisualRect, \
geometryMapper->localToVisualRectInAncestorSpace( \
inputRect, localPropertyTreeState, \
ancestorPropertyTreeState, success)); \
EXPECT_TRUE(success); \
EXPECT_RECT_EQ(expectedVisualRect, \
geometryMapper->mapToVisualRectInDestinationSpace( \
inputRect, localPropertyTreeState, \
ancestorPropertyTreeState, success)); \
EXPECT_TRUE(success); \
EXPECT_RECT_EQ(expectedTransformedRect, \
geometryMapper->localToAncestorRect( \
inputRect, localPropertyTreeState, \
ancestorPropertyTreeState, success)); \
EXPECT_RECT_EQ(expectedTransformedRect, \
geometryMapper->mapRectToDestinationSpace( \
inputRect, localPropertyTreeState, \
ancestorPropertyTreeState, success)); \
EXPECT_TRUE(success); \
EXPECT_EQ( \
expectedTransformToAncestor, \
getPrecomputedDataForAncestor(ancestorPropertyTreeState) \
.toAncestorTransforms.get(localPropertyTreeState.transform())); \
EXPECT_EQ(expectedClipInAncestorSpace, \
getPrecomputedDataForAncestor(ancestorPropertyTreeState) \
.toAncestorClipRects.get(localPropertyTreeState.clip())); \
} while (false)
TEST_F(GeometryMapperTest, Root) {
FloatRect input(0, 0, 100, 100);
CHECK_MAPPINGS(input, input, input, rootTransformNode->matrix(),
rootClipNode->clipRect().rect(), rootPropertyTreeState(),
rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, IdentityTransform) {
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
TransformationMatrix(),
FloatPoint3D());
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform.get());
FloatRect input(0, 0, 100, 100);
CHECK_MAPPINGS(input, input, input, transform->matrix(),
rootClipNode->clipRect().rect(), localState,
rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, TranslationTransform) {
TransformationMatrix transformMatrix;
transformMatrix.translate(20, 10);
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
transformMatrix, FloatPoint3D());
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform.get());
FloatRect input(0, 0, 100, 100);
FloatRect output = transformMatrix.mapRect(input);
CHECK_MAPPINGS(input, output, output, transform->matrix(),
rootClipNode->clipRect().rect(), localState,
rootPropertyTreeState());
bool success = false;
EXPECT_RECT_EQ(input,
geometryMapper->ancestorToLocalRect(
output, localState, rootPropertyTreeState(), success));
EXPECT_TRUE(success);
}
TEST_F(GeometryMapperTest, RotationAndScaleTransform) {
TransformationMatrix transformMatrix;
transformMatrix.rotate(45);
transformMatrix.scale(2);
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
transformMatrix,
FloatPoint3D(0, 0, 0));
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform.get());
FloatRect input(0, 0, 100, 100);
FloatRect output = transformMatrix.mapRect(input);
CHECK_MAPPINGS(input, output, output, transformMatrix,
rootClipNode->clipRect().rect(), localState,
rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, RotationAndScaleTransformWithTransformOrigin) {
TransformationMatrix transformMatrix;
transformMatrix.rotate(45);
transformMatrix.scale(2);
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
transformMatrix,
FloatPoint3D(50, 50, 0));
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform.get());
FloatRect input(0, 0, 100, 100);
transformMatrix.applyTransformOrigin(50, 50, 0);
FloatRect output = transformMatrix.mapRect(input);
CHECK_MAPPINGS(input, output, output, transformMatrix,
rootClipNode->clipRect().rect(), localState,
rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, NestedTransforms) {
TransformationMatrix rotateTransform;
rotateTransform.rotate(45);
RefPtr<TransformPaintPropertyNode> transform1 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform, FloatPoint3D());
TransformationMatrix scaleTransform;
scaleTransform.scale(2);
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(transform1, scaleTransform,
FloatPoint3D());
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform2.get());
FloatRect input(0, 0, 100, 100);
TransformationMatrix final = rotateTransform * scaleTransform;
FloatRect output = final.mapRect(input);
CHECK_MAPPINGS(input, output, output, final, rootClipNode->clipRect().rect(),
localState, rootPropertyTreeState());
// Check the cached matrix for the intermediate transform.
EXPECT_EQ(rotateTransform,
getPrecomputedDataForAncestor(rootPropertyTreeState())
.toAncestorTransforms.get(transform1.get()));
}
TEST_F(GeometryMapperTest, NestedTransformsScaleAndTranslation) {
TransformationMatrix scaleTransform;
scaleTransform.scale(2);
RefPtr<TransformPaintPropertyNode> transform1 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
scaleTransform, FloatPoint3D());
TransformationMatrix translateTransform;
translateTransform.translate(100, 0);
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(transform1, translateTransform,
FloatPoint3D());
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform2.get());
FloatRect input(0, 0, 100, 100);
// Note: unlike NestedTransforms, the order of these transforms matters. This
// tests correct order of matrix multiplication.
TransformationMatrix final = scaleTransform * translateTransform;
FloatRect output = final.mapRect(input);
CHECK_MAPPINGS(input, output, output, final, rootClipNode->clipRect().rect(),
localState, rootPropertyTreeState());
// Check the cached matrix for the intermediate transform.
EXPECT_EQ(scaleTransform,
getPrecomputedDataForAncestor(rootPropertyTreeState())
.toAncestorTransforms.get(transform1.get()));
}
TEST_F(GeometryMapperTest, NestedTransformsIntermediateDestination) {
TransformationMatrix rotateTransform;
rotateTransform.rotate(45);
RefPtr<TransformPaintPropertyNode> transform1 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform, FloatPoint3D());
TransformationMatrix scaleTransform;
scaleTransform.scale(2);
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(transform1, scaleTransform,
FloatPoint3D());
PropertyTreeState localState = rootPropertyTreeState();
localState.setTransform(transform2.get());
PropertyTreeState intermediateState = rootPropertyTreeState();
intermediateState.setTransform(transform1.get());
FloatRect input(0, 0, 100, 100);
FloatRect output = scaleTransform.mapRect(input);
CHECK_MAPPINGS(input, output, output, scaleTransform,
rootClipNode->clipRect().rect(), localState,
intermediateState);
}
TEST_F(GeometryMapperTest, SimpleClip) {
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
rootClipNode, rootTransformNode, FloatRoundedRect(10, 10, 50, 50));
PropertyTreeState localState = rootPropertyTreeState();
localState.setClip(clip.get());
FloatRect input(0, 0, 100, 100);
FloatRect output(10, 10, 50, 50);
CHECK_MAPPINGS(
input, // Input
output, // Visual rect
input, // Transformed rect (not clipped).
rootTransformNode->matrix(), // Transform matrix to ancestor space
clip->clipRect().rect(), // Clip rect in ancestor space
localState, rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, ClipBeforeTransform) {
TransformationMatrix rotateTransform;
rotateTransform.rotate(45);
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform, FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
rootClipNode, transform.get(), FloatRoundedRect(10, 10, 50, 50));
PropertyTreeState localState = rootPropertyTreeState();
localState.setClip(clip.get());
localState.setTransform(transform.get());
FloatRect input(0, 0, 100, 100);
FloatRect output(input);
output.intersect(clip->clipRect().rect());
output = rotateTransform.mapRect(output);
CHECK_MAPPINGS(
input, // Input
output, // Visual rect
rotateTransform.mapRect(input), // Transformed rect (not clipped).
rotateTransform, // Transform matrix to ancestor space
rotateTransform.mapRect(
clip->clipRect().rect()), // Clip rect in ancestor space
localState,
rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, ClipAfterTransform) {
TransformationMatrix rotateTransform;
rotateTransform.rotate(45);
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform, FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip =
ClipPaintPropertyNode::create(rootClipNode, rootTransformNode.get(),
FloatRoundedRect(10, 10, 200, 200));
PropertyTreeState localState = rootPropertyTreeState();
localState.setClip(clip.get());
localState.setTransform(transform.get());
FloatRect input(0, 0, 100, 100);
FloatRect output(input);
output = rotateTransform.mapRect(output);
output.intersect(clip->clipRect().rect());
CHECK_MAPPINGS(
input, // Input
output, // Visual rect
rotateTransform.mapRect(input), // Transformed rect (not clipped)
rotateTransform, // Transform matrix to ancestor space
clip->clipRect().rect(), // Clip rect in ancestor space
localState, rootPropertyTreeState());
}
TEST_F(GeometryMapperTest, TwoClipsWithTransformBetween) {
RefPtr<ClipPaintPropertyNode> clip1 =
ClipPaintPropertyNode::create(rootClipNode, rootTransformNode.get(),
FloatRoundedRect(10, 10, 200, 200));
TransformationMatrix rotateTransform;
rotateTransform.rotate(45);
RefPtr<TransformPaintPropertyNode> transform =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform, FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip2 = ClipPaintPropertyNode::create(
clip1, transform.get(), FloatRoundedRect(10, 10, 200, 200));
FloatRect input(0, 0, 100, 100);
{
PropertyTreeState localState = rootPropertyTreeState();
localState.setClip(clip1.get());
localState.setTransform(transform.get());
FloatRect output(input);
output = rotateTransform.mapRect(output);
output.intersect(clip1->clipRect().rect());
CHECK_MAPPINGS(
input, // Input
output, // Visual rect
rotateTransform.mapRect(input), // Transformed rect (not clipped)
rotateTransform, // Transform matrix to ancestor space
clip1->clipRect().rect(), // Clip rect in ancestor space
localState, rootPropertyTreeState());
}
{
PropertyTreeState localState = rootPropertyTreeState();
localState.setClip(clip2.get());
localState.setTransform(transform.get());
FloatRect mappedClip = rotateTransform.mapRect(clip2->clipRect().rect());
mappedClip.intersect(clip1->clipRect().rect());
// All clips are performed in the space of the ancestor. In cases such as
// this, this means the clip is a bit lossy.
FloatRect output(input);
// Map to transformed rect in ancestor space.
output = rotateTransform.mapRect(output);
// Intersect with all clips between local and ancestor, independently mapped
// to ancestor space.
output.intersect(mappedClip);
CHECK_MAPPINGS(
input, // Input
output, // Visual rect
rotateTransform.mapRect(input), // Transformed rect (not clipped)
rotateTransform, // Transform matrix to ancestor space
mappedClip, // Clip rect in ancestor space
localState, rootPropertyTreeState());
}
}
TEST_F(GeometryMapperTest, SiblingTransforms) {
// These transforms are siblings. Thus mapping from one to the other requires
// going through the root.
TransformationMatrix rotateTransform1;
rotateTransform1.rotate(45);
RefPtr<TransformPaintPropertyNode> transform1 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform1, FloatPoint3D());
TransformationMatrix rotateTransform2;
rotateTransform2.rotate(-45);
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform2, FloatPoint3D());
PropertyTreeState transform1State = rootPropertyTreeState();
transform1State.setTransform(transform1.get());
PropertyTreeState transform2State = rootPropertyTreeState();
transform2State.setTransform(transform2.get());
bool success;
FloatRect input(0, 0, 100, 100);
FloatRect result = geometryMapper->localToVisualRectInAncestorSpace(
input, transform1State, transform2State, success);
// Fails, because the transform2state is not an ancestor of transform1State.
EXPECT_FALSE(success);
EXPECT_RECT_EQ(input, result);
result = geometryMapper->localToAncestorRect(input, transform1State,
transform2State, success);
// Fails, because the transform2state is not an ancestor of transform1State.
EXPECT_FALSE(success);
EXPECT_RECT_EQ(input, result);
result = geometryMapper->localToVisualRectInAncestorSpace(
input, transform2State, transform1State, success);
// Fails, because the transform1state is not an ancestor of transform2State.
EXPECT_FALSE(success);
EXPECT_RECT_EQ(input, result);
result = geometryMapper->localToAncestorRect(input, transform2State,
transform1State, success);
// Fails, because the transform1state is not an ancestor of transform2State.
EXPECT_FALSE(success);
EXPECT_RECT_EQ(input, result);
FloatRect expected =
rotateTransform2.inverse().mapRect(rotateTransform1.mapRect(input));
result = geometryMapper->mapToVisualRectInDestinationSpace(
input, transform1State, transform2State, success);
EXPECT_TRUE(success);
EXPECT_RECT_EQ(expected, result);
result = geometryMapper->mapRectToDestinationSpace(input, transform1State,
transform2State, success);
EXPECT_TRUE(success);
EXPECT_RECT_EQ(expected, result);
}
TEST_F(GeometryMapperTest, SiblingTransformsWithClip) {
// These transforms are siblings. Thus mapping from one to the other requires
// going through the root.
TransformationMatrix rotateTransform1;
rotateTransform1.rotate(45);
RefPtr<TransformPaintPropertyNode> transform1 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform1, FloatPoint3D());
TransformationMatrix rotateTransform2;
rotateTransform2.rotate(-45);
RefPtr<TransformPaintPropertyNode> transform2 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
rotateTransform2, FloatPoint3D());
RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create(
rootPropertyTreeState().clip(), transform2.get(),
FloatRoundedRect(10, 10, 70, 70));
PropertyTreeState transform1State = rootPropertyTreeState();
transform1State.setTransform(transform1.get());
PropertyTreeState transform2AndClipState = rootPropertyTreeState();
transform2AndClipState.setTransform(transform2.get());
transform2AndClipState.setClip(clip.get());
bool success;
FloatRect input(0, 0, 100, 100);
// Test map from transform1State to transform2AndClipState.
FloatRect expected =
rotateTransform2.inverse().mapRect(rotateTransform1.mapRect(input));
// mapToVisualRectInDestinationSpace ignores clip from the common ancestor to
// destination.
FloatRect result = geometryMapper->mapToVisualRectInDestinationSpace(
input, transform1State, transform2AndClipState, success);
// Fails, because the clip of the destination state is not an ancestor of the
// clip of the source state.
EXPECT_FALSE(success);
// mapRectToDestinationSpace ignores clip.
result = geometryMapper->mapRectToDestinationSpace(
input, transform1State, transform2AndClipState, success);
EXPECT_TRUE(success);
EXPECT_RECT_EQ(expected, result);
// Test map from transform2AndClipState to transform1State.
FloatRect expectedUnclipped =
rotateTransform1.inverse().mapRect(rotateTransform2.mapRect(input));
FloatRect expectedClipped = rotateTransform1.inverse().mapRect(
rotateTransform2.mapRect(FloatRect(10, 10, 70, 70)));
// mapToVisualRectInDestinationSpace ignores clip from the common ancestor to
// destination.
result = geometryMapper->mapToVisualRectInDestinationSpace(
input, transform2AndClipState, transform1State, success);
EXPECT_TRUE(success);
EXPECT_RECT_EQ(expectedClipped, result);
// mapRectToDestinationSpace ignores clip.
result = geometryMapper->mapRectToDestinationSpace(
input, transform2AndClipState, transform1State, success);
EXPECT_TRUE(success);
EXPECT_RECT_EQ(expectedUnclipped, result);
}
TEST_F(GeometryMapperTest, LeastCommonAncestor) {
TransformationMatrix matrix;
RefPtr<TransformPaintPropertyNode> child1 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
matrix, FloatPoint3D());
RefPtr<TransformPaintPropertyNode> child2 =
TransformPaintPropertyNode::create(rootPropertyTreeState().transform(),
matrix, FloatPoint3D());
RefPtr<TransformPaintPropertyNode> childOfChild1 =
TransformPaintPropertyNode::create(child1, matrix, FloatPoint3D());
RefPtr<TransformPaintPropertyNode> childOfChild2 =
TransformPaintPropertyNode::create(child2, matrix, FloatPoint3D());
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(childOfChild1.get(), childOfChild2.get()));
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(childOfChild1.get(), child2.get()));
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(childOfChild1.get(),
rootPropertyTreeState().transform()));
EXPECT_EQ(child1, leastCommonAncestor(childOfChild1.get(), child1.get()));
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(childOfChild2.get(), childOfChild1.get()));
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(childOfChild2.get(), child1.get()));
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(childOfChild2.get(),
rootPropertyTreeState().transform()));
EXPECT_EQ(child2, leastCommonAncestor(childOfChild2.get(), child2.get()));
EXPECT_EQ(rootPropertyTreeState().transform(),
leastCommonAncestor(child1.get(), child2.get()));
}
} // namespace blink