Reland "PlatformView partial blur #36015" (flutter/engine#37086)

This commit is contained in:
Chris Yang 2022-10-28 09:43:14 -07:00 committed by GitHub
parent 9dd73f3f2c
commit b7b95e9372
14 changed files with 673 additions and 348 deletions

View File

@ -100,8 +100,10 @@ void MutatorsStack::PushOpacity(const int& alpha) {
};
void MutatorsStack::PushBackdropFilter(
const std::shared_ptr<const DlImageFilter>& filter) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(filter);
const std::shared_ptr<const DlImageFilter>& filter,
const SkRect& filter_rect) {
std::shared_ptr<Mutator> element =
std::make_shared<Mutator>(filter, filter_rect);
vector_.push_back(element);
};

View File

@ -32,6 +32,33 @@ enum MutatorType {
kBackdropFilter
};
// Represents an image filter mutation.
//
// Should be used for image_filter_layer and backdrop_filter_layer.
// TODO(cyanglaz): Refactor this into a ImageFilterMutator class.
// https://github.com/flutter/flutter/issues/108470
class ImageFilterMutation {
public:
ImageFilterMutation(std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect)
: filter_(filter), filter_rect_(filter_rect) {}
const DlImageFilter& GetFilter() const { return *filter_; }
const SkRect& GetFilterRect() const { return filter_rect_; }
bool operator==(const ImageFilterMutation& other) const {
return *filter_ == *other.filter_ && filter_rect_ == other.filter_rect_;
}
bool operator!=(const ImageFilterMutation& other) const {
return !operator==(other);
}
private:
std::shared_ptr<const DlImageFilter> filter_;
const SkRect filter_rect_;
};
// Stores mutation information like clipping or kTransform.
//
// The `type` indicates the type of the mutation: kClipRect, kTransform and etc.
@ -59,7 +86,7 @@ class Mutator {
alpha_ = other.alpha_;
break;
case kBackdropFilter:
filter_ = other.filter_;
filter_mutation_ = other.filter_mutation_;
break;
default:
break;
@ -73,15 +100,20 @@ class Mutator {
explicit Mutator(const SkMatrix& matrix)
: type_(kTransform), matrix_(matrix) {}
explicit Mutator(const int& alpha) : type_(kOpacity), alpha_(alpha) {}
explicit Mutator(std::shared_ptr<const DlImageFilter> filter)
: type_(kBackdropFilter), filter_(filter) {}
explicit Mutator(std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect)
: type_(kBackdropFilter),
filter_mutation_(
std::make_shared<ImageFilterMutation>(filter, filter_rect)) {}
const MutatorType& GetType() const { return type_; }
const SkRect& GetRect() const { return rect_; }
const SkRRect& GetRRect() const { return rrect_; }
const SkPath& GetPath() const { return *path_; }
const SkMatrix& GetMatrix() const { return matrix_; }
const DlImageFilter& GetFilter() const { return *filter_; }
const ImageFilterMutation& GetFilterMutation() const {
return *filter_mutation_;
}
const int& GetAlpha() const { return alpha_; }
float GetAlphaFloat() const { return (alpha_ / 255.0); }
@ -101,7 +133,7 @@ class Mutator {
case kOpacity:
return alpha_ == other.alpha_;
case kBackdropFilter:
return *filter_ == *other.filter_;
return *filter_mutation_ == *other.filter_mutation_;
}
return false;
@ -132,8 +164,7 @@ class Mutator {
int alpha_;
};
std::shared_ptr<const DlImageFilter> filter_;
std::shared_ptr<ImageFilterMutation> filter_mutation_;
}; // Mutator
// A stack of mutators that can be applied to an embedded platform view.
@ -154,7 +185,8 @@ class MutatorsStack {
void PushClipPath(const SkPath& path);
void PushTransform(const SkMatrix& matrix);
void PushOpacity(const int& alpha);
void PushBackdropFilter(const std::shared_ptr<const DlImageFilter>& filter);
void PushBackdropFilter(const std::shared_ptr<const DlImageFilter>& filter,
const SkRect& filter_rect);
// Removes the `Mutator` on the top of the stack
// and destroys it.
@ -249,8 +281,9 @@ class EmbeddedViewParams {
const SkRect& finalBoundingRect() const { return final_bounding_rect_; }
// Pushes the stored DlImageFilter object to the mutators stack.
void PushImageFilter(std::shared_ptr<const DlImageFilter> filter) {
mutators_stack_.PushBackdropFilter(filter);
void PushImageFilter(std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) {
mutators_stack_.PushBackdropFilter(filter, filter_rect);
}
// Whether the embedder should construct DisplayList objects to hold the
@ -454,7 +487,8 @@ class ExternalViewEmbedder {
// See also: |PushVisitedPlatformView| for pushing platform view ids to the
// visited platform views list.
virtual void PushFilterToVisitedPlatformViews(
std::shared_ptr<const DlImageFilter> filter) {}
std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) {}
private:
bool used_this_frame_ = false;

View File

@ -44,7 +44,8 @@ void BackdropFilterLayer::Preroll(PrerollContext* context,
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_));
if (context->view_embedder != nullptr) {
context->view_embedder->PushFilterToVisitedPlatformViews(filter_);
context->view_embedder->PushFilterToVisitedPlatformViews(
filter_, context->cull_rect);
}
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);

View File

@ -94,14 +94,19 @@ TEST(MutatorsStack, PushBackdropFilter) {
const int num_of_mutators = 10;
for (int i = 0; i < num_of_mutators; i++) {
auto filter = std::make_shared<DlBlurImageFilter>(i, 5, DlTileMode::kClamp);
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(i, i, i, i));
}
auto iter = stack.Begin();
int i = 0;
while (iter != stack.End()) {
ASSERT_EQ(iter->get()->GetType(), MutatorType::kBackdropFilter);
ASSERT_EQ(iter->get()->GetFilter().asBlur()->sigma_x(), i);
ASSERT_EQ(iter->get()->GetFilterMutation().GetFilter().asBlur()->sigma_x(),
i);
ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().x(), i);
ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().x(), i);
ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().width(), i);
ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().height(), i);
++iter;
++i;
}
@ -164,7 +169,7 @@ TEST(MutatorsStack, Equality) {
int alpha = 240;
stack.PushOpacity(alpha);
auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeEmpty());
MutatorsStack stack_other;
SkMatrix matrix_other = SkMatrix::Scale(1, 1);
@ -179,7 +184,7 @@ TEST(MutatorsStack, Equality) {
stack_other.PushOpacity(other_alpha);
auto other_filter =
std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
stack_other.PushBackdropFilter(other_filter);
stack_other.PushBackdropFilter(other_filter, SkRect::MakeEmpty());
ASSERT_TRUE(stack == stack_other);
}
@ -211,9 +216,9 @@ TEST(Mutator, Initialization) {
ASSERT_TRUE(mutator5.GetType() == MutatorType::kOpacity);
auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
Mutator mutator6 = Mutator(filter);
Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty());
ASSERT_TRUE(mutator6.GetType() == MutatorType::kBackdropFilter);
ASSERT_TRUE(mutator6.GetFilter() == *filter);
ASSERT_TRUE(mutator6.GetFilterMutation().GetFilter() == *filter);
}
TEST(Mutator, CopyConstructor) {
@ -244,7 +249,7 @@ TEST(Mutator, CopyConstructor) {
ASSERT_TRUE(mutator5 == copy5);
auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
Mutator mutator6 = Mutator(filter);
Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty());
Mutator copy6 = Mutator(mutator6);
ASSERT_TRUE(mutator6 == copy6);
}
@ -276,9 +281,10 @@ TEST(Mutator, Equality) {
Mutator other_mutator5 = Mutator(alpha);
ASSERT_TRUE(mutator5 == other_mutator5);
auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
Mutator mutator6 = Mutator(filter);
Mutator other_mutator6 = Mutator(filter);
auto filter1 = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
auto filter2 = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
Mutator mutator6 = Mutator(filter1, SkRect::MakeEmpty());
Mutator other_mutator6 = Mutator(filter2, SkRect::MakeEmpty());
ASSERT_TRUE(mutator6 == other_mutator6);
}
@ -299,8 +305,8 @@ TEST(Mutator, UnEquality) {
auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);
auto filter2 =
std::make_shared<DlBlurImageFilter>(10, 10, DlTileMode::kClamp);
Mutator mutator3 = Mutator(filter);
Mutator other_mutator3 = Mutator(filter2);
Mutator mutator3 = Mutator(filter, SkRect::MakeEmpty());
Mutator other_mutator3 = Mutator(filter2, SkRect::MakeEmpty());
ASSERT_TRUE(mutator3 != other_mutator3);
}

