blob: a7c419541ecf42a3c5a55f76ea2a7eb4245e7d40 [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/BoxReflection.h"
#include "platform/graphics/filters/PaintFilterBuilder.h"
#include "platform/graphics/paint/ClipPaintPropertyNode.h"
#include "platform/graphics/paint/EffectPaintPropertyNode.h"
#include "platform/graphics/paint/TransformPaintPropertyNode.h"
#include "platform/testing/PaintPropertyTestHelpers.h"
#include "platform/testing/PaintTestConfigurations.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class GeometryMapperTest : public ::testing::Test,
public PaintTestConfigurations {
public:
const FloatClipRect* GetCachedClip(
const ClipPaintPropertyNode* descendant_clip,
const PropertyTreeState& ancestor_property_tree_state) {
GeometryMapperClipCache::ClipAndTransform clip_and_transform(
ancestor_property_tree_state.Clip(),
ancestor_property_tree_state.Transform(),
kIgnorePlatformOverlayScrollbarSize);
return descendant_clip->GetClipCache().GetCachedClip(clip_and_transform);
}
void LocalToAncestorVisualRectInternal(
const PropertyTreeState& local_state,
const PropertyTreeState& ancestor_state,
FloatClipRect& mapping_rect,
bool& success) {
GeometryMapper::LocalToAncestorVisualRectInternal(
local_state, ancestor_state, mapping_rect,
kIgnorePlatformOverlayScrollbarSize, success);
}
// Variables required by CHECK_MAPPINGS(). The tests should set these
// variables with proper values before calling CHECK_MAPPINGS().
PropertyTreeState local_state = PropertyTreeState::Root();
PropertyTreeState ancestor_state = PropertyTreeState::Root();
FloatRect input_rect;
FloatClipRect expected_visual_rect;
TransformationMatrix expected_transform;
FloatClipRect expected_clip;
FloatRect expected_transformed_rect;
};
INSTANTIATE_TEST_CASE_P(All,
GeometryMapperTest,
::testing::ValuesIn(kSlimmingPaintVersions));
#define EXPECT_FLOAT_RECT_NEAR(expected, actual) \
do { \
EXPECT_PRED_FORMAT2(GeometryTest::AssertAlmostEqual, (actual).X(), \
(expected).X()); \
EXPECT_PRED_FORMAT2(GeometryTest::AssertAlmostEqual, (actual).Y(), \
(expected).Y()); \
EXPECT_PRED_FORMAT2(GeometryTest::AssertAlmostEqual, (actual).Width(), \
(expected).Width()); \
EXPECT_PRED_FORMAT2(GeometryTest::AssertAlmostEqual, (actual).Height(), \
(expected).Height()); \
} while (false)
#define EXPECT_CLIP_RECT_EQ(expected, actual) \
do { \
SCOPED_TRACE("EXPECT_CLIP_RECT_EQ: " #expected " vs " #actual); \
EXPECT_EQ((expected).IsInfinite(), (actual).IsInfinite()); \
EXPECT_EQ((expected).HasRadius(), (actual).HasRadius()); \
EXPECT_EQ((expected).IsTight(), (actual).IsTight()); \
if (!(expected).IsInfinite()) \
EXPECT_FLOAT_RECT_NEAR((expected).Rect(), (actual).Rect()); \
} while (false)
#define CHECK_LOCAL_TO_ANCESTOR_VISUAL_RECT() \
do { \
SCOPED_TRACE("Check LocalToAncestorVisualRect"); \
FloatClipRect actual_visual_rect(input_rect); \
GeometryMapper::LocalToAncestorVisualRect(local_state, ancestor_state, \
actual_visual_rect); \
EXPECT_CLIP_RECT_EQ(expected_visual_rect, actual_visual_rect); \
} while (false)
#define CHECK_LOCAL_TO_ANCESTOR_CLIP_RECT() \
do { \
SCOPED_TRACE("Check LocalToAncestorClipRect"); \
FloatClipRect actual_clip_rect; \
actual_clip_rect = \
GeometryMapper::LocalToAncestorClipRect(local_state, ancestor_state); \
EXPECT_CLIP_RECT_EQ(expected_clip, actual_clip_rect); \
} while (false)
#define CHECK_SOURCE_TO_DESTINATION_RECT() \
do { \
SCOPED_TRACE("Check SourceToDestinationRect"); \
auto actual_transformed_rect = input_rect; \
GeometryMapper::SourceToDestinationRect(local_state.Transform(), \
ancestor_state.Transform(), \
actual_transformed_rect); \
EXPECT_FLOAT_RECT_NEAR(expected_transformed_rect, \
actual_transformed_rect); \
} while (false)
#define CHECK_SOURCE_TO_DESTINATION_PROJECTION() \
do { \
if (ancestor_state.Transform() == local_state.Transform()) \
break; \
SCOPED_TRACE("Check SourceToDestinationProjection"); \
const auto& actual_transform_to_ancestor = \
GeometryMapper::SourceToDestinationProjection( \
local_state.Transform(), ancestor_state.Transform()); \
EXPECT_EQ(expected_transform, actual_transform_to_ancestor); \
} while (false)
#define CHECK_CACHED_CLIP() \
do { \
if (ancestor_state.Clip() == local_state.Clip() || \
ancestor_state.Effect() != local_state.Effect()) \
break; \
SCOPED_TRACE("Check cached clip"); \
const auto* cached_clip = \
GetCachedClip(local_state.Clip(), ancestor_state); \
DCHECK(cached_clip); \
EXPECT_CLIP_RECT_EQ(expected_clip, *cached_clip); \
} while (false)
// See the data fields of GeometryMapperTest for variables that will be used in
// this macro.
#define CHECK_MAPPINGS() \
do { \
CHECK_LOCAL_TO_ANCESTOR_VISUAL_RECT(); \
CHECK_LOCAL_TO_ANCESTOR_CLIP_RECT(); \
CHECK_SOURCE_TO_DESTINATION_RECT(); \
CHECK_SOURCE_TO_DESTINATION_PROJECTION(); \
{ \
SCOPED_TRACE("Repeated check to test caching"); \
CHECK_LOCAL_TO_ANCESTOR_VISUAL_RECT(); \
CHECK_LOCAL_TO_ANCESTOR_CLIP_RECT(); \
CHECK_SOURCE_TO_DESTINATION_RECT(); \
CHECK_SOURCE_TO_DESTINATION_PROJECTION(); \
} \
CHECK_CACHED_CLIP(); \
} while (false)
TEST_P(GeometryMapperTest, Root) {
input_rect = FloatRect(0, 0, 100, 100);
expected_visual_rect = FloatClipRect(input_rect);
expected_transformed_rect = input_rect;
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, IdentityTransform) {
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), TransformationMatrix(),
FloatPoint3D());
local_state.SetTransform(transform.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = input_rect;
expected_visual_rect = FloatClipRect(input_rect);
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, TranslationTransform) {
expected_transform = TransformationMatrix().Translate(20, 10);
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), expected_transform, FloatPoint3D());
local_state.SetTransform(transform.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
CHECK_MAPPINGS();
FloatRect rect = expected_transformed_rect;
GeometryMapper::SourceToDestinationRect(TransformPaintPropertyNode::Root(),
local_state.Transform(), rect);
EXPECT_FLOAT_RECT_NEAR(input_rect, rect);
}
TEST_P(GeometryMapperTest, RotationAndScaleTransform) {
expected_transform = TransformationMatrix().Rotate(45).Scale(2);
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), expected_transform,
FloatPoint3D(0, 0, 0));
local_state.SetTransform(transform.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
expected_visual_rect.ClearIsTight();
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, RotationAndScaleTransformWithTransformOrigin) {
expected_transform = TransformationMatrix().Rotate(45).Scale(2);
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), expected_transform,
FloatPoint3D(50, 50, 0));
local_state.SetTransform(transform.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transform.ApplyTransformOrigin(50, 50, 0);
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
expected_visual_rect.ClearIsTight();
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, NestedTransforms) {
auto rotate_transform = TransformationMatrix().Rotate(45);
auto transform1 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform, FloatPoint3D());
auto scale_transform = TransformationMatrix().Scale(2);
auto transform2 = TransformPaintPropertyNode::Create(
transform1, scale_transform, FloatPoint3D());
local_state.SetTransform(transform2.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transform = rotate_transform * scale_transform;
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
expected_visual_rect.ClearIsTight();
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, NestedTransformsFlattening) {
auto rotate_transform = TransformationMatrix().Rotate3d(45, 0, 0);
auto transform1 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform, FloatPoint3D());
auto inverse_rotate_transform = TransformationMatrix().Rotate3d(-45, 0, 0);
auto transform2 = TransformPaintPropertyNode::Create(
transform1, inverse_rotate_transform, FloatPoint3D(),
true); // Flattens
local_state.SetTransform(transform2.get());
input_rect = FloatRect(0, 0, 100, 100);
rotate_transform.FlattenTo2d();
expected_transform = rotate_transform * inverse_rotate_transform;
expected_transform.FlattenTo2d();
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
expected_visual_rect.ClearIsTight();
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, NestedTransformsScaleAndTranslation) {
auto scale_transform = TransformationMatrix().Scale(2);
auto transform1 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), scale_transform, FloatPoint3D());
auto translate_transform = TransformationMatrix().Translate(100, 0);
auto transform2 = TransformPaintPropertyNode::Create(
transform1, translate_transform, FloatPoint3D());
local_state.SetTransform(transform2.get());
input_rect = FloatRect(0, 0, 100, 100);
// Note: unlike NestedTransforms, the order of these transforms matters. This
// tests correct order of matrix multiplication.
expected_transform = scale_transform * translate_transform;
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
expected_visual_rect.ClearIsTight();
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, NestedTransformsIntermediateDestination) {
auto rotate_transform = TransformationMatrix().Rotate(45);
auto transform1 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform, FloatPoint3D());
auto scale_transform = TransformationMatrix().Translate(10, 20);
auto transform2 = TransformPaintPropertyNode::Create(
transform1, scale_transform, FloatPoint3D());
local_state.SetTransform(transform2.get());
ancestor_state.SetTransform(transform1.get());
expected_transform = scale_transform;
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(expected_transformed_rect);
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, SimpleClip) {
auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
TransformPaintPropertyNode::Root(),
FloatRoundedRect(10, 10, 50, 50));
local_state.SetClip(clip.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = input_rect; // not clipped.
expected_clip = FloatClipRect(clip->ClipRect());
expected_visual_rect = expected_clip;
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, SimpleClipOverlayScrollbars) {
FloatRoundedRect rect_without_overlay_scrollbars(10, 10, 45, 43);
auto clip = ClipPaintPropertyNode::Create(
ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
FloatRoundedRect(10, 10, 50, 50), &rect_without_overlay_scrollbars);
local_state.SetClip(clip.get());
input_rect = FloatRect(0, 0, 100, 100);
FloatClipRect actual_visual_rect(input_rect);
GeometryMapper::LocalToAncestorVisualRect(
local_state, ancestor_state, actual_visual_rect,
kExcludeOverlayScrollbarSizeForHitTesting);
EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(10, 10, 45, 43)),
actual_visual_rect);
// Check that not passing kExcludeOverlayScrollbarSizeForHitTesting gives
// a different result.
actual_visual_rect = FloatClipRect(input_rect);
GeometryMapper::LocalToAncestorVisualRect(
local_state, ancestor_state, actual_visual_rect,
kIgnorePlatformOverlayScrollbarSize);
EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(10, 10, 50, 50)),
actual_visual_rect);
FloatClipRect actual_clip_rect = GeometryMapper::LocalToAncestorClipRect(
local_state, ancestor_state, kExcludeOverlayScrollbarSizeForHitTesting);
EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(10, 10, 45, 43)),
actual_clip_rect);
// Check that not passing kExcludeOverlayScrollbarSizeForHitTesting gives
// a different result.
actual_clip_rect = GeometryMapper::LocalToAncestorClipRect(
local_state, ancestor_state, kIgnorePlatformOverlayScrollbarSize);
EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(10, 10, 50, 50)),
actual_clip_rect);
}
TEST_P(GeometryMapperTest, RoundedClip) {
FloatRoundedRect rect(FloatRect(10, 10, 50, 50),
FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(),
FloatSize(), FloatSize()));
auto clip = ClipPaintPropertyNode::Create(
ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(), rect);
local_state.SetClip(clip.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = input_rect;
expected_clip = FloatClipRect(clip->ClipRect());
EXPECT_TRUE(expected_clip.HasRadius());
expected_visual_rect = expected_clip;
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, TwoClips) {
FloatRoundedRect clip_rect1(
FloatRect(10, 10, 30, 40),
FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(), FloatSize(),
FloatSize()));
auto clip1 = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
TransformPaintPropertyNode::Root(),
clip_rect1);
auto clip2 =
ClipPaintPropertyNode::Create(clip1, TransformPaintPropertyNode::Root(),
FloatRoundedRect(10, 10, 50, 50));
local_state.SetClip(clip2.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = input_rect;
expected_clip = FloatClipRect(clip1->ClipRect());
EXPECT_TRUE(expected_clip.HasRadius());
expected_visual_rect = expected_clip;
CHECK_MAPPINGS();
ancestor_state.SetClip(clip1.get());
expected_clip = FloatClipRect(clip2->ClipRect());
expected_visual_rect = expected_clip;
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, TwoClipsTransformAbove) {
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), TransformationMatrix(),
FloatPoint3D());
FloatRoundedRect clip_rect1(
FloatRect(10, 10, 50, 50),
FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(), FloatSize(),
FloatSize()));
auto clip1 = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
transform.get(), clip_rect1);
auto clip2 = ClipPaintPropertyNode::Create(clip1, transform.get(),
FloatRoundedRect(10, 10, 30, 40));
local_state.SetClip(clip2.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = input_rect;
expected_clip = FloatClipRect(clip2->ClipRect());
expected_clip.SetHasRadius();
expected_visual_rect = expected_clip;
CHECK_MAPPINGS();
expected_clip = FloatClipRect(clip1->ClipRect());
EXPECT_TRUE(expected_clip.HasRadius());
local_state.SetClip(clip1.get());
expected_visual_rect = expected_clip;
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, ClipBeforeTransform) {
expected_transform = TransformationMatrix().Rotate(45);
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), expected_transform, FloatPoint3D());
auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
transform.get(),
FloatRoundedRect(10, 10, 50, 50));
local_state.SetClip(clip.get());
local_state.SetTransform(transform.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_visual_rect = FloatClipRect(input_rect);
expected_visual_rect.Intersect(FloatClipRect(clip->ClipRect()));
expected_visual_rect.Map(expected_transform);
EXPECT_FALSE(expected_visual_rect.IsTight());
expected_clip = FloatClipRect(clip->ClipRect());
expected_clip.Map(expected_transform);
EXPECT_FALSE(expected_clip.IsTight());
expected_transformed_rect = expected_transform.MapRect(input_rect);
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, ClipAfterTransform) {
expected_transform = TransformationMatrix().Rotate(45);
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), expected_transform, FloatPoint3D());
auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
TransformPaintPropertyNode::Root(),
FloatRoundedRect(10, 10, 200, 200));
local_state.SetClip(clip.get());
local_state.SetTransform(transform.get());
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(input_rect);
expected_visual_rect.Map(expected_transform);
expected_visual_rect.Intersect(FloatClipRect(clip->ClipRect()));
EXPECT_FALSE(expected_visual_rect.IsTight());
expected_clip = FloatClipRect(clip->ClipRect());
EXPECT_TRUE(expected_clip.IsTight());
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, TwoClipsWithTransformBetween) {
auto clip1 = ClipPaintPropertyNode::Create(
ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
FloatRoundedRect(10, 10, 200, 200));
expected_transform = TransformationMatrix().Rotate(45);
auto transform = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), expected_transform, FloatPoint3D());
auto clip2 = ClipPaintPropertyNode::Create(
clip1, transform.get(), FloatRoundedRect(10, 10, 200, 200));
input_rect = FloatRect(0, 0, 100, 100);
expected_transformed_rect = expected_transform.MapRect(input_rect);
{
local_state.SetClip(clip1.get());
local_state.SetTransform(transform.get());
expected_visual_rect = FloatClipRect(input_rect);
expected_visual_rect.Map(expected_transform);
expected_visual_rect.Intersect(FloatClipRect(clip1->ClipRect()));
EXPECT_FALSE(expected_visual_rect.IsTight());
expected_clip = FloatClipRect(clip1->ClipRect());
EXPECT_TRUE(expected_clip.IsTight());
CHECK_MAPPINGS();
}
{
local_state.SetClip(clip2.get());
local_state.SetTransform(transform.get());
expected_clip = FloatClipRect(clip2->ClipRect());
expected_clip.Map(expected_transform);
expected_clip.Intersect(FloatClipRect(clip1->ClipRect()));
EXPECT_FALSE(expected_clip.IsTight());
// All clips are performed in the space of the ancestor. In cases such as
// this, this means the clip is not tight.
expected_visual_rect = FloatClipRect(input_rect);
expected_visual_rect.Map(expected_transform);
// Intersect with all clips between local and ancestor, independently mapped
// to ancestor space.
expected_visual_rect.Intersect(expected_clip);
EXPECT_FALSE(expected_visual_rect.IsTight());
CHECK_MAPPINGS();
}
}
TEST_P(GeometryMapperTest, SiblingTransforms) {
// These transforms are siblings. Thus mapping from one to the other requires
// going through the root.
auto rotate_transform1 = TransformationMatrix().Rotate(45);
auto transform1 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform1, FloatPoint3D());
auto rotate_transform2 = TransformationMatrix().Rotate(-45);
auto transform2 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform2, FloatPoint3D());
auto transform1_state = PropertyTreeState::Root();
transform1_state.SetTransform(transform1.get());
auto transform2_state = PropertyTreeState::Root();
transform2_state.SetTransform(transform2.get());
input_rect = FloatRect(0, 0, 100, 100);
FloatClipRect result_clip(input_rect);
GeometryMapper::LocalToAncestorVisualRect(transform1_state, transform2_state,
result_clip);
FloatClipRect expected_clip(FloatRect(-100, 0, 100, 100));
// This is because the combined Rotate(45) and Rotate(-45) is not exactly a
// translation-only transform due to calculation errors.
expected_clip.ClearIsTight();
EXPECT_CLIP_RECT_EQ(expected_clip, result_clip);
FloatRect result = input_rect;
GeometryMapper::SourceToDestinationRect(transform1.get(), transform2.get(),
result);
EXPECT_FLOAT_RECT_NEAR(FloatRect(-100, 0, 100, 100), result);
result_clip = FloatClipRect(input_rect);
GeometryMapper::LocalToAncestorVisualRect(transform2_state, transform1_state,
result_clip);
expected_clip = FloatClipRect(FloatRect(0, -100, 100, 100));
expected_clip.ClearIsTight();
EXPECT_CLIP_RECT_EQ(expected_clip, result_clip);
result = input_rect;
GeometryMapper::SourceToDestinationRect(transform2.get(), transform1.get(),
result);
EXPECT_FLOAT_RECT_NEAR(FloatRect(0, -100, 100, 100), result);
}
TEST_P(GeometryMapperTest, SiblingTransformsWithClip) {
// These transforms are siblings. Thus mapping from one to the other requires
// going through the root.
auto rotate_transform1 = TransformationMatrix().Rotate(45);
auto transform1 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform1, FloatPoint3D());
auto rotate_transform2 = TransformationMatrix().Rotate(-45);
auto transform2 = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(), rotate_transform2, FloatPoint3D());
auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
transform2.get(),
FloatRoundedRect(10, 20, 30, 40));
auto transform1_state = PropertyTreeState::Root();
transform1_state.SetTransform(transform1.get());
auto transform2_and_clip_state = PropertyTreeState::Root();
transform2_and_clip_state.SetTransform(transform2.get());
transform2_and_clip_state.SetClip(clip.get());
bool success;
input_rect = FloatRect(0, 0, 100, 100);
FloatClipRect result(input_rect);
LocalToAncestorVisualRectInternal(transform1_state, transform2_and_clip_state,
result, success);
// Fails, because the clip of the destination state is not an ancestor of the
// clip of the source state. A known bug in SPv1* would make such query,
// in such case, no clips are applied.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_FALSE(success);
} else {
EXPECT_TRUE(success);
FloatClipRect expected(FloatRect(-100, 0, 100, 100));
expected.ClearIsTight();
EXPECT_CLIP_RECT_EQ(expected, result);
}
result = FloatClipRect(input_rect);
GeometryMapper::LocalToAncestorVisualRect(transform2_and_clip_state,
transform1_state, result);
FloatClipRect expected(FloatRect(20, -40, 40, 30));
// This is because the combined Rotate(45) and Rotate(-45) is not exactly a
// translation-only transform due to calculation errors.
expected.ClearIsTight();
EXPECT_CLIP_RECT_EQ(expected, result);
}
TEST_P(GeometryMapperTest, FilterWithClipsAndTransforms) {
auto transform_above_effect = TransformPaintPropertyNode::Create(
TransformPaintPropertyNode::Root(),
TransformationMatrix().Translate(40, 50), FloatPoint3D());
auto transform_below_effect = TransformPaintPropertyNode::Create(
transform_above_effect, TransformationMatrix().Translate(20, 30),
FloatPoint3D());
// This clip is between transformAboveEffect and the effect.
auto clip_above_effect = ClipPaintPropertyNode::Create(
ClipPaintPropertyNode::Root(), transform_above_effect,
FloatRoundedRect(-100, -100, 200, 200));
// This clip is between the effect and transformBelowEffect.
auto clip_below_effect =
ClipPaintPropertyNode::Create(clip_above_effect, transform_above_effect,
FloatRoundedRect(10, 10, 100, 100));
CompositorFilterOperations filters;
filters.AppendBlurFilter(20);
auto effect = EffectPaintPropertyNode::Create(
EffectPaintPropertyNode::Root(), transform_above_effect,
clip_above_effect, kColorFilterNone, filters, 1.0, SkBlendMode::kSrcOver);
local_state = PropertyTreeState(transform_below_effect.get(),
clip_below_effect.get(), effect.get());
input_rect = FloatRect(0, 0, 100, 100);
// 1. transformBelowEffect
auto output = transform_below_effect->Matrix().MapRect(input_rect);
// 2. clipBelowEffect
output.Intersect(clip_below_effect->ClipRect().Rect());
EXPECT_EQ(FloatRect(20, 30, 90, 80), output);
// 3. effect (the outset is 3 times of blur amount).
output = filters.MapRect(output);
EXPECT_EQ(FloatRect(-40, -30, 210, 200), output);
// 4. clipAboveEffect
output.Intersect(clip_above_effect->ClipRect().Rect());
EXPECT_EQ(FloatRect(-40, -30, 140, 130), output);
// 5. transformAboveEffect
output = transform_above_effect->Matrix().MapRect(output);
EXPECT_EQ(FloatRect(0, 20, 140, 130), output);
expected_transform =
transform_above_effect->Matrix() * transform_below_effect->Matrix();
expected_transformed_rect = expected_transform.MapRect(input_rect);
expected_visual_rect = FloatClipRect(output);
expected_visual_rect.ClearIsTight();
expected_clip = FloatClipRect(FloatRect(50, 60, 90, 90));
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, ReflectionWithPaintOffset) {
CompositorFilterOperations filters;
filters.AppendReferenceFilter(PaintFilterBuilder::BuildBoxReflectFilter(
BoxReflection(BoxReflection::kHorizontalReflection, 0), nullptr));
auto effect = EffectPaintPropertyNode::Create(
EffectPaintPropertyNode::Root(), TransformPaintPropertyNode::Root(),
ClipPaintPropertyNode::Root(), kColorFilterNone, filters, 1.0,
SkBlendMode::kSrcOver, CompositingReason::kNone, CompositorElementId(),
FloatPoint(100, 100));
local_state.SetEffect(effect.get());
input_rect = FloatRect(100, 100, 50, 50);
expected_transformed_rect = input_rect;
// Reflection is at (50, 100, 50, 50).
expected_visual_rect = FloatClipRect(FloatRect(50, 100, 100, 50));
expected_visual_rect.ClearIsTight();
CHECK_MAPPINGS();
}
TEST_P(GeometryMapperTest, InvertedClip) {
if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
TransformPaintPropertyNode::Root(),
FloatRoundedRect(10, 10, 50, 50));
PropertyTreeState dest(TransformPaintPropertyNode::Root(), clip.get(),
EffectPaintPropertyNode::Root());
FloatClipRect visual_rect(FloatRect(0, 0, 10, 200));
GeometryMapper::LocalToAncestorVisualRect(PropertyTreeState::Root(), dest,
visual_rect);
// The "ancestor" clip is below the source clip in this case, so
// LocalToAncestorVisualRect must fall back to the original rect, mapped
// into the root space.
EXPECT_EQ(FloatRect(0, 0, 10, 200), visual_rect.Rect());
EXPECT_FALSE(visual_rect.IsTight());
}
} // namespace blink