diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 119fddc181fb8..212c396b6efbf 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -11,9 +11,22 @@ ImageFilterLayer::ImageFilterLayer(sk_sp filter) void ImageFilterLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "ImageFilterLayer::Preroll"); + Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); - ContainerLayer::Preroll(context, matrix); + + child_paint_bounds_ = SkRect::MakeEmpty(); + PrerollChildren(context, matrix, &child_paint_bounds_); + if (filter_) { + const SkIRect filter_input_bounds = child_paint_bounds_.roundOut(); + SkIRect filter_output_bounds = + filter_->filterBounds(filter_input_bounds, SkMatrix::I(), + SkImageFilter::kForward_MapDirection); + set_paint_bounds(SkRect::Make(filter_output_bounds)); + } else { + set_paint_bounds(child_paint_bounds_); + } if (!context->has_platform_view && context->raster_cache && SkRect::Intersects(context->cull_rect, paint_bounds())) { @@ -48,8 +61,12 @@ void ImageFilterLayer::Paint(PaintContext& context) const { SkPaint paint; paint.setImageFilter(filter_); + // Normally a save_layer is sized to the current layer bounds, but in this + // case the bounds of the child may not be the same as the filtered version + // so we use the child_paint_bounds_ which were snapshotted from the + // Preroll on the children before we adjusted them based on the filter. Layer::AutoSaveLayer save_layer = - Layer::AutoSaveLayer::Create(context, paint_bounds(), &paint); + Layer::AutoSaveLayer::Create(context, child_paint_bounds_, &paint); PaintChildren(context); } diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h index 30ec99935ff0a..8e40a9ab339ba 100644 --- a/flow/layers/image_filter_layer.h +++ b/flow/layers/image_filter_layer.h @@ -21,6 +21,7 @@ class ImageFilterLayer : public ContainerLayer { private: sk_sp filter_; + SkRect child_paint_bounds_; FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer); }; diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index 63357fbd89ce8..f4f621a529a08 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -83,8 +83,47 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) { auto layer = std::make_shared(layer_filter); layer->Add(mock_layer); + const SkRect child_rounded_bounds = + SkRect::MakeLTRB(5.0f, 6.0f, 21.0f, 22.0f); + layer->Preroll(preroll_context(), initial_transform); - EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_rounded_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setImageFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({ + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_bounds, filter_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(ImageFilterLayerTest, SimpleFilterBounds) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + const SkMatrix filter_transform = SkMatrix::MakeScale(2.0, 2.0); + auto layer_filter = SkImageFilter::MakeMatrixFilter( + filter_transform, SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer); + + const SkRect filter_bounds = SkRect::MakeLTRB(10.0f, 12.0f, 42.0f, 44.0f); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), filter_bounds); EXPECT_TRUE(layer->needs_painting()); EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); @@ -123,10 +162,12 @@ TEST_F(ImageFilterLayerTest, MultipleChildren) { SkRect children_bounds = child_path1.getBounds(); children_bounds.join(child_path2.getBounds()); + SkRect children_rounded_bounds = SkRect::Make(children_bounds.roundOut()); + layer->Preroll(preroll_context(), initial_transform); EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); - EXPECT_EQ(layer->paint_bounds(), children_bounds); + EXPECT_EQ(layer->paint_bounds(), children_rounded_bounds); EXPECT_TRUE(mock_layer1->needs_painting()); EXPECT_TRUE(mock_layer2->needs_painting()); EXPECT_TRUE(layer->needs_painting()); @@ -172,12 +213,17 @@ TEST_F(ImageFilterLayerTest, Nested) { layer1->Add(layer2); SkRect children_bounds = child_path1.getBounds(); - children_bounds.join(child_path2.getBounds()); + children_bounds.join(SkRect::Make(child_path2.getBounds().roundOut())); + const SkRect children_rounded_bounds = + SkRect::Make(children_bounds.roundOut()); + const SkRect mock_layer2_rounded_bounds = + SkRect::Make(child_path2.getBounds().roundOut()); + layer1->Preroll(preroll_context(), initial_transform); EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); - EXPECT_EQ(layer1->paint_bounds(), children_bounds); - EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds()); + EXPECT_EQ(layer1->paint_bounds(), children_rounded_bounds); + EXPECT_EQ(layer2->paint_bounds(), mock_layer2_rounded_bounds); EXPECT_TRUE(mock_layer1->needs_painting()); EXPECT_TRUE(mock_layer2->needs_painting()); EXPECT_TRUE(layer1->needs_painting());