From 67ebf9952f250344568abbba97b981924695561c Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 27 Oct 2022 11:09:32 -0700 Subject: [PATCH] Revert "PlatformView partial blur (#36015)" This reverts commit abc3aabad667363dddba2bf974d91029e06f77a9. --- flow/embedded_views.cc | 6 +- flow/embedded_views.h | 56 +- flow/layers/backdrop_filter_layer.cc | 3 +- flow/mutators_stack_unittests.cc | 30 +- .../shell_test_external_view_embedder.cc | 5 +- .../shell_test_external_view_embedder.h | 3 +- shell/common/shell_unittests.cc | 2 +- .../framework/Source/FlutterPlatformViews.mm | 52 +- .../Source/FlutterPlatformViewsTest.mm | 578 ++++++------------ .../Source/FlutterPlatformViews_Internal.h | 62 +- .../Source/FlutterPlatformViews_Internal.mm | 208 +++---- .../darwin/ios/ios_external_view_embedder.h | 3 +- .../darwin/ios/ios_external_view_embedder.mm | 5 +- ...ackdrop_filter_iPhone 8_13.0_simulator.png | Bin 47294 -> 49083 bytes 14 files changed, 344 insertions(+), 669 deletions(-) diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index f3388bab4641b..2982cb40824b9 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -100,10 +100,8 @@ void MutatorsStack::PushOpacity(const int& alpha) { }; void MutatorsStack::PushBackdropFilter( - const std::shared_ptr& filter, - const SkRect& filter_rect) { - std::shared_ptr element = - std::make_shared(filter, filter_rect); + const std::shared_ptr& filter) { + std::shared_ptr element = std::make_shared(filter); vector_.push_back(element); }; diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 84ad68f41faa6..4627c7d880a06 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -32,33 +32,6 @@ 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 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 filter_; - const SkRect filter_rect_; -}; - // Stores mutation information like clipping or kTransform. // // The `type` indicates the type of the mutation: kClipRect, kTransform and etc. @@ -86,7 +59,7 @@ class Mutator { alpha_ = other.alpha_; break; case kBackdropFilter: - filter_mutation_ = other.filter_mutation_; + filter_ = other.filter_; break; default: break; @@ -100,20 +73,15 @@ 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 filter, - const SkRect& filter_rect) - : type_(kBackdropFilter), - filter_mutation_( - std::make_shared(filter, filter_rect)) {} + explicit Mutator(std::shared_ptr filter) + : type_(kBackdropFilter), filter_(filter) {} 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 ImageFilterMutation& GetFilterMutation() const { - return *filter_mutation_; - } + const DlImageFilter& GetFilter() const { return *filter_; } const int& GetAlpha() const { return alpha_; } float GetAlphaFloat() const { return (alpha_ / 255.0); } @@ -133,7 +101,7 @@ class Mutator { case kOpacity: return alpha_ == other.alpha_; case kBackdropFilter: - return *filter_mutation_ == *other.filter_mutation_; + return *filter_ == *other.filter_; } return false; @@ -164,7 +132,8 @@ class Mutator { int alpha_; }; - std::shared_ptr filter_mutation_; + std::shared_ptr filter_; + }; // Mutator // A stack of mutators that can be applied to an embedded platform view. @@ -185,8 +154,7 @@ class MutatorsStack { void PushClipPath(const SkPath& path); void PushTransform(const SkMatrix& matrix); void PushOpacity(const int& alpha); - void PushBackdropFilter(const std::shared_ptr& filter, - const SkRect& filter_rect); + void PushBackdropFilter(const std::shared_ptr& filter); // Removes the `Mutator` on the top of the stack // and destroys it. @@ -284,9 +252,8 @@ class EmbeddedViewParams { const SkRect& finalBoundingRect() const { return final_bounding_rect_; } // Pushes the stored DlImageFilter object to the mutators stack. - void PushImageFilter(std::shared_ptr filter, - const SkRect& filter_rect) { - mutators_stack_.PushBackdropFilter(filter, filter_rect); + void PushImageFilter(std::shared_ptr filter) { + mutators_stack_.PushBackdropFilter(filter); } // Whether the embedder should construct DisplayList objects to hold the @@ -490,8 +457,7 @@ class ExternalViewEmbedder { // See also: |PushVisitedPlatformView| for pushing platform view ids to the // visited platform views list. virtual void PushFilterToVisitedPlatformViews( - std::shared_ptr filter, - const SkRect& filter_rect) {} + std::shared_ptr filter) {} private: bool used_this_frame_ = false; diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index 6fceb859c380b..07a596662d730 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -43,8 +43,7 @@ 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->cull_rect); + context->view_embedder->PushFilterToVisitedPlatformViews(filter_); } SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, &child_paint_bounds); diff --git a/flow/mutators_stack_unittests.cc b/flow/mutators_stack_unittests.cc index 42ce6363d47ff..54b6b516e4a72 100644 --- a/flow/mutators_stack_unittests.cc +++ b/flow/mutators_stack_unittests.cc @@ -94,19 +94,14 @@ TEST(MutatorsStack, PushBackdropFilter) { const int num_of_mutators = 10; for (int i = 0; i < num_of_mutators; i++) { auto filter = std::make_shared(i, 5, DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(i, i, i, i)); + stack.PushBackdropFilter(filter); } auto iter = stack.Begin(); int i = 0; while (iter != stack.End()) { ASSERT_EQ(iter->get()->GetType(), MutatorType::kBackdropFilter); - 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); + ASSERT_EQ(iter->get()->GetFilter().asBlur()->sigma_x(), i); ++iter; ++i; } @@ -169,7 +164,7 @@ TEST(MutatorsStack, Equality) { int alpha = 240; stack.PushOpacity(alpha); auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeEmpty()); + stack.PushBackdropFilter(filter); MutatorsStack stack_other; SkMatrix matrix_other = SkMatrix::Scale(1, 1); @@ -184,7 +179,7 @@ TEST(MutatorsStack, Equality) { stack_other.PushOpacity(other_alpha); auto other_filter = std::make_shared(5, 5, DlTileMode::kClamp); - stack_other.PushBackdropFilter(other_filter, SkRect::MakeEmpty()); + stack_other.PushBackdropFilter(other_filter); ASSERT_TRUE(stack == stack_other); } @@ -216,9 +211,9 @@ TEST(Mutator, Initialization) { ASSERT_TRUE(mutator5.GetType() == MutatorType::kOpacity); auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty()); + Mutator mutator6 = Mutator(filter); ASSERT_TRUE(mutator6.GetType() == MutatorType::kBackdropFilter); - ASSERT_TRUE(mutator6.GetFilterMutation().GetFilter() == *filter); + ASSERT_TRUE(mutator6.GetFilter() == *filter); } TEST(Mutator, CopyConstructor) { @@ -249,7 +244,7 @@ TEST(Mutator, CopyConstructor) { ASSERT_TRUE(mutator5 == copy5); auto filter = std::make_shared(5, 5, DlTileMode::kClamp); - Mutator mutator6 = Mutator(filter, SkRect::MakeEmpty()); + Mutator mutator6 = Mutator(filter); Mutator copy6 = Mutator(mutator6); ASSERT_TRUE(mutator6 == copy6); } @@ -281,10 +276,9 @@ TEST(Mutator, Equality) { Mutator other_mutator5 = Mutator(alpha); ASSERT_TRUE(mutator5 == other_mutator5); - auto filter1 = std::make_shared(5, 5, DlTileMode::kClamp); - auto filter2 = std::make_shared(5, 5, DlTileMode::kClamp); - Mutator mutator6 = Mutator(filter1, SkRect::MakeEmpty()); - Mutator other_mutator6 = Mutator(filter2, SkRect::MakeEmpty()); + auto filter = std::make_shared(5, 5, DlTileMode::kClamp); + Mutator mutator6 = Mutator(filter); + Mutator other_mutator6 = Mutator(filter); ASSERT_TRUE(mutator6 == other_mutator6); } @@ -305,8 +299,8 @@ TEST(Mutator, UnEquality) { auto filter = std::make_shared(5, 5, DlTileMode::kClamp); auto filter2 = std::make_shared(10, 10, DlTileMode::kClamp); - Mutator mutator3 = Mutator(filter, SkRect::MakeEmpty()); - Mutator other_mutator3 = Mutator(filter2, SkRect::MakeEmpty()); + Mutator mutator3 = Mutator(filter); + Mutator other_mutator3 = Mutator(filter2); ASSERT_TRUE(mutator3 != other_mutator3); } diff --git a/shell/common/shell_test_external_view_embedder.cc b/shell/common/shell_test_external_view_embedder.cc index 7d660b724b2f2..789ebc22d72b8 100644 --- a/shell/common/shell_test_external_view_embedder.cc +++ b/shell/common/shell_test_external_view_embedder.cc @@ -89,11 +89,10 @@ void ShellTestExternalViewEmbedder::PushVisitedPlatformView(int64_t view_id) { // |ExternalViewEmbedder| void ShellTestExternalViewEmbedder::PushFilterToVisitedPlatformViews( - std::shared_ptr filter, - const SkRect& filter_rect) { + std::shared_ptr filter) { for (int64_t id : visited_platform_views_) { EmbeddedViewParams params = current_composition_params_[id]; - params.PushImageFilter(filter, filter_rect); + params.PushImageFilter(filter); current_composition_params_[id] = params; mutators_stacks_[id] = params.mutatorsStack(); } diff --git a/shell/common/shell_test_external_view_embedder.h b/shell/common/shell_test_external_view_embedder.h index 5bff1c97ebdc9..583a09182e5fc 100644 --- a/shell/common/shell_test_external_view_embedder.h +++ b/shell/common/shell_test_external_view_embedder.h @@ -76,8 +76,7 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder { // |ExternalViewEmbedder| void PushFilterToVisitedPlatformViews( - std::shared_ptr filter, - const SkRect& filter_rect) override; + std::shared_ptr filter) override; // |ExternalViewEmbedder| void SubmitFrame(GrDirectContext* context, diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 0c93386c12c64..799aa814a841a 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -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->GetFilterMutation().GetFilter(), filter); + ASSERT_EQ(mutator->GetFilter(), filter); DestroyShell(std::move(shell)); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 8442e1eadd9cb..00d8324d11b55 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -321,11 +321,10 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } void FlutterPlatformViewsController::PushFilterToVisitedPlatformViews( - std::shared_ptr filter, - const SkRect& filter_rect) { + std::shared_ptr filter) { for (int64_t id : visited_platform_views_) { EmbeddedViewParams params = current_composition_params_[id]; - params.PushImageFilter(filter, filter_rect); + params.PushImageFilter(filter); current_composition_params_[id] = params; } } @@ -426,7 +425,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { CGRectGetWidth(flutter_view.bounds), CGRectGetHeight(flutter_view.bounds))] autorelease]; - NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease]; + NSMutableArray* blurRadii = [[[NSMutableArray alloc] init] autorelease]; auto iter = mutators_stack.Begin(); while (iter != mutators_stack.End()) { @@ -449,35 +448,13 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha; break; case kBackdropFilter: { - // 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]; + // 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())]; } break; } @@ -486,16 +463,15 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } if (canApplyBlurBackdrop) { - [clipView applyBlurBackdropFilters:blurFilters]; + canApplyBlurBackdrop = [clipView applyBlurBackdropFilters:blurRadii]; } // Reverse the offset of the clipView. // The clipView's frame includes the final translate of the final transform matrix. - // Thus, this translate needs to be reversed so the platform view can layout at the correct - // offset. + // So we need to revese this translate so the platform view can layout at the correct offset. // - // 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. + // 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. CATransform3D reverseTranslate = CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0); embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 9e678e5121bd7..b73373760c106 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -283,7 +283,7 @@ - (void)testApplyBackdropFilter { stack.PushTransform(screenScaleMatrix); // Push a backdrop filter auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack.PushBackdropFilter(filter); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); @@ -297,90 +297,15 @@ - (void)testApplyBackdropFilter { [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, 10, 10) - inputRadius:5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); -} - -- (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(); - auto platform_view = std::make_unique( - /*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(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 8, 8)); + // 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]); - auto embeddedViewParams = - std::make_unique(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); + // 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)testApplyMultipleBackdropFilters { @@ -424,7 +349,7 @@ - (void)testApplyMultipleBackdropFilters { // Push backdrop filters for (int i = 0; i < 50; i++) { auto filter = std::make_shared(i, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack.PushBackdropFilter(filter); } auto embeddedViewParams = @@ -439,19 +364,17 @@ - (void)testApplyMultipleBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; - } + // 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"]); } - XCTAssertEqual(numberOfExpectedVisualEffectView, (NSUInteger)numberOfExpectedVisualEffectView); } - (void)testAddBackdropFilters { @@ -494,7 +417,7 @@ - (void)testAddBackdropFilters { stack.PushTransform(screenScaleMatrix); // Push a backdrop filter auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack.PushBackdropFilter(filter); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); @@ -508,19 +431,10 @@ - (void)testAddBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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); + // 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]); // // Simulate adding 1 backdrop filter (create a new mutators stack) @@ -530,7 +444,7 @@ - (void)testAddBackdropFilters { stack2.PushTransform(screenScaleMatrix); // Push backdrop filters for (int i = 0; i < 2; i++) { - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -541,20 +455,17 @@ - (void)testAddBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - XCTAssertLessThan(numberOfExpectedVisualEffectView, 2u); + // 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]); - if ([self validateOneVisualEffectView:subview - expectedFrame:CGRectMake(0, 0, 10, 10) - inputRadius:(CGFloat)5]) { - numberOfExpectedVisualEffectView++; - } + // 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"]); } - XCTAssertEqual(numberOfExpectedVisualEffectView, 2u); } - (void)testRemoveBackdropFilters { @@ -598,7 +509,7 @@ - (void)testRemoveBackdropFilters { // Push backdrop filters auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack.PushBackdropFilter(filter); } auto embeddedViewParams = @@ -620,7 +531,7 @@ - (void)testRemoveBackdropFilters { stack2.PushTransform(screenScaleMatrix); // Push backdrop filters for (int i = 0; i < 4; i++) { - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -631,19 +542,18 @@ - (void)testRemoveBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; - } + // 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"]); } - XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); + + // No new views were added + XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); // Simulate removing all backdrop filters (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -660,13 +570,10 @@ - (void)testRemoveBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if ([subview isKindOfClass:[UIVisualEffectView class]]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); + // 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]); } - (void)testEditBackdropFilters { @@ -710,7 +617,7 @@ - (void)testEditBackdropFilters { // Push backdrop filters auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); for (int i = 0; i < 5; i++) { - stack.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack.PushBackdropFilter(filter); } auto embeddedViewParams = @@ -736,11 +643,11 @@ - (void)testEditBackdropFilters { auto filter2 = std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter2); continue; } - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -751,23 +658,21 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; + // 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"]); } } - 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 @@ -779,11 +684,11 @@ - (void)testEditBackdropFilters { if (i == 0) { auto filter2 = std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter2); continue; } - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -794,23 +699,21 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; + // 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"]); } } - 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 @@ -822,11 +725,11 @@ - (void)testEditBackdropFilters { if (i == 4) { auto filter2 = std::make_shared(2, 5, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter2); continue; } - stack2.PushBackdropFilter(filter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -837,23 +740,21 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; + // 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"]); } } - XCTAssertEqual(numberOfExpectedVisualEffectView, 5u); // Simulate editing all backdrop filters in the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -864,7 +765,7 @@ - (void)testEditBackdropFilters { for (int i = 0; i < 5; i++) { auto filter2 = std::make_shared(i, 2, flutter::DlTileMode::kClamp); - stack2.PushBackdropFilter(filter2, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(filter2); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -875,19 +776,18 @@ - (void)testEditBackdropFilters { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; - } + // 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"]); } - XCTAssertEqual(numberOfExpectedVisualEffectView, 5u); } - (void)testApplyBackdropFilterNotDlBlurImageFilter { @@ -930,7 +830,7 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { stack.PushTransform(screenScaleMatrix); // Push a dilate backdrop filter auto dilateFilter = std::make_shared(5, 2); - stack.PushBackdropFilter(dilateFilter, SkRect::MakeEmpty()); + stack.PushBackdropFilter(dilateFilter); auto embeddedViewParams = std::make_unique(screenScaleMatrix, SkSize::Make(10, 10), stack); @@ -939,19 +839,15 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { flutterPlatformViewsController->CompositeEmbeddedView(2); XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; - [mockFlutterView addSubview:childClippingView]; [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - NSUInteger numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if ([subview isKindOfClass:[UIVisualEffectView class]]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); + // No filters were added + XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); + // No new views were added + XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); // Simulate adding a non-DlBlurImageFilter in the middle of the stack (create a new mutators // stack) Create embedded view params @@ -963,11 +859,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { for (int i = 0; i < 5; i++) { if (i == 2) { - stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(dilateFilter); continue; } - stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(blurFilter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -978,19 +874,17 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; - } + // 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"]); } - 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 @@ -1000,11 +894,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { // Push backdrop filters and dilate filter for (int i = 0; i < 5; i++) { if (i == 0) { - stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(dilateFilter); continue; } - stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(blurFilter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -1015,19 +909,17 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; - } + // 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"]); } - 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 @@ -1037,11 +929,11 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { // Push backdrop filters and dilate filter for (int i = 0; i < 5; i++) { if (i == 4) { - stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(dilateFilter); continue; } - stack2.PushBackdropFilter(blurFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(blurFilter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -1052,19 +944,17 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - 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++; - } + // 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"]); } - XCTAssertEqual(numberOfExpectedVisualEffectView, 4u); // Simulate adding only non-DlBlurImageFilter to the stack (replace the mutators stack) // Update embedded view params, delete except screenScaleMatrix @@ -1073,7 +963,7 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { } // Push dilate filters for (int i = 0; i < 5; i++) { - stack2.PushBackdropFilter(dilateFilter, SkRect::MakeXYWH(0, 0, 10, 10)); + stack2.PushBackdropFilter(dilateFilter); } embeddedViewParams = std::make_unique(screenScaleMatrix, @@ -1084,45 +974,38 @@ - (void)testApplyBackdropFilterNotDlBlurImageFilter { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if ([subview isKindOfClass:[UIVisualEffectView class]]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); + // No filters were added + XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); + // No new views were added + XCTAssertEqual(0, (int)[gMockPlatformView.subviews count]); } -- (void)testApplyBackdropFilterCorrectAPI { - [PlatformViewFilter resetPreparation]; - // The gaussianBlur filter is extracted from UIVisualEffectView. - // Each test requires a new PlatformViewFilter +- (void)testApplyBackdropFilterAPIChanged { + NSArray* blurRadii = @[ @(1), @(5), @(10) ]; + + // The gaussianBlur filter is extracted once for each childClippingView. + // Each test requires a new childClippingView // Valid UIVisualEffectView API - UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc] + ChildClippingView* childClippingView1 = [[ChildClippingView alloc] init]; + childClippingView1.blurEffectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:visualEffectView]; - XCTAssertNotNil(platformViewFilter); -} + XCTAssertTrue([childClippingView1 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 UIVisualEffectView initialization + ChildClippingView* childClippingView2 = [[ChildClippingView alloc] init]; + childClippingView2.blurEffectView = [[UIVisualEffectView alloc] init]; + XCTAssertFalse([childClippingView2 applyBlurBackdropFilters:blurRadii]); + + // Invalid UIView + ChildClippingView* childClippingView3 = [[ChildClippingView alloc] init]; + childClippingView3.blurEffectView = [[UIView alloc] init]; + XCTAssertFalse([childClippingView3 applyBlurBackdropFilters:blurRadii]); -- (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter { - [PlatformViewFilter resetPreparation]; - UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] + // Invalid UIVisualEffectView API for "name" + UIVisualEffectView* editedUIVisualEffectView1 = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - NSArray* subviews = editedUIVisualEffectView.subviews; - for (UIView* view in subviews) { + NSArray* subviews1 = editedUIVisualEffectView1.subviews; + for (UIView* view in subviews1) { if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) { for (CIFilter* filter in view.layer.filters) { if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { @@ -1133,19 +1016,16 @@ - (void)testApplyBackdropFilterAPIChangedNoGaussianBlurFilter { break; } } - PlatformViewFilter* platformViewFilter = - [[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10) - blurRadius:5 - visualEffectView:editedUIVisualEffectView]; - XCTAssertNil(platformViewFilter); -} -- (void)testApplyBackdropFilterAPIChangedInvalidInputRadius { - [PlatformViewFilter resetPreparation]; - UIVisualEffectView* editedUIVisualEffectView = [[UIVisualEffectView alloc] + ChildClippingView* childClippingView4 = [[ChildClippingView alloc] init]; + childClippingView4.blurEffectView = editedUIVisualEffectView1; + XCTAssertFalse([childClippingView4 applyBlurBackdropFilters:blurRadii]); + + // Invalid UIVisualEffectView API for "inputRadius" + UIVisualEffectView* editedUIVisualEffectView2 = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; - NSArray* subviews = editedUIVisualEffectView.subviews; - for (UIView* view in subviews) { + NSArray* subviews2 = editedUIVisualEffectView2.subviews; + for (UIView* view in subviews2) { if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) { for (CIFilter* filter in view.layer.filters) { if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"]) { @@ -1157,28 +1037,9 @@ - (void)testApplyBackdropFilterAPIChangedInvalidInputRadius { } } - 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)); + ChildClippingView* childClippingView5 = [[ChildClippingView alloc] init]; + childClippingView5.blurEffectView = editedUIVisualEffectView2; + XCTAssertFalse([childClippingView5 applyBlurBackdropFilters:blurRadii]); } - (void)testCompositePlatformView { @@ -1281,8 +1142,7 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams)); flutterPlatformViewsController->PushVisitedPlatformView(2); auto filter = std::make_shared(5, 2, flutter::DlTileMode::kClamp); - flutterPlatformViewsController->PushFilterToVisitedPlatformViews(filter, - SkRect::MakeXYWH(0, 0, 10, 10)); + flutterPlatformViewsController->PushFilterToVisitedPlatformViews(filter); flutterPlatformViewsController->CompositeEmbeddedView(2); XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:[ChildClippingView class]]); ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview; @@ -1291,20 +1151,15 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { [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, 10, 10) - inputRadius:5]) { - numberOfExpectedVisualEffectView++; - } - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 1u); + // 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"]); // New frame, with no filter pushed. auto embeddedViewParams2 = @@ -1317,14 +1172,8 @@ - (void)testBackdropFilterCorrectlyPushedAndReset { [mockFlutterView setNeedsLayout]; [mockFlutterView layoutIfNeeded]; - numberOfExpectedVisualEffectView = 0; - for (UIView* subview in childClippingView.subviews) { - if (![subview isKindOfClass:[UIVisualEffectView class]]) { - continue; - } - numberOfExpectedVisualEffectView++; - } - XCTAssertEqual(numberOfExpectedVisualEffectView, 0u); + // No filter in this frame. + XCTAssertEqual(0, (int)[childClippingView.layer.filters count]); } - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { @@ -2234,31 +2083,4 @@ - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstRe 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 diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 06180a9d04c78..542f39b736f4f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -45,54 +45,18 @@ @end -// 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. +// The parent view handles clipping to its subviews. @interface ChildClippingView : UIView -// Applies blur backdrop filters to the ChildClippingView with blur values from -// filters. -- (void)applyBlurBackdropFilters:(NSMutableArray*)filters; +// 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; @end @@ -105,9 +69,6 @@ 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; @@ -236,8 +197,7 @@ class FlutterPlatformViewsController { long FindFirstResponderPlatformViewId(); // Pushes backdrop filter mutation to the mutator stack of each visited platform view. - void PushFilterToVisitedPlatformViews(std::shared_ptr filter, - const SkRect& filter_rect); + void PushFilterToVisitedPlatformViews(std::shared_ptr filter); // 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); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index 9f46bf854e75d..e3c3389080a7d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -55,165 +55,124 @@ 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); -} +} // namespace flutter -BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) { - const CGFloat epsilon = 0.01; - return radius1 - radius2 < epsilon; +@implementation ChildClippingView { + // A gaussianFilter from UIVisualEffectView that can be copied for new backdrop filters. + NSObject* _gaussianFilter; } -} // namespace flutter +// 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; +} -@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; +// 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. +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { + for (UIView* view in self.subviews) { + if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) { + return YES; } - 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; + return NO; } -+ (void)resetPreparation { - _preparedOnce = NO; - [_gaussianBlurFilter release]; - _gaussianBlurFilter = nil; - _indexOfBackdropView = -1; - _indexOfVisualEffectSubview = -1; -} +// 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; -+ (void)prepareOnce:(UIVisualEffectView*)visualEffectView { - if (_preparedOnce) { - return; - } - for (NSUInteger i = 0; i < visualEffectView.subviews.count; i++) { - UIView* view = visualEffectView.subviews[i]; + for (UIView* view in self.blurEffectView.subviews) { 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]; + 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; } } - } else if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) { - _indexOfVisualEffectSubview = i; + // 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; } } - _preparedOnce = YES; -} -+ (BOOL)isUIVisualEffectViewImplementationValid { - return _indexOfBackdropView > -1 && _indexOfVisualEffectSubview > -1 && _gaussianBlurFilter; + return gaussianFilter; } -- (void)dealloc { - [_backdropFilterView release]; - _backdropFilterView = nil; - - [super dealloc]; -} - -@end +- (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]; -@interface ChildClippingView () - -@property(retain, nonatomic) NSMutableArray* 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. -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { - for (UIView* view in self.subviews) { - if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) { - return YES; + if (!_gaussianFilter) { + FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to " + "access the gaussianBlur CAFilter."; + return NO; } } - return NO; -} -- (void)applyBlurBackdropFilters:(NSMutableArray*)filters { - BOOL needUpdateFilterViews = NO; - if (self.filters.count != filters.count) { - needUpdateFilterViews = YES; + BOOL newRadiusValues = NO; + + if ([blurRadii count] != [self.layer.filters count]) { + newRadiusValues = YES; } else { - 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; + for (NSUInteger i = 0; i < [blurRadii count]; i++) { + if ([self.layer.filters[i] valueForKey:@"inputRadius"] != blurRadii[i]) { + newRadiusValues = YES; + break; } } } - 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]; + + 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]; } + + self.layer.filters = newGaussianFilters; } + + return YES; } - (void)dealloc { - [_filters release]; - _filters = nil; + [_blurEffectView release]; + _blurEffectView = nil; + [_gaussianFilter release]; + _gaussianFilter = nil; [super dealloc]; } -- (NSMutableArray*)filters { - if (!_filters) { - _filters = [[[NSMutableArray alloc] init] retain]; - } - return _filters; -} - @end @interface FlutterClippingMaskView () - (fml::CFRef)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix; +- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect; @end @@ -253,7 +212,7 @@ - (void)drawRect:(CGRect)rect { } - (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix { - CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRect); + CGRect clipRect = [self getCGRectFromSkRect:clipSkRect]; CGPathRef path = CGPathCreateWithRect(clipRect, nil); paths_.push_back([self getTransformedPath:path matrix:matrix]); } @@ -270,7 +229,7 @@ - (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matri } case SkRRect::kOval_Type: case SkRRect::kSimple_Type: { - CGRect clipRect = flutter::GetCGRectFromSkRect(clipSkRRect.rect()); + CGRect clipRect = [self getCGRectFromSkRect:clipSkRRect.rect()]; pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(), clipSkRRect.getSimpleRadii().y(), nil); break; @@ -337,7 +296,7 @@ - (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix { SkPath::Iter iter(path, true); SkPoint pts[kMaxPointsInVerb]; SkPath::Verb verb = iter.next(pts); - SkPoint last_pt_from_last_verb = SkPoint::Make(0, 0); + SkPoint last_pt_from_last_verb; while (verb != SkPath::kDone_Verb) { if (verb == SkPath::kLine_Verb || verb == SkPath::kQuad_Verb || verb == SkPath::kConic_Verb || verb == SkPath::kCubic_Verb) { @@ -394,4 +353,9 @@ - (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix { return fml::CFRef(transformedPath); } +- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect { + return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft, + clipSkRect.fBottom - clipSkRect.fTop); +} + @end diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.h b/shell/platform/darwin/ios/ios_external_view_embedder.h index 8779f936db838..03cec910bae39 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.h +++ b/shell/platform/darwin/ios/ios_external_view_embedder.h @@ -69,8 +69,7 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder { // |ExternalViewEmbedder| void PushFilterToVisitedPlatformViews( - std::shared_ptr filter, - const SkRect& filter_rect) override; + std::shared_ptr filter) override; // |ExternalViewEmbedder| void PushVisitedPlatformView(int64_t view_id) override; diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm index 3222bf2a096c3..76995be2c56ad 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.mm +++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -100,9 +100,8 @@ // |ExternalViewEmbedder| void IOSExternalViewEmbedder::PushFilterToVisitedPlatformViews( - std::shared_ptr filter, - const SkRect& filter_rect) { - platform_views_controller_->PushFilterToVisitedPlatformViews(filter, filter_rect); + std::shared_ptr filter) { + platform_views_controller_->PushFilterToVisitedPlatformViews(filter); } // |ExternalViewEmbedder| diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_two_platform_views_with_other_backdrop_filter_iPhone 8_13.0_simulator.png index 8fbee0e9eac2caefa834bfeefd9d9bc9a76d126a..307864c09a863c7b3b8e696d7c7c63a76968ab25 100644 GIT binary patch literal 49083 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