View File

@ -89,10 +89,11 @@ void ShellTestExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) {
// |ExternalViewEmbedder|
void ShellTestExternalViewEmbedder::PushFilterToVisitedPlatformViews(
std::shared_ptr<const DlImageFilter> filter) {
std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) {
for (int64_t id : visited_platform_views_) {
EmbeddedViewParams params = current_composition_params_[id];
params.PushImageFilter(filter);
params.PushImageFilter(filter, filter_rect);
current_composition_params_[id] = params;
mutators_stacks_[id] = params.mutatorsStack();
}

View File

@ -76,7 +76,8 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder {
// |ExternalViewEmbedder|
void PushFilterToVisitedPlatformViews(
std::shared_ptr<const DlImageFilter> filter) override;
std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) override;
// |ExternalViewEmbedder|
void SubmitFrame(GrDirectContext* context,

View File

@ -845,7 +845,7 @@ TEST_F(ShellTest, PushBackdropFilterToVisitedPlatformViews) {
auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);
auto mutator = *external_view_embedder->GetStack(50).Begin();
ASSERT_EQ(mutator->GetType(), MutatorType::kBackdropFilter);
ASSERT_EQ(mutator->GetFilter(), filter);
ASSERT_EQ(mutator->GetFilterMutation().GetFilter(), filter);
DestroyShell(std::move(shell));
}

View File

@ -321,10 +321,11 @@ void FlutterPlatformViewsController::EndFrame(
}
void FlutterPlatformViewsController::PushFilterToVisitedPlatformViews(
std::shared_ptr<const DlImageFilter> filter) {
std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) {
for (int64_t id : visited_platform_views_) {
EmbeddedViewParams params = current_composition_params_[id];
params.PushImageFilter(filter);
params.PushImageFilter(filter, filter_rect);
current_composition_params_[id] = params;
}
}
@ -425,7 +426,7 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
CGRectGetWidth(flutter_view.bounds),
CGRectGetHeight(flutter_view.bounds))] autorelease];
NSMutableArray* blurRadii = [[[NSMutableArray alloc] init] autorelease];
NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
auto iter = mutators_stack.Begin();
while (iter != mutators_stack.End()) {
@ -448,13 +449,35 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
break;
case kBackdropFilter: {
// We only support DlBlurImageFilter for BackdropFilter.
if ((*iter)->GetFilter().asBlur() && canApplyBlurBackdrop) {
// sigma_x is arbitrarily chosen as the radius value because Quartz sets
// sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
// is not supported in Quartz's gaussianBlur CAFilter, so it is not used
// to blur the PlatformView.
[blurRadii addObject:@((*iter)->GetFilter().asBlur()->sigma_x())];
// Only support DlBlurImageFilter for BackdropFilter.
if (!(*iter)->GetFilterMutation().GetFilter().asBlur() || !canApplyBlurBackdrop) {
break;
}
CGRect filterRect =
flutter::GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect());
// `filterRect` reprents the rect that should be filtered inside the `flutter_view_`.
// The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
// filtered.
if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) {
break;
}
CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView];
// sigma_x is arbitrarily chosen as the radius value because Quartz sets
// sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
// is not supported in Quartz's gaussianBlur CAFilter, so it is not used
// to blur the PlatformView.
CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
UIVisualEffectView* visualEffectView = [[[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] autorelease];
PlatformViewFilter* filter =
[[[PlatformViewFilter alloc] initWithFrame:frameInClipView
blurRadius:blurRadius
visualEffectView:visualEffectView] autorelease];
if (!filter) {
canApplyBlurBackdrop = NO;
} else {
[blurFilters addObject:filter];
}
break;
}
@ -463,15 +486,16 @@ void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators
}
if (canApplyBlurBackdrop) {
canApplyBlurBackdrop = [clipView applyBlurBackdropFilters:blurRadii];
[clipView applyBlurBackdropFilters:blurFilters];
}
// Reverse the offset of the clipView.
// The clipView's frame includes the final translate of the final transform matrix.
// So we need to revese this translate so the platform view can layout at the correct offset.
// Thus, this translate needs to be reversed so the platform view can layout at the correct
// offset.
//
// Note that we don't apply this transform matrix the clippings because clippings happen on the
// mask view, whose origin is always (0,0) to the flutter_view.
// Note that the transforms are not applied to the clipping paths because clipping paths happen on
// the mask view, whose origin is always (0,0) to the flutter_view.
CATransform3D reverseTranslate =
CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0);
embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate);