o|MZ2cpLm>2Wo(}Rn z?Nj&FdZEkHy50q7kFwUdu8NLW0g?B|J8C&>LV5lRO!-BTM({U>7txu(r&2@1oo3Ka z$-SSmPUGceclWE4saEvPSsl#`z9~{SACAQ~nbJ0&^c0y|Z1q`2fk=^!Z?+EWZs%x` z{0VP6-*&Rn@_$(tX1;H`%%)4xbIQ&KU!cJ9Xw`eHlT94GD)dinMXav}rCM5h&S)Zl z#g#IF{P2Hr3t|4EEZPIZ?EDc%^KiCR+WVZW@13W>Y);jkxO5G-}e&L{$JEB~po zue+|vO;^Y5rOrD{%p^=jp+p#et1k2*Qr{9`>!mNPRRwMKsiol8JrQkl&a}GA>J<>F z%MW5dZ`_Z2lu6H5c!cL9V|O~0MxK0?j98c7khi9FRH2#{V5jKPVR`DU)1GIt?tyQz z`!HBQsUB~=y?rC}LvDLjp!;vytIQH>{4iq7JjwtAA%v}>jWZAr`OVxJC~_>oiTQ&G z52>xAnrpM{xbaoONxb7G>{Jsuga@;ySo?r1u__BJepifB~Y_x`|Fx`|0&^i>pX zRHs39pNseDxwXb)qwGOsf2w2<;0QZr*j8J4sfE$}z|b1+erhT^LNY`@8H|%pW|U0S zA9JR0K>oE)X`8-EP1qgkLpCKV3fU0aO7#|+BLi6RZL1N%CZmWmX#il2oH@j@L&3CO zyS8mD#Y7G4)Oz#}5$QK@$NjM<<~OphrX0GRMgWqc+|oHQIe~`Km#&ymU)B4zMVi0Q zSY&v9UP)}}o?0piP~EcYi#fa&Nj3LqFghRhyC9mUz3KWTzD@RufE@dq@Nw}qjF*Y8 zo>UFspA9-2q!lk%6k+0%(PI3ArOcW4ZzOMKLT^Ul3Nvf?E*0I$XH~($WHSv7E~7^A zZ5x|c)wxYcGX>VPDpH)2U{^5>n|uu@E>TF74g@|a>6i)AmaEnj$n9R|!|S7(0YT(z zjs2N6_nF=({=Dwsn6VUerwvsrur#=V)@I#0VF^V>*9_VC+3v zJf$^I0NsFov}&qwK;Wzz54588UPIaF_7|}e9TVRt4kRKH2PPdzFJEf+CP$CzA4PZd zKEAu$j}%pC$bmH;$pN&8fPyH-g5bKVM z7s+++`FGW&UtbXJIhDA{;k~#P#)G*Y+;tET#JAj^-FHZ~H=7U)fNMcK+$tHXr+>NXs&&Zqq#6 zvv3qB{rHdch{Db;*op=gHA8v4^*55e8fI}ZU6sraoKAjcH#f7o%?yr;++~SiB5a5- zX>t#(Iz8WIkl(T6EhcG)BAhw|)+uui=a0Kv?U;uU`&C?;UFZ56qc!=j+`2YiCt${g zYN?V2vK~Q`CAB&JfIU-zun&meCyb&+C8h$@yLcEgg!9SvqV;2s(6L(q7=)06XvxwH z%>SyV#-|wP=k30uMj{XYt>@7&@vuMhVhQ~pquY);K09e$=lB!!e)nXUR~< z?U`h4|1BuJK?8Wc3BUIz#-pCG8;v?P8;YL$TN}Aancv_Qggc|25ypXY{MudIerezf z;XikDBSqbgU76%cznK-GuhGkj7ZXJAsp_ph5hH!O3YY^kK9G@a`2+ zq-`qcZuQmah2UuFmGQx;!$Z_uP(;gT-UfQL7Ot>PnYSLL{P>U{zVqcOwh z_;-4V#re$Zd$qrQQHK2pZ@9UV8&cKj2N-Q$uKFGGmZ$Aq7dO~mF(dV!K_u+@$O(p(g!@KiJ-jq2iUc0ElI8&5$tebR{KE!<%(Znb`yd`!7|*mfdZ z$lr;7Ck(DcG{BKGEqjBv2F6c(!+aZ-=y&+=HclaL*tUAI*!bo)fY={0D60@fJk2IL z0@#dKwR@@4*yDf*Gz4D#L9dIbNpbCd@{l28a%1F4!g7#hlvB|d%#8%h9b0Ow<#Z&0 zoeO4}QZ-dGQ-R*UyY8uKJy@#WbQ>m({A1N=NVrItZ zHTNM>mGpkVNTgAGimka zZhhU_*HCEXuUf6H_hmHWq5_)*VDVl@OReE{B@of4!Kmd^X&hn)zJ#$&_~i*I5nK5}nDzv7Pef zfydb{H#tK%&djo3qKcc}2S_-73{--Fw=qx$Af%QyWS^`O2U+Im12} zxz4#)P=P?ER}9j6q#nSYn6PELi#rww{H)m23*#$n49u^hxCNS{{Arc}WnDB`l)>$f z!l(vw0W6Lc?;1ee@7{yLlB$y`w3?I{57nc50kdi^{d&uR*eWGrh2^VH6^h)o3yMHY z1x89jAG?{#m>$VR%^K1VO*^OlVqJ5YmEUgD7josRKYf_I@i70g$5w>=uAAIf4z<6b z220MDOS<#NVsUzX%cCzT!m*b88=4;G9A;l_rv(ZEZDGg;TQQdt5M}(+eE2uus-87< zD}SGoskGT#JvY*xdx2*!Z2%dS__U5ZS-Z1Wz>_2!C&t~cm&ghad*Di59)%ZtrBM$` zzq|rR@~*TVx;-plhgwk*KN_ZJX0nOK~EN z(?$|?FsG$tO10FMpXaYUk<6uNJsrjM(|NHHSH(CdBET3BaJ?A-xg76pLGX7KiS0r50AlF`2d12#_=NhJefx#8$pi03mq(x}&Ldwb zjS{EzzW!Rtb1ix#0FcDTsy$dF^;$@^QCRGk~Fz{k!Uxw&7K|i7nQPU4`lt zuYaUYIU_>ETCCYLSArAd{9+Zv9R;kQ-z8k+b{BPc3Df%CbItWDSst!`v$BmkpL{Ea zI4|{C;WPBQa=Azs$lV8}UKZ;{_mX7hHG*J4{o(?iO`)D+C|=(9E?aVp)Ow0y@O#h( zDywq%%k^D;uuF`#E@h(XS&Y8~iV9prhbbB@QXrpNE1m>;JzX|=6iwe1T+ zq24@%AJc10+XoPb2S+?Pyj8*J5=AHYqk%Xv90;O>hbK*xI-y-$Wd~AT@d+x9ce|K$LNwE>Fi{8*zq(!V;wy~)!4&nhMSi}p!6E!H* zxT*b=r$J22i#)Ve^=vu3O~IQgulVHYA!jAB9B#UTc&02a%!TQ1jnt@&zoOs8_zcvx zE1$z>>%GjFR7dLLMO@Qar{sN+lH|RgAiF#@?edl$m!HH}3$++|Il|QGS5s<}Ut>BN0gXUR-@yP59` zv;Zo+rABS9aYE(Ma$5Y1OIHm3un}*%h<&PW;xPD_X4ZS0d*905>x+F#+&#?Al0^|j zI4U#Aqe9#Xp2~!f;Uo=cCDhc@h(5Q=Sv*yjzGXT1+gno(d7@YG-g{^P-f$dQjaM`a;k^cpc-!|>?JhG7Sn<+p;@U9s z5E33p$S@?OR8l;Dk=m{K$oi2xr?{L31=FFGe;v3JG_pjcRSr4-JgFQ2I_-~Bo+w)7 zi6mv>?f-F^t3ENNd;FctTV{j3%*5tw^v^1|+nsF6ve0&3OPP}wau8GvRf+X_zoy)9 z3NFV0oTj8IRkwIWeL}Fi$gQ{l`Rfwz)-cD^qY>6ySV}r0*wXl=4@}tPAkce2loMB^ zL`^C`RJrtm;%iMIg&LmFLrA^3zvQ_dt8g?3oG@q&6B;EB33KPx;{Vh#Dd!!uz5}r4 zEGYDzA%@-T&VxbeYBCY`;n}--ns^6>VEjJT(h=R?0i;FIO5{kejOAA=Ucyo3;Ao}R z`H2Jaow8RZ3{>$NEwRywa;(|OXIQ>2`SK0e;De+v=xOh^7bO>Z2v^}bZ?J}ZFD;`M zTk_9~d7kDie;)-EeC9=FN{)uDd;CA|0RhBZ!!_O7Uzw)vRs|b{QQ>11{07fiy7ONB zQ$%r#9q=~7HKg>7r@7= z-TNRRDlWVyW9G?-xfiooE^5ys!2G?nW$JS#W=mi*Zg6JBJGrPU5(+Ls*PNv2Fhgtv z=4=@0`##z>ES0qCjU-+CF_>ujMyjphaq1tAQji%1-F~Bgzp}x36v8d{Vs(mXO6~!7 zDtvitBr;v^@NkYaN+IfeN(K@&3DQ~Bf`NWDGE#gTZ{LvH;ZW+jjG!VEUXR*s5D~_d zx2>RfXDa{#4P5Qlm4NU%x|I`3SK3Gi@}%p2Tzae;SUN(KU#m*$)EIhMy7mayvp)GP z0JmLyo_{7weNiE*zI=4Itc_|GfFWJZM=zK$<@D5>L#2$7zy0#m&arV7AW=m*?Ly4V z@y&2P$oau>jkuu}Kub%hZt#o>JUmbgteOageh3fnmc$-noq@?KOoMWGiBf%=2quTg z{FPWs%;vAZ$B8~#wjn3% zi$lo25UWd6vIa1ss2cw4Duj^js9iFi1OXS@qXfT6s~nb}R_Qdar=&-9vP(dUUWGVi z?v=6y@zJ(l9ZGo}kFU4Yywh-_+=t1a(nMBvjOEN_xQq0cy$Rlo{L+WpQ3M>h7j<;i3(W`3F zLERmnZ$*)V>$@B>QuP8D$#YdR{hkBuJn@X_VqWMNaM2gUJB)vpopp||+g4lP^S)Gx zsm|bT;pAE24F6JlCMRq+sboWfr8!!Bn&w!p%E0sfEs7X-^F-P104+LoBU+eyqK>x* zI(Yq*9QT!+&0?8cuI`4MvYm9yi;Gb!X(2S-*qs)aQYZnVE5TsBU^=H z40>5km2TVn0#)5N5~AXa+`NO&Jq+QThA?9Xf90$o2UG@z`b~^9sx#ZgrdE3eNBvX_ zJN^4x0Op{>w!sOSTm@D$R^r16d)gi=%gVEYG%fnq8p2o=nW1q_LadT*3)~+}rG-uG z#rA-Lsqc+)0lP{URo;xJMeI9A4$GVurKz)Umc4w0(80j#s}Rw)ex>P)U)rn_G2O?n zzM|I1=YOCgvXLbEbf`cWQ|+)RHIU*!^$UH z6YS#5l8&!Q$%3a%T})iRdb5lBgAI8`<7{(_g+w8e0c_)ff4Hu4)b7|OfGg!pgw&$~ z%6{>Wc3*ML+h}wz>tQcx5(IK#%Gip*yqWj7J(A10Zmv=)sVcw+nr3FMkUsRelLj6M z8~TgS=jj52T%+z+dCB*gOz4Wo7T5_5G=ix1^0=y8A79MjrwQ$+NO+D}aubm$AzL<- z6qUD+Jm2L?Q5s>ZnDOKmFWLrr;vvTtENZCk$XC>EW$jhNJcl((Ouo`V%j_3=R&M2| z)VaL;EqoQ{Csd=A`27@QaG@lIsOR7VR6`_n>xh7uOv8HHp0~2(+HTpaNJ7e$m!3`F zGm7WDV_vrQ3dv&y*?&z%06K@{shfkQop_(Oncl)l=0jPm2~IY>zLxOCU+oH((|YVDJv;~F znAgD*2O;^pJdR%vt;01&cUEqWbNjEnGED3NV#KNp;7>)Y(pnXKDH}BxPpWfIc8u=+} zXs!ITO%xOafcw-^9qx~hD?bP12B{F$jki0n{L-`}iT~6d1ZltWx=42j?mLb2O8s5$ z^0Rsn0La}jxm>60;1R{Xq^UVSgl;nYX&BK%aUI4_1Y=wjJ7r4cre@{9MfmWil51yD z_RUj#!y5-FtgoX`hO(tmc69P#x6QST=;<%pch)0QvpF{O?EnRV-0+7(LNpA|``+mt z_O_0+Y~-Heb$zpv=q`vuu7O9B)fNO(!$(a8%iEGEdib<7`HgmD;{ERIWAYwqe>J{Z zm_!f@`s0~^$Cs4$PE)UuSW_?ga@OLL_1O~AFn|}T+XSf=Hpy|JfrL$WD!)Se|(Q79lI6H_X(cxe)da*goP&dFIxAUHy1 z1w%}3?M@wJNKa*o$BqKIr0$fGB&ZIc|M){Px_Jw}S!o%^UlRbRVAHDL;=_2Nn1PG* zUnG=~rqY&Iy3CK~9Jpu?6qWgawtrO_+~{Cui$>p8bug6+ixpjM*>J6@>Iq{F6+63@?(y#;!~d5 zA(!uAfAUZp+!pS+O994ls1)!bMcZ2mgL1?ZH+y)M^{t%QmVAKnOPfUd-JFG>S&wH8 zaQ!#6Ts$L>l+vsfT$-!kDc-{Gh%n6CzU>yx5T+=k4did zV;l%7^l4BK%NCO^xu36ILCD^S@%8zb!_zyYz?aQ?#n$*5gD}y4_|v_pzRA8TRcEft zfjDJ9kl^EGh%P;YAj18MSpsW_R^F35UW07_`iPVIYv0T^nLt9u2UW#Y(lF?|eI3E| zaqjzHw3LeY9}o?)PzHOr1i_OYDLA>(KS=sY!e)!|12I2C6qzi7u3eBYzt%=r*}eRR zayU%;qp)iW#R1gINcjp2em1*U?mK7vT!b1APJ|uq&-Dqv_YDm+S( zUE~TROgSg2Q6eH~8#a91ED)*QuP4YG0Upt(*aq<@yQQtV_Y;p)P{*^b|E=qytR?u1Rh@RlO5Lxl&o<`w(^P z>@brrR}ZI8BCXCZ2 z^5+cq(@4!yh9YK;+3E**Pn0MXv#^n{t>RltL8+H_^=Uhbr#Ld1Y_zAvWT%))UZsF) zWn-(AcE^=k1Xe;xJ-$^1NsLGOgQuoT26?b~ejT~O_g=!|<>LC@C~o7wXHF8xBBHbt zfb>=B8mH7j%a(XFYCa2L@Qu>lZM&IoMy1iM!-BegVi-&cR6)~LSztA2SbK=OGN-Px@Iw$`uMsh7AV{i_F@kZu9uG>=W^vk5uX(l4LmQCcicYu)7!* zClom{3vw?FYFY+i9Z#ERr9|ZmBP}E&WwoWL$tdn-@77|1{E= zYoRbwfnQOOCj*IJ4^=uq|e-F;PQAN>?bc*B4$tljX% zXoK#QfJloiwB@_s)#)>Fxo6YT1o&57A-nx1m^8hroZr~zS!YMiWf#>gWqa2j6{~2( z=qHkJ(UNjzhtEvG$Spzi&Aex?sD_bV!AJdz-OCiaEWhSw2 z9`#!IqeqLfzPoW}gx&{R8%CNM#sO68 zyc;e`q8zZ%)fMc#c5UyE1l^V5c$2{}3G#aCHwVx0*TM&cmOmes2~1>0j(?|SEbdz2 z!LeH&w^Js|!<-iCKB3KqXBBT7&dMfu0HUQX#}aNF@QIjwxh z+QCoWG+s`}J4^2GNX59FUI+Kd4rJCk1`?BP?5P5VuP>fN(J#%=iuJNO3%{r)`{4D< zcD}2vXK7vK(bS(serFU+m?GyMHw%|~oTKQM7P%=wK5_M-P$?Eq+Q#CE>QsZn`fCyms*RogJ5JNiT+tG*+-Qz%H}r-zuWleW)* z_{U1}uWB?j(f!7KXQxM}6HFg{7-U{c%hE)6E+&>!a~7oD2}%VOWL-*wj+mlPsPi^oaYm*x$8j{s5&0)UVjgz7=tCmF{FX zu>25%oJE))**ucR*qL~m+YSA}NAd_3X-1WI9kA-qU~&8aPa5>g$bJE`gBLM~Ywid0xn;GsVm86T<-!lM zCQ8JU5QDU5W`ZF^Z^P1s@&li87Kw_#$!dbT=ve##q5^cNALV%qQLIpU7NPdI7OxYe zfvJR?sf4~Zu}`$$K)x0LcGJb>qD;@ z|13}&C!=7B$y}`u4u%6qu|19K4i8PG)4B;GpIApm`-&+J{@ene>RGz3%j-p=pFfqT zn{7JiHQX*@(v+7NX130I9LF-4{oB6kmW5EQ)}H>p>rebIta@Sy1N7K94AqxvReBzb zM%q0dAxzmYT&nHeAVqCy*h7=sHCibyPHl>o^%gq$N@GR+99&hwN3sg+bPuU%<4r3S z5PJHR?zRO@hJ_Bb1c_Qo@^?@>uA3XOjEn6`gph;bwZLEe5a=s+i0~{%9@G5Ir_CS7 zAFmh00k1G^)$dW+HA_v-NuL<&;0#+3BEj!#is{{1uJ$f8p>gX465!E7LpU>3{Uw=O^;0Qt@OI7Q}}Ss8zu+xKr?j30yQ zi54jA=CCLOP#xs0LZ|>09hg_K8)0cZu9RDJCS3aC`%@aN@Z*-}a`rnw!b_1A_i35H z@i;4=A9Fq~x;DsZr-0pJajV9n58dnES?8GFp?@Hpx|Ne4oGn)IE%Vx~OPiTydwhX1 zomOkq-IYR`D;DMuR+^&I`c4e)#&kA4KZx#mPLLJyn>T22wtheye+qoCvAVisQqZfq z$|2Rgc^WG>L-4yN_66Whqg#)A0#fc=a z^yAE?kXx_VYxzPI`>U*ybAu>X*Ye0hQ_R&j(k(Z^Dl21`r?7FL^u28guCu+aptT@U|cWD>4TElWX!<}Wq&!|H7(YAwQ;XD8*7-y6_{TXFh z8(*H>?gy!{l(KGYf9C+?FNJ{Nwgb*Q2ANjHhS^62xxeFs+DUTq>`={AEB_yR?-|xq z)Af%ka#Q415L84EM5-bn0Vz@i1nE))=|~B^MtX@TiYUE@DlJG?dMF8s(tGGFQbO;9 zl7x`6@hR_n{m=PyKAr1a_qXi5C$nd-nOXI>)_fA(Y}kE*PWqFn*8&GNk=Cvr{u)i9 zJ^vupB->Y6P7oElm_dKu0a4AS7UHzycERWJTye1rSUkTe+iCZr@6jzSQ_QO{pV`+xB>DGVLs&EUq(eMe35eObxcC+4lVKfk(38S(S#AN%;3+28)Qu-X64OxTsc^Caln4*v&U> zHX{A>l|9vjmjV14U~v6<$xyHGGR8mKdRZw+tl>L2SNvh3X`bvf?nLsv<{zV9oNp&& z|GL9DiYbPHubFQPRSbD%7hhOhD5El`X0(>Eh{Vj8-xqTwhY&uW2!CrPrcKqYy1HQu z{M~dB?mW;iaemdpS_F!3*yI)yZ9&b1l-?>EfE(11jwll2p`y9icJ(7o&)>-l8rJcB zGUf=kaaxJxWsks%I_Fe$fo<^w@GV*lF9f}l$h%@-of9__7&Cv<7c$`HN0x=cd#M%ZDc@e3AK>BPI)x40Mm0_G;BHT|TRRXV zMN&uEL?p?Erd%wdbIOG(k2R&sVO#lE2}p%!+tXGcsN<=inwo5y0Nd7uKgnTZe1Kw> zEhXrtXvOwR!$Is^;O+jFNLLju-^lr!2&@SE5BczRWkNU3B9tX+o+VXRbAFTlN_tUC zr1E1(l^f@~auANvk*1LPQ zcnSe#kfr#kvXLwHTWX|=GYAU$e)T)W^`aosM4_W@+8VK zGO_P_4k5$Zep$OaG}Q8@8rYYe)QCG)z)SeXnt~_KyzW*MU;ZXDZlx+vs(@q$jv(Wo zW3V^hW7MpXi>zPUCkp6A{Gi9WlD&lnu%m4Im_bVP@?NStfFTamC}c~BZBP8U=pi(N zXcI7X*D~g+J{onGndf(WFk1hDG9f|r@J6G?O80M(Y%>L7d@3&Wdc0apgkgLDB^bAN zdh-aVT?c3^?0~``bMF0yH23G1H7dB5WtwFtuDev!+@Qai829tk81udWg$Wt+v_fIm z;|D4A-7a@hUUf!K@&8UqbN$A`GjFx9D{GfKS*sWJ@~Jh*c8FW$`n;Bf9>i%?C`VY{ zDK1rNu!fgH{GR+|B&O%#SMR(s`aj5_|3(}DK#ZWGHK^c0#Ap@2x#;~Y+rjY@Z7y8O z2PKE=YK%-<))8^Ni}zSGdggCwx`XcJg%r;l>o1B_vQYCAWUFH}54kV8@9JU8#aU_I z>fSk;6Jd!kZnM#|yU#C3WfOxR#Ob?^rn!mdwGX#bJ-WBhobD`072{VUgs|B-Q{ByA zfL>ttTg>+*#m$a~v6JlY2WJ)<{E0^bow%PwVt!v8{TBef>Q-d){SpN$T8xin|2qMo2~#kWA2KPsHSwr5I^ zs>>*UFsXDS@@BELtOLrj~m%%thR;>`pctPmvZHX<<>7I?GkR3XwtWZ zRj+D$r!REfPzC-v%Po?`|4Y~3JgIM4ClP?%@TRI~ZMfC4WPn2y@`2r&4(wL9H<>EJ z0Q@88o{LGsLU1Hb_5ss{rZ@a}TED%dev4V@08RBs?O$&&)d{+GOZ;JJ0s@_e8zN;p z9z*7OAx#ED?dG0AuL3OJ1y9cBR_|90g0CUJ4?k4RS|h1*@+fhGucl;2Dssi(Qz4oG z8Yx1%hT;op`l%+LCJKx#FaBDU=43vbp+i6%Z?d3&)X*$Gt@t+5ZTQ0`E2pbr*@V`{zd7*sf&f3Pmr8WSP`iR-lVwbpjCI(y|h&EOq^%}cN;qsF~fZCZbj0! z_MeLep&k;yX|ZiN0w_V@AcstuB>GhMpHmA{+k;YaCjli&c~tP%Qgn@Q|0z0)x&g$e?sAkr z_?tW2qFKY3O(O0ijqOCP>r7(eSuar0uj z@*UyAjyG~ZEXu~Is)+{FM1@-vIeC%qlqct6)20Wf5P<=gcO0UknWt@)bYG5&Z;kxW zvz`V^q^>A#L#xxZ>iT2+hi!gn$W-Jv7bXl@*^17Z#DY^z9)-}?!jr9YB8?X74>mM+ zruSBLCC)@5AK?1Sij1W5(NM@M6;zhReJB|;TH@r3HRTsh%KZgSJX9X4@29vPUEl4- zsGI&AZu*Jq`eeIuqquU+s6scO9SS{JZIgWmzzU>*!0`NgF+$^bB7ArERx0QWp(*XLD!d_1c zup!=Y$5*BlHyyAHPI=Oy8C=mg-3P1J9|90@0OYb)W&bw`qHKFP+%I*^FFPICbT^k- zUBh9|^_j^#CQjJ2qC@4A63vb0RY$PYsm}7Y9Kpv#&vizoi*_R&*91kF67@A)iz=Fb zvIFtdS~>7bQI=vy_Pie!cg@%rOuMgh-AkBpAict9JB|l&0lescL`9w8`KTK6t87yr zmTYN17A>+?9enq(OoHSy-?N_b*sWI=?*4g)J%KCuSlKoHMsQ|AsrB)fW-yqY5{dB`P9fA5@t}M=b5g34ucvm>L@9ze zMIJWV#uP~i+0_ho%?<0lg8$t{+L&4T13zH}X3y>9oI?l~0J(U0xO9%~ULMz_{#~x5 zCl`EtnKL8zVH{XOiRPW4ADkCeEnOan({9RZS)y&^g4B{&T;c8DwBEl@^Hmllx>Ja- zTOUUubTOXm&Gb3XoFwY}TI1xOsBn0sl=x!#U2`%|~g}vOqzz^OweL zOq=NsZ`ha}!v19MNB|1m^vRWhP&%f8bN3ye6(}jH-i$GIY1{PnjfiLQ_z-5P9+l@8 zwRP?N>f^YWy&JvVixMvFXEG+8K@6R!3mcc_hKQl_;U;LoYp$w%F=>Y2o|o8$j1N@a zpN8gdpzPEF%X}l~<5`$bOpK|5VLWY|u*B3wGkI01kJWvTh^4Z&6vw&#L~cJwewi%c}Qrs)e$O2-R3v5Li_0yqev4u5BH zPkf z5m!HdP+Pont$(}P@fP)y(kte?a<9?{+4S$H4c18?v_#s7rdc^{JH{jZlD-kZN#CA4 z34%Y7yS`a`fG4TDnPct^Jh^8LAbr@AV0@`VCg7aipZf1zCry)Ism*+F>Jv2VJT8y= zu{FPyG>~yl^{nhk=+qg$pgk+Oj{Eaqo#`7cF+EEENB{s-RcFD;a_x&qBmE-#WD02T zQcIO6pbV!u>RG8!Ed&=uR=0(+6!m;3V75e>zb}cn`rNg7^SlQ*1tf;uEfzZPAr0^$Kkb3!V96=q)R~`wb=6p zJ`}?4+|!3>M)%@`1LM2ykCG3!svGGiZeHzTXVM~~oq-7E{od8V&u=Gq9J{PUcO6lq zK&A}yvuwyGXV)ju?fE~4O)GwJiXXK%3*luK0Vpq}Pd55wgQ{ekdEGS3Cg>&!i?gT4-WZ+~GB+3?mO!%2Z zwJx?DU(&)G0-(D4i7d;h8yDS-h)$D(w^t3E!mrv!ci}E8C)#Cz@0G1ah+il`a3^hK z(u=H_PG`D_nhM86e9?s7PFi94gD3mS2MF`5EUMs%*RMBzDtYo+4=s*^L85OM7I?FF zSpD1XvUi8ow6HJ4YHB}-a<+3qM259}f=+p{z@lWAbd!p{^svRp`Lh*Avf2IJlUNxx zA^G=0YSc&kg8g;&g!6?vsRpq6iKbv`o4<>{PyYV)jOUE#pMV7^^gjM0Ad9%Fo9FU- z7so`OFev_dMTdx~u6vow2TmK}!y%oe!e-x>1()uZy#s|a0RX>m?GD*=c30zRV^+lz z%6f-?un&Lv_2Yd`u=pCu(}35Y4Wc3|zR)UFCDSI`-)tn(E+>ImRbp3q3Jy^Om^+X* z11vqf*~-W}+NW38D_QVwlly)KrtcNR6DDNa-Ub8ZnA`}@JmVrgf(}iS_aJuGHST42 z;O$c1uj;b62EQrF1vz!@es+*Fm2oh78J}UL#{tLyZ8UGHm(ShgCdmi-NIh^u;nFJi zEy7;fx0^C;jA-SqlHdg8S8cR^Dm)yX`P28&IqoUkpF{Eay&b%JdHeH94lWN6(3IWO zS{LjL?roVOM*DZm`wh-tPw4ED&#K7>Z4a@{JTt0(Q= zgVjERtJ*pF6eW{XsE2I8aq3eae2AdT(Q?6iN1XgKMv9j2TRVJNST&zmze)|rf1v57 z=bwH_C44X`C~7s!w;-DgCLl1dkW))dsPdG2>S?|yi4VO-0V{+%pvJ~D;PU8=h+m~35bYFzSXD;ZK&dWuGZ@IY5UzAJQ^)ObXW+7mg0QJTHjSfwOD4uF`018 z`qZ`#nnM#EQ@F~9#@(&!N9Su{ymPhm2MMl4n>F7imr1!GVvrEd$&`YAJn~c~$=>fK z|Dj32x_4t1-_We0sDl=zRRdD_QQclDShToF_}H~iMB!{%tK+nw&=(xHw7 z!HgwNr^T+1aTcW86~7oI*go&&@IcFQEgrd4 zH*TPYwDK>KN_NeS}8_ApHZ$;h*J;nt-fA+Qi-uKOAmc zkDjy-gZDCct46f`N)}0%s-k>)Ala61t+=QX_S(JRX*Nysvs}Za%P*6R_1m6lUEe8M zl5H@9A&g|HO>4d1s`04x0F+f#)12Pavivx3vWJs^c{i*ojhiog>DglMWRec+lDTr@ zm*t(%i=8V=_hcWnD>~fgcjT+RPGYYK>s=bKntyBSqWD}#+(0C*&0sQTn4cpgTla_& z|M8mrb!OHWS9oJLZN#AKL~Lcj)1$G}Eq`WG(K*1l7=xn%22HX}Cn_{i1mL1I5e<<- zCa#|mH~vdcv;1QMRD@zLvbfvPS&IUF6nOmo;cbgJsA){jR2{cEYj0JY~TDmydPb6e|{OWZ)@5LBA9Nw7*4OsjvI8L)gvmLnbY zNSISDkqqB-O>b90`tcu{0-4b9J3u&R^=_iYCf-%TjPCijZp(s9J5HWaP3V3|;|BxC;`xwj&p8k1Z|g=n%s zIv#??SZ2%vg+%L_0g=}%bC=*ROHp)1FeukB_=dca!%4hYhsG<(Ni_{qJD?;=uGZM! zw6~<%pFf#=DE;u>hx9EZIX>G-kFW}YAETN6njVM%B8c?jm@Ky5+w=uuf3 z3DFE;YGti8h2C?~KSE2Jv6%AMKJeWXnct&uds$Mb(abO|0h~;7ND5k+k)wG^GaMJ` zr(FM&lWE!Zk=LtHJPQDm?C2<&eDeHlG-=P+XlNg&?*z*BoeX}q>vp`{S%4n~x%Jc#bwZXC7(+Seu zf%7GQPdX8LBQ+5Jt8Jw_C?~zV%R3c0?_(bzG^*v$hY>okmTCL26`bbMNUhb&FfIt- zw(4TuM{lMmjJ=DcJ*v!rYd`csWqR<7PEe z;&x+337~hB0Qye(`?U8K%%RVH)y8(OKA)1`5KB%CKHqe7B#^0uepv`n*P{Z~3nyR@ zzu$2rJZE}379k-!;-e~w>s7zGwEd+OGxK}~?snKHysQZL^)CX$7EplG(lqf?5G>xH z9+Hxny)|NHBi#(^P8_ai175y50BkZHFXyWCdyD2c@XmHQP@Ctg;a}eX@YbtK=Y{_gbL$sl7sPnrWs`fCc6v)d^VvK?vkp8w}jQ zrtS2mg$KZJr{t>vRq?)4UWE`e4jk&u6)HEBJ zuN6Uo+w(O9>W|KG@LM$hb25z+EkF0ZZy{>O$sOvsp!l7&idj=Kti|;Vj*it?X8_jZ zeFkG0?&bvNxK;r8;-Lg^ic527G0LsiuagOk@y0TI!Kd2-mgk$#{on)=vjHFgaMn?H z%PcKF#rLnxeorZW*!SE|^`Hr5J;OWDWWzZkJcgM-CIKL1-yK@yRDz2%TB=x%!)XLMh$}>E#(Ugp+ldt@2n_rs58)ExmtSd zZ4Z0ORP%tv(DTbzBs1Q$JOl*L>9 zG)xoisrJq~Ct?geJym#l~|5{ShJfToYQbgm%(KYEy zxdK1ncWGn*=!HMSCBEA`X|yZ)zhCfI5$F{@9G|p2SHJqYgO|P>RSPrW?JODB2XMoMj?~-qcy`gfG4lIW$N_4h~6Z!s%m;d8qD`{XJd|ZmoQpNC;FAj-&*&@F6fxyizjw`qRTO9Hw zaHG{l@bHhyR>!#ZV8W=tzhEzB;zq+6*FT~SAkiUe?(eemr(x% zc##2Ou{g#8B@S+eT4tQyz2C>V(+-DvYm0ONJ^pv!{dpOHY5_73CfGzS0gr@JTR75A zH|dblZkH&0fn%l+pW2oAI6Yh`paBHcF}FRrDI4A`xopi??_-`n?>SiIJ6)Cb^<=5BnQq(GtCi-k|8aT zya^_1MTJjQ56X*3HA+C@<Z>C=i>#p7 zTZW@h!P3=$|1SdoX-m;Dy#ts%?F!l@Lg9WE^7RfUijJ2}@%MXRxU4@49ciYi&o8B3 z@29|a;&~@KYxm*q3i>IX&D+Hy%>NhEg^PjrU7Z+(_vhUCEukP$!wneM;x-g%}{_Ur`cCz!&;(gPTFvo<{yr{GnH;ZsxhI! z>`|OyqhCK}q+oc#_4!Ffg~zio!Kz8$&1|Y=P7hJwue}542b5chua}H1MjBzSsx$>> zR#DFzNa{3A__?HM{yG3qVwTtAdd|2m>&iAaU?LK&uhRi;v3){ADPdX#x>33YVb1HO# z^3~gh-jv{&>06W*|0y(ZoWk{=sHUS~kNtfXf1p#f0}gwpymN$nPDudF3n}u5)xoJO#Vs z6kv(Sd}^VqVgbsxkZ;!Cp!g3QOYyhem9M=`ub;Pspcr>kQx5tQ8vXIW%%zYc5lS>b zk#@STEGWx}0}-R+F-0bZ`ul*auaqgeL{X9QdUhi(#tMPf>DR9t@t>{01(5Mx%SUGr z{pCVc4Chxsk0h*+oeAz+E3R5=#g9(9P`E%SIeO&^W!4!ik>dUthg)VCXg?A7;mM52 zDbp@42@?J;6_W(|A1&!IPV_?B=o`2Jj z)AvQAbZ4e@;WIr$JC~#ciE!{BqsbIe16@JJ zu1fe<96UiFW4n&Q8-dW+f0;M*q0(GC?(l5l=4!vXLx?>AFYwAd`#M*|paJvCRDl5` zn)ew@I@n{x6|iw*0bW|J-tze7FUM0XE?ju_tKq>tDy17s^H!F&@6I|jG+3I=zD){m znPG}WpS)ew2?&fHeQJ4%!^Jd-#JxHq-!J#O@TKS4OZTCx_o`?O?*YmxGUhcr`?M3+ z&Lc6IIFPfZ8(_e8_mkEWhzj8cWkJo|?^lP8M3(OXJ(Djrp^jKTlL6kZap(3e-4|G% znJY&W0CU%REn(xXl}b2^LZ69~aR5kP#c5Xj-%7RrWwK|I1`s}%0O7;>qrmCE!UsTj zx%|_?KQ#Df9Z=T(LxcY}G&mt|&d}mqp9gbz#D16L?7MUM@7%|E6mMu~s0!0khw}x} za`lIy57BvvjCkI>Sy$$Uo1_DuQh9ZCjU28L@%kFN}d zU}H(*M^N%YBXNcdhRzZy+7ZiHjs3IP80^e8IU~65n%&KLr?hkDDJW?e90YH7RbL;K z`*U%7;F&_AU9fKaBCH)J7{^irk)D?i!_kw|xw>rD!l`{FkQqiIKtXI#1E9 zf48Lt+A3_Fn1ls=dsT7p|%<4Z2*sA}@!~C-iS9-O6tDzaS^R98$t9$DR zd1SQZ-=FS-A*Bn{eR~gVD`~FVT^onHF&Og9w%q62r_=qxE84!3Nb+O|Rv<7lgrFpc z?plevv-0*?2;l#xHTE(Kxxq)IwesnM z+L^HwRM4_$pI?V#q3p(x#x}mDVpErZXlKXxttg>_PIj~9PLDOV{dElgYNG%~0&M3E zA8e9uKML_=_b=h&XU4N`blXs}k_DO2j`3Tt5WUs7)gb)|B+A>kG*?u}zGHdXt&2v^ z;``oO;JfOUTUyH}4i2ZgjgXbg3emKMT(B;0wu@+$HDJ z>*RO6D*}TLS}vfDUsF$e6iVYqd}yC^3ZV!ix9oNHcHrr4vOV}to!O%T;ov=c`M^4u z`r?C;$JFM59h{aOX0RDw)J%dD3HQ+`BbLUmv4$OBmR1QA3#Z+UYG3|xKV62N*6;VD z`iEDzIPiE5;_%}5aQTpxc)lfd!)k+j;N>#hD zPdS!PW^S+GmCdSjGhwYUGidLdC7BRy8MWn8mkznO8pb4No=xZhZwHJ$mSWeh?P(-D zuXuDQPbxgxKom+MaiFzxp%#EI0Ba!BJ|bNIyOuwJC2!ql{$zjKw6?R@mM$5WE0vI{n3sp%~D(`-N^bsHqzn1&_bd)^))`i;8M z9641Ppo^+~{_{-2RUL#{l`B8a^dk89(O~Ic>jU7 z5+%|3EGYOneKO;!PmWztIW{d z3sS5}XgFHm-M^bl8OFjxPs=b~-x7V{2P-s6(6?DZcIqhPsL^xGw}qXyl-Hx{E_Gs| zSMuf);Q-7{De&apfKr7NVdWOz0?2VGUgJIESCfP_+In3kuY>%tz+Jo0&yCZGJm@pfS6J z=P$YCtU#1waC30EZ&_VGl}^YWPvEvJ`I(lj9AS_ep4lzbxSWD&I_j0!_DpG<&cLTq zv-@pcQ8j}(Bf3M3Ogm;=x`7%$Y&&HoPE}Y56oN>^kblPn*`j2U8W!)ALXWoPWNIGh z!wcmO^s!r=*~dRgUMVP?d~3uIX&b6kA1$?1iCE^cCVr=vlSJ)WLVC^7;EQQ)_C#QP ze;lD|oMeC>7@2z?H7Rw8hu`wSQpIpAZp^aS%Tzb*T!}e0gUtrwvBbr`ZfsGZ>}D=( zJ*r#Zje zy$lI=EBiyiyRgUrGvYQGd$%jYcQ$zKz>XNS)>A6K_C+dt%aXK~1wHbOISGeK=4aW9 zKJ*%V(ulPnLh&h>xK9n}k)Xb$HY1^ofVK05Spiew^Htfw7BWnh7Nj*BInruia+C(V zCk+PD5)oh}4|3dku5-CJnc2c`+wZiWj8Dxz>5O3u#$79@Nq0x;6Ot=OQCm7oc)vc? z-!Z2v?OBB3Pu+*CW*vN6tg{}+5=hS})g|B!rvNp`^>Jfu#Z=@>jK8Y?b2qr2{6S)2 zkW-8-(l{&Axc-#TOeR!t930khBq~Vl8^rep&O#I#QYI=8_nfu_-!gqBAG*6Ya$%I% z_X-kpLWr7N2d$I?4K|31Ex>*1e?TSL4KM)CySQr5c6gL3tcm0Fq#q-Lo>XDyEgntB z_l29Zg3eQIA9S%r2Xty^XLO3#y>+f2)f%A)<8{rOwF`P`im20d9W|N~h~|VO9n0Xl zC3JD!!q>Lhi>n*|HE4RFT*!3ki=MGXV9xAb4n1i|<+5Fw%YzgQd96ZDFyf_M^j+3v zUW8gJg4%7!)!6v@WoZ}HWnt7w7Cx8b^z(**KzkRA31^V+`UQS)|G=Lb5>J1Sa+8_HHn z*%njiJE;1U{?s0QeAYI;(4BnPk2MGe(=Nhu?K2NPvvpRubCcU7)mHdIP|V$PrzU;J}wO^x9!uKkc1dO z4~%r6xIACYhMqzjxs1Ke#QwtR{t=Q1j@F?K;c>9yZxIJv(HTysVAw41a~s znC&>!KhwElgz>kXE04%K9DQH-?ktXBPHf@2a&pk^qi7*VVGKCKG`LjO9ZWEn2i^oQ z)Vl9exXj?g3kfG6qXi<;z1=LTCtiE2=Sp-B;%;^xEnNRwym!bhGsZH!PxH99_2&mR z*?SyAQrMIVF`Y2M_;sOt9AdVPj~ej~s~mwP`!qTksv&;a~MXOlaA=_f|BzNaYrQCDegJEAb5(yNAFpDbTV z*C&KMe#WZ}qzb2mo@w@b1Av-NDK0RuOAB9(4~Omw3!SWH%atsq2Am*nEdCOf6NJG_ zdkU?z;fdj_X2&hm>}GP?`|;Cp&3z)?ToE+#`=WK`9inB~(*1QIoz^Lk;Ai+R8r>0e z!v38_-qA(AN5}2ZB_3h_ZHvIobUqqL3%gjT2tQW3!0}(RDWctlq&l%kpF{>`S2jSbj)-9G30#y>zTc$K-M<<3;(? z-ONXgiR?=O1(ofKNkGHWN9~D)TS3besFf$%I=6+K8HMcoYDUm@AoIJ-3Q1Ax?-=bs z)QJ4K%F00T1#hR5Hm#Q%bN-^B+5dp!l$xO%;}Te60k*|4>a)b@ME zEmrNh$hPK{ngwzS_e&*KYW=V@wm`(1?xmzNEyY)lXs?t8y)@nj6gGq81hUuWbN_lT z&h5o)(!^Af3pil(0=q}Bzb{YM@>fik3LK(_Ca`S^NR0Fv7iNr^B7*y%NxqI-Bk7PA z0(Hi}(sP!lq!?zv? z%kXS7;n;-T2buF*99C{A_Su%1=fUt<6Cs=8&a)ZsCN@Y<^^gH;ZgPf@467RXgK#&{ z_h}-g^l-AYKXEQVhYUtT$X&@5rg4m~%x?;$CtFWeLf97dA-mhGK(`&48Iu=~R zyyT~u8E80FYGKJ26A)~i#c3`Pg5C1K)=1gvMW-butqTq7@*3~|pmXW(M1hvp$h+>GL_m|C(Ta5Auv=nFDiUthPVDR)@FIYhD&da4 z2g)$Geaar$oY(7G`HB0U0Ln{c)zsKM6fpGDuL0k)h{3VZV>(_#Ml@wD7o!QkyqPVV z7U=L>o-yfOdk$QZ6YywIrv>6dmy>YMtC^836IT+FST3p~t{Sj5wiISS^1$v*7WNpr zp}e&E28Zp7)|7+~+Bi;rpYq&Xg4AWbdhTA^=7oT|)v75^{eGO;Q(Qh(T9{-@%Hs&u zf;mBUe;t|lM|ex&;ybrH<0c#}Ko3*b-@_-KdJ_+HVLp%$NYjHc+iU%7M0}E^RP!mS zax4DK!f{cz8o{6>M!NLGi11Uc0c*vjgmS3j%j$0kO6i|U38g5eN865mesvVtR6VDHUl%^ zP}eO~`!)80s9mlaz)HU^YJv6(vs4l`eKEo4Tiw%nLtwdI-4A{3u+#4}eTv4GxlKq9 zs@~Ge;vne2`>t8g)8O?5F{yY1#?85KjQCs0cCgniotKJP7a`4@GEm z#S_8sM=5zJSChCzq}Z#r<{F-Cngd?U>AAlEEh_yHTGw4>f8y)z&1F$u2=m@^Nkn@m z+>NPj*L6&yb#YuRqI=c$mYvt*K$widFmSTs5Nkhasj}OlJ(lsLIDU@iZw~kOq z6^nAv%+or^j{=w4nmiDql<=3Z-{>~^bkKPGy%cX=iTwWIgncGj>|^7Re97&af$gcA-f9LezWpM+}Yidt; zWVua;RcF)_J9ksx=_GcJ4Wmx5I(>F#`bMIMW=>fPF9lR>F9DpzN9FSZlVBQl)`*-hW_8d?2{oqKUk6 zryFtJ3YqL>;@@(9%x&$#GOqaHX@C&in+@@*aX`uCjs_-oC-opk1%A3`k$J zbXUbp!Zd#YQa)Hp5_MPtw}*&Ern^n%x!}3vz51%MyuVLMEfLno2-jDJh5^;z6Dy-A z^%b=h-DUj#-{U=?I(Yo%5pwuwd}cBX6Hy?CtQk+|S^9LV3kTOjdifNc)>ahpN!1!4 z>F(&iE!>a5zMBAt+NZ=vS`vnsy5_!!Wf6a4=~UoZ$LRoXreg+pvzgyqv$$XM-JC*X z%fq@hOudcUjIMa!_AV=)8DCst*etB!So}*-mQ^hKIr^ZEbwbzelaY9LZ(N=DubS$C zK}DfK;iH>@~-He!UyA8N+*v0A9f%N8D$#&MKRPlqohvI#Xy_zan z;vGiGOQYvH*RgwhFeMNgYTapl zy_@4U8-=)Ih_m6Y`Efz|Lx9%u5^E3U)pyKcQ|%;~>?L<|CdZF0tH55t&73neD&6Z} zR%i$zTyr&NrKClBh6<*QRskk4SK006EWVyioYv_f@eb}W(w(_k{6nMjG^xn+WRIui z@h?m{vo#Mwab^?Wii+qDUWEtwzeMzoIZ2bcQ9-YQ9}YOBZz7hXiDe^>jy0PSm2&oM zS;s$brG(pxuK>R2uUmAG_PB;t)2#W1t?1fis&)?Ysu!GhIaN{DvhfvQmTqX)K?Gk& zU61!+ERyiD;{%RmfV!kZO#L^x7M@|(p{?k zX{e_=a|%v@yjZCVSwAK;8j(j(gvB!-YPxIt;n}B@DqwVZQSXBKYvJocV;F|SndYNr>eAi#Gbd8N~EJqVWAv`U~9f z#K58?^OSc%E~vRns>9*7XBJ)F%-e^MjBf7uo}S)1M_vkTWvM}*4$ivFpLZS7&5vid zWl_on(^q~!tnjxkPd3Sm^KD*l34r%8WqJ5ZC4%;ggP3i!MYRN&?7Ea;86zgHLPk<1 z3GN0RM@mUl>-q_HSorllsygamY^ZHsN>3IMMW zA=;%X4#7+B*K$x`grJ_JioqtPl-9<=gz&XHdmFeUB<7Ckb8iG#kKv( zlGKZXljZE^;y3Jbkgl4Q=6BtKw;a1~QgZ~k4r%)PkLx^Ib%beHaL}e_^+x2Qjz6^{ zPY+a+-|`f7K(eaVbfcOCHE1*v@1ph9E$&JS(qS2N4=sdVcd^Yqsz`~FK*oN6?sc|L zpO#1n<4?EAwtX*M*X#>A+lW>`R8{5r%CYqZ$LU-Q3Xt2Kh2G_t{0~ZE)LwU7E5yTF z%sdi<5}+@-Wd-3B%WvoW`-v;yPnp|SdbFCmz9pZOkkZE35-uFzTb{agRG`p;YI1vV z)Y_QP47bI4g0m$ttj1Ril72G1zffAmo(P8c+?L2CNsz&+Y{re5N&QQTyJF9^CGuMl zmZ#~MAP11y5@E3oIoFjUIL15=k2_h*X4B8sGM(7F<=v~RY99C2R)139yTom&qv}{r z`6I+K{0??lPj9=#!rNum2p^qKoIQGzOdRaR> z3ez9b5tp3*Fog_o1TV;FmUp=nuaZCQ#b?SiCN8F!T4e_xM#B~+bo*S~78jQ^>B+z8 zXVb_~d&7XIfW7a1raIxR`efVp)_*5*dZmG*{6+DLe!HJ^rY3LchY$+;`~H5tXX>uG z|22#0St5jGUyU!*&=O~mm1f+b+AV7uVQ|JIAnbrr8mn>w*Bav=`0gD8{#<))QqXZL(cE_?)5z^>f^!yW z!`*vT(A{6T?{Uv`zps;&1h3TY&rdosyYW%nS(zI>;!yu`XN`z%^BJ$s;Ar&y+WyCV zcbm?9_Xdlt93ju75}qG=ITs&qc($7-U#;1d(2FiI3s{Z*lnogw7L)Ylw1j4oncWcB zTIZB_6hzZ)E7=f0EPQ4Tox$MX5(h(LK|@VD?=a>8>H1O!^3ziG*|Bjfv=M&=ZtO;} z6-({3I5T@kf8G$IlR_uLE+OJf0Bcs7fsw}Ruz1#ru6xWPFvToBv%CciIr{Gm77=HRW{z?CR<)D0)As%yP!H&WtBD+sr0D z+tiJOZ*yzJN-XSE72TSsk5f2Owfm@Xd1iT~Lt9(yZduv4ub7ZAtbNp_+8u)}4@=Bq z2%R7x8csf<$fsxbU1e$n{_;8p9&0l4#eUxS1ZXMUj4OfVJ(a^7K;8|&m>I@-0VId` z-7vR*V>e8%Fxb`REyJQ??%KnOe`8N-BY;b-i#e?SZf!^z z&~PIVIASgmbfkK>`})6eCL(_Yd;}r>uXe69s;MIiR|Jk47i85c3MiOJlt6-1qWDC1 zAG8{)reSk=*a~rBJz`m+BK3%<5CSg_Saz0%%bt zE~ffxWGrFelCz2mLjOg|8|WU!bH}c{1(G zN|D7tM!h(q>8o_6IG#*Tuk(Ti1;IB5D6UUCzSJy?whiIq#4-tQeyc&7WHGAfiOh_3 zn?Gv0@0>_(T6{s*1;U4;PsXiC>hPsz((o-*KTCLKUq|9=vmAzh@GiQdNO#4#{$@Q( z;OFI9me&g945g%eZR11(5UDZy`dT4e8mkabnq}qgTs8ge+@&qB z8a>4+nDmKj|4r;e8++5_xcB}g0NP76+r8loc1>G)7u>OV(OHoa1N7z^UDP4Z5UZx7 zB}0TGBpyX7pT+3#%L*J&8n1oS+XgUQRCUM*QeXFa?R_2GijOTkv6DyYdM{enjV3^a zHfKga861yrz*s&b^{L(4)Sh$(%eeEM=U^sa!&P}02jaZ4cKYv=ZFzQMS8$xXziGVTCny0Y$;K+8;K1#rHHr#1oTAt*kni0i6m)OA z9^ePGc0Kp#&>pmSL?lOr6jrH#YT{IS*FcQ9G~`lf|4caXp}oA4p6{^%2HiA+ zvRXXb_Ih>LyNW4*pQd`Og3Qcg7S|YJAcas~l!yNudE9??EkA)->>jto0GqgkFM|AA zqopG@K!X>j)sHO9WaoU?nW>K%4@&Ie4Exa>r#o8$Qr4QL;QC2NZM&*29^Tj2@Nxls zdWiSaI?iEsUx`r5efnbMnn#8|iCG@awi%RLFJ67@4j+8dT-F?cDlNp4gr=dpz^P$n zOy_Y|oat2qh|oCu zW>`8DS|6CK=5)_>Ag{JMnkka2#>4sf7jPGWV~xM5Dt|#$$YXA=h{`9I`NkavMKEH( zv&}=v8(SVY$AZJ3(4Ah3e0Jn7jKS>`{Qc4=%>oul`N}Gysow%bkSlpF+z-3Zr^mT% z=lwdej-CF9tFuL@U~uXRmXJOCDq7}vAC)yD;(G;HoDKAD^z?8T7$;3C55D$(*YeEJ z-b)WR9!Rn?jj9(?8c`BR(Ij~KI75ABV!3qY)&sScNZ?FfNs_I+)s5|rhpeG zFBIG&o3Juj!B~F6FwlO6Brflt3LIlh9c|UAXb%Wuy~)t`j1CZaKHDfL&@1b7?x>`y z9Mor{snVn^++E;n=kdB%VprFlFr~+d?O6-!S(fR$cg$dfnG7hz%9qzz&)Lr(Mj8MZ za$HqVwMo*ON0Yj6v4)H`{_Qf_igYr)i2Y%_rSs}*)wtH5q7$pYZn$7u5x`uaM62rW zuK2j#pk&8nzLF}ZJ}f-p-;v|t6D-|8>T*H&9aagAvw&Zzn*-%!`nJn2PCE)gub7NB zY49FF-^c?g2G~KCQV{?;F$^CV)u~-_8Z5?Y@uAXAR^0({R_Wuv{!0=hV?Au^k+y>u zs3pD+NHVG9ES_l1DG!3XdFbSD7Fd9{wp~%{`7|wh?(X5lLm%Y2)(fr%F#(Ts<<6LZ zfYBknpP7xcM{MUFZEdru^bvB7fy`!_D;J(TWlm4};CUl8?{R(l{<4Z1q-QRsySzMC z-;9d&bVFBfDbxw8GE5dTO{6i6S&XAG<(h7l$qqJ#hrK?(#<5?cqcEy6^F z$P_?UL?ll{K1t-tMEO8eRNqLo2v0sf*S5qPP~iVxBh(b@z5g_-&CEZuOc3-PczL?| KxD-1F9{Cp+j9N1Q literal 47294 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 A+yDRo