View File

@ -283,7 +283,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
stack.PushTransform(screenScaleMatrix);
// Push a backdrop filter
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
auto embeddedViewParams =
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
@ -297,15 +297,90 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has the CAFilter, no additional filters were added
XCTAssertEqual(1, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// childClippingView has visual effect view with the correct configurations.
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
}
// sigmaX is chosen for input radius, regardless of sigmaY
NSObject* gaussianFilter = [childClippingView.layer.filters firstObject];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
- (void)testApplyBackdropFilterWithCorrectFrame {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners);
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
flutterPlatformViewsController->RegisterViewFactory(
factory, @"MockFlutterPlatformView",
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
FlutterResult result = ^(id result) {
};
flutterPlatformViewsController->OnMethodCall(
[FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
result);
XCTAssertNotNil(gMockPlatformView);
UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
// Create embedded view params
flutter::MutatorsStack stack;
// Layer tree always pushes a screen scale factor to the stack
SkMatrix screenScaleMatrix =
SkMatrix::Scale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
stack.PushTransform(screenScaleMatrix);
// Push a backdrop filter
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 8, 8));
auto embeddedViewParams =
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(5, 10), stack);
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
flutterPlatformViewsController->CompositeEmbeddedView(2);
XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
[mockFlutterView addSubview:childClippingView];
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has visual effect view with the correct configurations.
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 5, 8)
inputRadius:5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
}
- (void)testApplyMultipleBackdropFilters {
@ -349,7 +424,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
// Push backdrop filters
for (int i = 0; i < 50; i++) {
auto filter = std::make_shared<flutter::DlBlurImageFilter>(i, 2, flutter::DlTileMode::kClamp);
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
auto embeddedViewParams =
@ -364,17 +439,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(50, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// All filters have sigma X radius
for (int i = 0; i < 50; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(i), [gaussianFilter valueForKey:@"inputRadius"]);
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 50u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView);
}
- (void)testAddBackdropFilters {
@ -417,7 +494,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
stack.PushTransform(screenScaleMatrix);
// Push a backdrop filter
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
auto embeddedViewParams =
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
@ -431,10 +508,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has the CAFilter, no additional filters were added
XCTAssertEqual(1, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
//
// Simulate adding 1 backdrop filter (create a new mutators stack)
@ -444,7 +530,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
stack2.PushTransform(screenScaleMatrix);
// Push backdrop filters
for (int i = 0; i < 2; i++) {
stack2.PushBackdropFilter(filter);
stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -455,17 +541,20 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(2, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 2u);
// All filters have sigma X radius
for (int i = 0; i < 2; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 2u);
}
- (void)testRemoveBackdropFilters {
@ -509,7 +598,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
// Push backdrop filters
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
for (int i = 0; i < 5; i++) {
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
auto embeddedViewParams =
@ -531,7 +620,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
stack2.PushTransform(screenScaleMatrix);
// Push backdrop filters
for (int i = 0; i < 4; i++) {
stack2.PushBackdropFilter(filter);
stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -542,18 +631,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(4, (int)[childClippingView.layer.filters count]);
// All filters have sigma X radius
for (int i = 0; i < 4; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)5]) {
numberOfExpectedVisualEffectView++;
}
}
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
// Simulate removing all backdrop filters (replace the mutators stack)
// Update embedded view params, delete except screenScaleMatrix
@ -570,10 +660,13 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has no CAFilters because no backdrop filters were added
XCTAssertEqual(0, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if ([subview isKindOfClass:[UIVisualEffectView class]]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
}
- (void)testEditBackdropFilters {
@ -617,7 +710,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
// Push backdrop filters
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
for (int i = 0; i < 5; i++) {
stack.PushBackdropFilter(filter);
stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
auto embeddedViewParams =
@ -643,11 +736,11 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
auto filter2 =
std::make_shared<flutter::DlBlurImageFilter>(2, 5, flutter::DlTileMode::kClamp);
stack2.PushBackdropFilter(filter2);
stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10));
continue;
}
stack2.PushBackdropFilter(filter);
stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -658,21 +751,23 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(5, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The edited backdrop filter has the new radius value
for (int i = 0; i < 5; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
if (i == 3) {
XCTAssertEqualObjects(@(2), [gaussianFilter valueForKey:@"inputRadius"]);
} else {
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u);
CGFloat expectInputRadius = 5;
if (numberOfExpectedVisualEffectView == 3) {
expectInputRadius = 2;
}
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)expectInputRadius]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 5u);
// Simulate editing 1 backdrop filter in the beginning of the stack (replace the mutators stack)
// Update embedded view params, delete except screenScaleMatrix
@ -684,11 +779,11 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
if (i == 0) {
auto filter2 =
std::make_shared<flutter::DlBlurImageFilter>(2, 5, flutter::DlTileMode::kClamp);
stack2.PushBackdropFilter(filter2);
stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10));
continue;
}
stack2.PushBackdropFilter(filter);
stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -699,21 +794,23 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(5, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The edited backdrop filter has the new radius value
for (int i = 0; i < 5; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
if (i == 0) {
XCTAssertEqualObjects(@(2), [gaussianFilter valueForKey:@"inputRadius"]);
} else {
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u);
CGFloat expectInputRadius = 5;
if (numberOfExpectedVisualEffectView == 0) {
expectInputRadius = 2;
}
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)expectInputRadius]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 5u);
// Simulate editing 1 backdrop filter in the end of the stack (replace the mutators stack)
// Update embedded view params, delete except screenScaleMatrix
@ -725,11 +822,11 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
if (i == 4) {
auto filter2 =
std::make_shared<flutter::DlBlurImageFilter>(2, 5, flutter::DlTileMode::kClamp);
stack2.PushBackdropFilter(filter2);
stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10));
continue;
}
stack2.PushBackdropFilter(filter);
stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -740,21 +837,23 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(5, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The edited backdrop filter has the new radius value
for (int i = 0; i < 5; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
if (i == 4) {
XCTAssertEqualObjects(@(2), [gaussianFilter valueForKey:@"inputRadius"]);
} else {
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u);
CGFloat expectInputRadius = 5;
if (numberOfExpectedVisualEffectView == 4) {
expectInputRadius = 2;
}
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)expectInputRadius]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 5u);
// Simulate editing all backdrop filters in the stack (replace the mutators stack)
// Update embedded view params, delete except screenScaleMatrix
@ -765,7 +864,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
for (int i = 0; i < 5; i++) {
auto filter2 = std::make_shared<flutter::DlBlurImageFilter>(i, 2, flutter::DlTileMode::kClamp);
stack2.PushBackdropFilter(filter2);
stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -776,18 +875,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has CAFilters for the multiple backdrop filters
XCTAssertEqual(5, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The edited backdrop filter has the new radius value
for (int i = 0; i < 5; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(i), [gaussianFilter valueForKey:@"inputRadius"]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 5u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)numberOfExpectedVisualEffectView]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 5u);
}
- (void)testApplyBackdropFilterNotDlBlurImageFilter {
@ -830,7 +930,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
stack.PushTransform(screenScaleMatrix);
// Push a dilate backdrop filter
auto dilateFilter = std::make_shared<flutter::DlDilateImageFilter>(5, 2);
stack.PushBackdropFilter(dilateFilter);
stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty());
auto embeddedViewParams =
std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
@ -839,15 +939,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
flutterPlatformViewsController->CompositeEmbeddedView(2);
XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
[mockFlutterView addSubview:childClippingView];
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// No filters were added
XCTAssertEqual(0, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if ([subview isKindOfClass:[UIVisualEffectView class]]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
// Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators
// stack) Create embedded view params
@ -859,11 +963,11 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
for (int i = 0; i < 5; i++) {
if (i == 2) {
stack2.PushBackdropFilter(dilateFilter);
stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10));
continue;
}
stack2.PushBackdropFilter(blurFilter);
stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -874,17 +978,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// Filters were only added for DlBlurImageFilters
XCTAssertEqual(4, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The added filters are all gaussianBlur filters
for (int i = 0; i < 4; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
// Simulate adding a non-DlBlurImageFilter to the beginning of the stack (replace the mutators
// stack) Update embedded view params, delete except screenScaleMatrix
@ -894,11 +1000,11 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
// Push backdrop filters and dilate filter
for (int i = 0; i < 5; i++) {
if (i == 0) {
stack2.PushBackdropFilter(dilateFilter);
stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10));
continue;
}
stack2.PushBackdropFilter(blurFilter);
stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -909,17 +1015,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// Filters were only added for DlBlurImageFilters
XCTAssertEqual(4, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The added filters are all gaussianBlur filters
for (int i = 0; i < 4; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
// Simulate adding a non-DlBlurImageFilter to the end of the stack (replace the mutators stack)
// Update embedded view params, delete except screenScaleMatrix
@ -929,11 +1037,11 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
// Push backdrop filters and dilate filter
for (int i = 0; i < 5; i++) {
if (i == 4) {
stack2.PushBackdropFilter(dilateFilter);
stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10));
continue;
}
stack2.PushBackdropFilter(blurFilter);
stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -944,17 +1052,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// Filters were only added for DlBlurImageFilters
XCTAssertEqual(4, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// The added filters are all gaussianBlur filters
for (int i = 0; i < 4; i++) {
NSObject* gaussianFilter = childClippingView.layer.filters[i];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 4u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:(CGFloat)5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 4u);
// Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack)
// Update embedded view params, delete except screenScaleMatrix
@ -963,7 +1073,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
}
// Push dilate filters
for (int i = 0; i < 5; i++) {
stack2.PushBackdropFilter(dilateFilter);
stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10));
}
embeddedViewParams = std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix,
@ -974,38 +1084,45 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// No filters were added
XCTAssertEqual(0, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if ([subview isKindOfClass:[UIVisualEffectView class]]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
}
- (void)testApplyBackdropFilterAPIChanged {
NSArray* blurRadii = @[ @(1), @(5), @(10) ];
// The gaussianBlur filter is extracted once for each childClippingView.
// Each test requires a new childClippingView
- (void)testApplyBackdropFilterCorrectAPI {
[PlatformViewFilter resetPreparation];
// The gaussianBlur filter is extracted from UIVisualEffectView.
// Each test requires a new PlatformViewFilter
// Valid UIVisualEffectView API
ChildClippingView* childClippingView1 = [[ChildClippingView alloc] init];
childClippingView1.blurEffectView = [[UIVisualEffectView alloc]
UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
XCTAssertTrue([childClippingView1 applyBlurBackdropFilters:blurRadii]);
PlatformViewFilter* platformViewFilter =
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:visualEffectView];
XCTAssertNotNil(platformViewFilter);
}
// Invalid UIVisualEffectView initialization
ChildClippingView* childClippingView2 = [[ChildClippingView alloc] init];
childClippingView2.blurEffectView = [[UIVisualEffectView alloc] init];
XCTAssertFalse([childClippingView2 applyBlurBackdropFilters:blurRadii]);
- (void)testApplyBackdropFilterAPIChangedInvalidUIVisualEffectView {
[PlatformViewFilter resetPreparation];
UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] init];
PlatformViewFilter* platformViewFilter =
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:visualEffectView];
XCTAssertNil(platformViewFilter);
}
// Invalid UIView
ChildClippingView* childClippingView3 = [[ChildClippingView alloc] init];
childClippingView3.blurEffectView = [[UIView alloc] init];
XCTAssertFalse([childClippingView3 applyBlurBackdropFilters:blurRadii]);
// Invalid UIVisualEffectView API for "name"
UIVisualEffectView* editedUIVisualEffectView1 = [[UIVisualEffectView alloc]
- (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter {
[PlatformViewFilter resetPreparation];
UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
NSArray* subviews1 = editedUIVisualEffectView1.subviews;
for (UIView* view in subviews1) {
NSArray* subviews = editedUIVisualEffectView.subviews;
for (UIView* view in subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
for (CIFilter* filter in view.layer.filters) {
if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
@ -1016,16 +1133,19 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
break;
}
}
PlatformViewFilter* platformViewFilter =
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:editedUIVisualEffectView];
XCTAssertNil(platformViewFilter);
}
ChildClippingView* childClippingView4 = [[ChildClippingView alloc] init];
childClippingView4.blurEffectView = editedUIVisualEffectView1;
XCTAssertFalse([childClippingView4 applyBlurBackdropFilters:blurRadii]);
// Invalid UIVisualEffectView API for "inputRadius"
UIVisualEffectView* editedUIVisualEffectView2 = [[UIVisualEffectView alloc]
- (void)testApplyBackdropFilterAPIChangedInvalidInputRadius {
[PlatformViewFilter resetPreparation];
UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
NSArray* subviews2 = editedUIVisualEffectView2.subviews;
for (UIView* view in subviews2) {
NSArray* subviews = editedUIVisualEffectView.subviews;
for (UIView* view in subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
for (CIFilter* filter in view.layer.filters) {
if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
@ -1037,9 +1157,28 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
}
}
ChildClippingView* childClippingView5 = [[ChildClippingView alloc] init];
childClippingView5.blurEffectView = editedUIVisualEffectView2;
XCTAssertFalse([childClippingView5 applyBlurBackdropFilters:blurRadii]);
PlatformViewFilter* platformViewFilter =
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:editedUIVisualEffectView];
XCTAssertNil(platformViewFilter);
}
- (void)testBackdropFilterVisualEffectSubviewBackgroundColor {
UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
PlatformViewFilter* platformViewFilter =
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:visualEffectView];
CGColorRef visualEffectSubviewBackgroundColor;
for (UIView* view in [platformViewFilter backdropFilterView].subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) {
visualEffectSubviewBackgroundColor = view.layer.backgroundColor;
}
}
XCTAssertTrue(
CGColorEqualToColor(visualEffectSubviewBackgroundColor, UIColor.clearColor.CGColor));
}
- (void)testCompositePlatformView {
@ -1142,7 +1281,8 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
flutterPlatformViewsController->PushVisitedPlatformView(2);
auto filter = std::make_shared<flutter::DlBlurImageFilter>(5, 2, flutter::DlTileMode::kClamp);
flutterPlatformViewsController->PushFilterToVisitedPlatformViews(filter);
flutterPlatformViewsController->PushFilterToVisitedPlatformViews(filter,
SkRect::MakeXYWH(0, 0, 10, 10));
flutterPlatformViewsController->CompositeEmbeddedView(2);
XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]);
ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
@ -1151,15 +1291,20 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// childClippingView has the CAFilter, no additional filters were added
XCTAssertEqual(1, (int)[childClippingView.layer.filters count]);
// No new views were added
XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]);
// sigmaX is chosen for input radius, regardless of sigmaY
NSObject* gaussianFilter = [childClippingView.layer.filters firstObject];
XCTAssertEqualObjects(@"gaussianBlur", [gaussianFilter valueForKey:@"name"]);
XCTAssertEqualObjects(@(5), [gaussianFilter valueForKey:@"inputRadius"]);
// childClippingView has visual effect view with the correct configurations.
NSUInteger numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
XCTAssertLessThan(numberOfExpectedVisualEffectView, 1u);
if ([self validateOneVisualEffectView:subview
expectedFrame:CGRectMake(0, 0, 10, 10)
inputRadius:5]) {
numberOfExpectedVisualEffectView++;
}
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 1u);
// New frame, with no filter pushed.
auto embeddedViewParams2 =
@ -1172,8 +1317,14 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
[mockFlutterView setNeedsLayout];
[mockFlutterView layoutIfNeeded];
// No filter in this frame.
XCTAssertEqual(0, (int)[childClippingView.layer.filters count]);
numberOfExpectedVisualEffectView = 0;
for (UIView* subview in childClippingView.subviews) {
if (![subview isKindOfClass:[UIVisualEffectView class]]) {
continue;
}
numberOfExpectedVisualEffectView++;
}
XCTAssertEqual(numberOfExpectedVisualEffectView, 0u);
}
- (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
@ -2083,4 +2234,31 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree);
}
// Return true if a correct visual effect view is found. It also implies all the validation in this
// method passes.
//
// There are two fail states for this method. 1. One of the XCTAssert method failed; or 2. No
// correct visual effect view found.
- (BOOL)validateOneVisualEffectView:(UIView*)visualEffectView
expectedFrame:(CGRect)frame
inputRadius:(CGFloat)inputRadius {
XCTAssertTrue(CGRectEqualToRect(visualEffectView.frame, frame));
for (UIView* view in visualEffectView.subviews) {
if (![view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
continue;
}
XCTAssertEqual(view.layer.filters.count, 1u);
NSObject* filter = view.layer.filters.firstObject;
XCTAssertEqualObjects([filter valueForKey:@"name"], @"gaussianBlur");
NSObject* inputRadiusInFilter = [filter valueForKey:@"inputRadius"];
XCTAssertTrue([inputRadiusInFilter isKindOfClass:[NSNumber class]] &&
flutter::BlurRadiusEqualToBlurRadius(((NSNumber*)inputRadiusInFilter).floatValue,
inputRadius));
return YES;
}
return NO;
}
@end

View File

@ -45,18 +45,54 @@
@end
// The parent view handles clipping to its subviews.
// An object represents a blur filter.
//
// This object produces a `backdropFilterView`.
// To blur a View, add `backdropFilterView` as a subView of the View.
@interface PlatformViewFilter : NSObject
// Determines the rect of the blur effect in the coordinate system of `backdropFilterView`'s
// parentView.
@property(assign, nonatomic, readonly) CGRect frame;
// Determines the blur intensity.
//
// It is set as the value of `inputRadius` of the `gaussianFilter` that is internally used.
@property(assign, nonatomic, readonly) CGFloat blurRadius;
// This is the view to use to blur the PlatformView.
//
// It is a modified version of UIKit's `UIVisualEffectView`.
// The inputRadius can be customized and it doesn't add any color saturation to the blurred view.
@property(nonatomic, retain, readonly) UIVisualEffectView* backdropFilterView;
// For testing only.
+ (void)resetPreparation;
- (instancetype)init NS_UNAVAILABLE;
// Initialize the filter object.
//
// The `frame` determines the rect of the blur effect in the coordinate system of
// `backdropFilterView`'s parentView. The `blurRadius` determines the blur intensity. It is set as
// the value of `inputRadius` of the `gaussianFilter` that is internally used. The
// `UIVisualEffectView` is the view that is used to add the blur effects. It is modified to become
// `backdropFilterView`, which better supports the need of Flutter.
//
// Note: if the implementation of UIVisualEffectView changes in a way that affects the
// implementation in `PlatformViewFilter`, this method will return nil.
- (instancetype)initWithFrame:(CGRect)frame
blurRadius:(CGFloat)blurRadius
visualEffectView:(UIVisualEffectView*)visualEffectView NS_DESIGNATED_INITIALIZER;
@end
// The parent view handles clipping to its subViews.
@interface ChildClippingView : UIView
// Applies blur backdrop filters to the ChildClippingView with blur radius values from
// blurRadii. Returns NO if Apple's API has changed and blurred backdrop filters cannot
// be applied, otherwise returns YES.
- (BOOL)applyBlurBackdropFilters:(NSArray*)blurRadii;
// The UIView used to extract the gaussianBlur filter. This must be a UIVisualEffectView
// initalized with UIBlurEffect to extract the correct filter. Made a public property
// for custom unit tests.
@property(nonatomic, retain) UIView* blurEffectView;
// Applies blur backdrop filters to the ChildClippingView with blur values from
// filters.
- (void)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters;
@end
@ -69,6 +105,9 @@ CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix);
// The position of the `layer` should be unchanged after resetting the anchor.
void ResetAnchor(CALayer* layer);
CGRect GetCGRectFromSkRect(const SkRect& clipSkRect);
BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2);
class IOSContextGL;
class IOSSurface;
@ -197,7 +236,8 @@ class FlutterPlatformViewsController {
long FindFirstResponderPlatformViewId();
// Pushes backdrop filter mutation to the mutator stack of each visited platform view.
void PushFilterToVisitedPlatformViews(std::shared_ptr<const DlImageFilter> filter);
void PushFilterToVisitedPlatformViews(std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect);
// Pushes the view id of a visted platform view to the list of visied platform views.
void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); }

View File

@ -55,26 +55,106 @@ void ResetAnchor(CALayer* layer) {
layer.position = CGPointZero;
}
CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) {
return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
clipSkRect.fBottom - clipSkRect.fTop);
}
BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) {
const CGFloat epsilon = 0.01;
return radius1 - radius2 < epsilon;
}
} // namespace flutter
@implementation ChildClippingView {
// A gaussianFilter from UIVisualEffectView that can be copied for new backdrop filters.
NSObject* _gaussianFilter;
@implementation PlatformViewFilter
static NSObject* _gaussianBlurFilter = nil;
// The index of "_UIVisualEffectBackdropView" in UIVisualEffectView's subViews.
static NSInteger _indexOfBackdropView = -1;
// The index of "_UIVisualEffectSubview" in UIVisualEffectView's subViews.
static NSInteger _indexOfVisualEffectSubview = -1;
static BOOL _preparedOnce = NO;
- (instancetype)initWithFrame:(CGRect)frame
blurRadius:(CGFloat)blurRadius
visualEffectView:(UIVisualEffectView*)visualEffectView {
if (self = [super init]) {
_frame = frame;
_blurRadius = blurRadius;
[PlatformViewFilter prepareOnce:visualEffectView];
if (![PlatformViewFilter isUIVisualEffectViewImplementationValid]) {
FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to "
"access the gaussianBlur CAFilter.";
[self release];
return nil;
}
NSObject* gaussianBlurFilter = [[_gaussianBlurFilter copy] autorelease];
FML_DCHECK(gaussianBlurFilter);
UIView* backdropView = visualEffectView.subviews[_indexOfBackdropView];
[gaussianBlurFilter setValue:@(_blurRadius) forKey:@"inputRadius"];
backdropView.layer.filters = @[ gaussianBlurFilter ];
UIView* visualEffectSubview = visualEffectView.subviews[_indexOfVisualEffectSubview];
visualEffectSubview.layer.backgroundColor = UIColor.clearColor.CGColor;
_backdropFilterView = [visualEffectView retain];
_backdropFilterView.frame = _frame;
}
return self;
}
// Lazy initializes blurEffectView as the expected UIVisualEffectView. The backdropFilter blur
// requires this UIVisualEffectView initialization. The lazy initalization is only used to allow
// custom unit tests.
- (UIView*)blurEffectView {
if (!_blurEffectView) {
// blurEffectView is only needed to extract its gaussianBlur filter. It is released after
// searching its subviews and extracting the filter.
_blurEffectView = [[[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] retain];
}
return _blurEffectView;
+ (void)resetPreparation {
_preparedOnce = NO;
[_gaussianBlurFilter release];
_gaussianBlurFilter = nil;
_indexOfBackdropView = -1;
_indexOfVisualEffectSubview = -1;
}
+ (void)prepareOnce:(UIVisualEffectView*)visualEffectView {
if (_preparedOnce) {
return;
}
for (NSUInteger i = 0; i < visualEffectView.subviews.count; i++) {
UIView* view = visualEffectView.subviews[i];
if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
_indexOfBackdropView = i;
for (NSObject* filter in view.layer.filters) {
if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"] &&
[[filter valueForKey:@"inputRadius"] isKindOfClass:[NSNumber class]]) {
_gaussianBlurFilter = [filter retain];
break;
}
}
} else if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) {
_indexOfVisualEffectSubview = i;
}
}
_preparedOnce = YES;
}
+ (BOOL)isUIVisualEffectViewImplementationValid {
return _indexOfBackdropView > -1 && _indexOfVisualEffectSubview > -1 && _gaussianBlurFilter;
}
- (void)dealloc {
[_backdropFilterView release];
_backdropFilterView = nil;
[super dealloc];
}
@end
@interface ChildClippingView ()
@property(retain, nonatomic) NSMutableArray<PlatformViewFilter*>* filters;
@end
@implementation ChildClippingView
// The ChildClippingView's frame is the bounding rect of the platform view. we only want touches to
// be hit tested and consumed by this view if they are inside the embedded platform view which could
// be smaller the embedded platform view is rotated.
@ -87,92 +167,53 @@ void ResetAnchor(CALayer* layer) {
return NO;
}
// Creates and initializes a UIVisualEffectView with a UIBlurEffect. Extracts and returns its
// gaussianFilter. Returns nil if Apple's API has changed and the filter cannot be extracted.
- (NSObject*)extractGaussianFilter {
NSObject* gaussianFilter = nil;
for (UIView* view in self.blurEffectView.subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
for (CIFilter* filter in view.layer.filters) {
if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) {
if ([[filter valueForKey:@"inputRadius"] isKindOfClass:[NSNumber class]]) {
gaussianFilter = filter;
}
// No need to look at other CIFilters. If the API structure has not changed, the
// gaussianBlur filter was succesfully saved. Otherwise, still exit the loop because the
// filter cannot be extracted.
break;
}
}
// No need to look at other UIViews. If the API structure has not changed, the gaussianBlur
// filter was succesfully saved. Otherwise, still exit the loop because the filter cannot
// be extracted.
break;
}
}
return gaussianFilter;
}
- (BOOL)applyBlurBackdropFilters:(NSArray*)blurRadii {
// The outer if-statement checks for the first time this method is called and _gaussianFilter is
// not initialized. The inner if-statement checks if extracting the gaussianBlur was successful.
// If it was not successful, this method will not be called again. Thus the if-statements check
// for different conditions.
if (!_gaussianFilter) {
_gaussianFilter = [self extractGaussianFilter];
if (!_gaussianFilter) {
FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to "
"access the gaussianBlur CAFilter.";
return NO;
}
}
BOOL newRadiusValues = NO;
if ([blurRadii count] != [self.layer.filters count]) {
newRadiusValues = YES;
- (void)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters {
BOOL needUpdateFilterViews = NO;
if (self.filters.count != filters.count) {
needUpdateFilterViews = YES;
} else {
for (NSUInteger i = 0; i < [blurRadii count]; i++) {
if ([self.layer.filters[i] valueForKey:@"inputRadius"] != blurRadii[i]) {
newRadiusValues = YES;
break;
for (NSUInteger i = 0; i < filters.count; i++) {
if (!CGRectEqualToRect(self.filters[i].frame, filters[i].frame) ||
!flutter::BlurRadiusEqualToBlurRadius(self.filters[i].blurRadius,
filters[i].blurRadius)) {
needUpdateFilterViews = YES;
}
}
}
if (newRadiusValues) {
NSMutableArray* newGaussianFilters = [[[NSMutableArray alloc] init] autorelease];
for (NSUInteger i = 0; i < [blurRadii count]; i++) {
NSObject* newGaussianFilter = [[_gaussianFilter copy] autorelease];
[newGaussianFilter setValue:blurRadii[i] forKey:@"inputRadius"];
[newGaussianFilters addObject:newGaussianFilter];
if (needUpdateFilterViews) {
// Clear the old filter views.
for (PlatformViewFilter* filter in self.filters) {
[[filter backdropFilterView] removeFromSuperview];
}
// Update to the new filters.
self.filters = [filters retain];
// Add new filter views.
for (PlatformViewFilter* filter in self.filters) {
UIView* backdropFilterView = [filter backdropFilterView];
[self addSubview:backdropFilterView];
}
self.layer.filters = newGaussianFilters;
}
return YES;
}
- (void)dealloc {
[_blurEffectView release];
_blurEffectView = nil;
[_filters release];
_filters = nil;
[_gaussianFilter release];
_gaussianFilter = nil;
[super dealloc];
}
- (NSMutableArray*)filters {
if (!_filters) {
_filters = [[[NSMutableArray alloc] init] retain];
}
return _filters;
}
@end
@interface FlutterClippingMaskView ()
- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect;
@end
@ -212,7 +253,7 @@ void ResetAnchor(CALayer* layer) {
}
- (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix {
CGRect clipRect = [self getCGRectFromSkRect:clipSkRect];
CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRect);
CGPathRef path = CGPathCreateWithRect(clipRect, nil);
paths_.push_back([self getTransformedPath:path matrix:matrix]);
}
@ -229,7 +270,7 @@ void ResetAnchor(CALayer* layer) {
}
case SkRRect::kOval_Type:
case SkRRect::kSimple_Type: {
CGRect clipRect = [self getCGRectFromSkRect:clipSkRRect.rect()];
CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRRect.rect());
pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(),
clipSkRRect.getSimpleRadii().y(), nil);
break;
@ -296,7 +337,7 @@ void ResetAnchor(CALayer* layer) {
SkPath::Iter iter(path, true);
SkPoint pts[kMaxPointsInVerb];
SkPath::Verb verb = iter.next(pts);
SkPoint last_pt_from_last_verb;
SkPoint last_pt_from_last_verb = SkPoint::Make(0, 0);
while (verb != SkPath::kDone_Verb) {
if (verb == SkPath::kLine_Verb || verb == SkPath::kQuad_Verb || verb == SkPath::kConic_Verb ||
verb == SkPath::kCubic_Verb) {
@ -353,9 +394,4 @@ void ResetAnchor(CALayer* layer) {
return fml::CFRef<CGPathRef>(transformedPath);
}
- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect {
return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
clipSkRect.fBottom - clipSkRect.fTop);
}
@end

View File

@ -69,7 +69,8 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder {
// |ExternalViewEmbedder|
void PushFilterToVisitedPlatformViews(
std::shared_ptr<const DlImageFilter> filter) override;
std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) override;
// |ExternalViewEmbedder|
void PushVisitedPlatformView(int64_t view_id) override;

View File

@ -100,8 +100,9 @@ bool IOSExternalViewEmbedder::SupportsDynamicThreadMerging() {
// |ExternalViewEmbedder|
void IOSExternalViewEmbedder::PushFilterToVisitedPlatformViews(
std::shared_ptr<const DlImageFilter> filter) {
platform_views_controller_->PushFilterToVisitedPlatformViews(filter);
std::shared_ptr<const DlImageFilter> filter,
const SkRect& filter_rect) {
platform_views_controller_->PushFilterToVisitedPlatformViews(filter, filter_rect);
}
// |ExternalViewEmbedder|

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 46 KiB