From c2f79a0a4e3b6f7b3df498360ebb8cb939842e7e Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 13 Dec 2019 17:53:28 -0800 Subject: [PATCH 1/8] Engine support for ImageFiltered widget (https://github.com/flutter/flutter/issues/13489) --- flow/BUILD.gn | 3 + flow/layers/color_filter_layer_unittests.cc | 2 +- flow/layers/image_filter_layer.cc | 56 +++++ flow/layers/image_filter_layer.h | 30 +++ flow/layers/image_filter_layer_unittests.cc | 236 ++++++++++++++++++++ lib/ui/compositing.dart | 30 +++ lib/ui/compositing/scene_builder.cc | 10 + lib/ui/compositing/scene_builder.h | 1 + lib/ui/painting/image_filter.h | 2 +- 9 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 flow/layers/image_filter_layer.cc create mode 100644 flow/layers/image_filter_layer.h create mode 100644 flow/layers/image_filter_layer_unittests.cc diff --git a/flow/BUILD.gn b/flow/BUILD.gn index f3ed537fe9113..dad0635fa760a 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -30,6 +30,8 @@ source_set("flow") { "layers/container_layer.h", "layers/elevated_container_layer.cc", "layers/elevated_container_layer.h", + "layers/image_filter_layer.cc", + "layers/image_filter_layer.h", "layers/layer.cc", "layers/layer.h", "layers/layer_tree.cc", @@ -139,6 +141,7 @@ executable("flow_unittests") { "layers/clip_rrect_layer_unittests.cc", "layers/color_filter_layer_unittests.cc", "layers/container_layer_unittests.cc", + "layers/image_filter_layer_unittests.cc", "layers/layer_tree_unittests.cc", "layers/opacity_layer_unittests.cc", "layers/performance_overlay_layer_unittests.cc", diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc index 42a65f255cd1e..af1e5788b6e34 100644 --- a/flow/layers/color_filter_layer_unittests.cc +++ b/flow/layers/color_filter_layer_unittests.cc @@ -29,7 +29,7 @@ TEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) { "needs_painting\\(\\)"); } -TEST_F(ColorFilterLayerTest, PaintBeforePreollDies) { +TEST_F(ColorFilterLayerTest, PaintBeforePrerollDies) { const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); const SkPath child_path = SkPath().addRect(child_bounds); auto mock_layer = std::make_shared(child_path); diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc new file mode 100644 index 0000000000000..8718551b08f70 --- /dev/null +++ b/flow/layers/image_filter_layer.cc @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/image_filter_layer.h" + +namespace flutter { + +ImageFilterLayer::ImageFilterLayer(sk_sp filter) + : filter_(std::move(filter)) {} + +void ImageFilterLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + ContainerLayer::Preroll(context, matrix); + + if (!context->has_platform_view && context->raster_cache && + SkRect::Intersects(context->cull_rect, paint_bounds())) { + SkMatrix ctm = matrix; +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + ctm = RasterCache::GetIntegralTransCTM(ctm); +#endif + context->raster_cache->Prepare(context, this, ctm); + } +} + +void ImageFilterLayer::Paint(PaintContext& context) const { + TRACE_EVENT0("flutter", "ImageFilterLayer::Paint"); + FML_DCHECK(needs_painting()); + +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); +#endif + + if (context.raster_cache) { + const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); + RasterCacheResult child_cache = + context.raster_cache->Get((Layer*)this, ctm); + if (child_cache.is_valid()) { + child_cache.draw(*context.leaf_nodes_canvas); + return; + } + } + + SkPaint paint; + paint.setImageFilter(filter_); + + Layer::AutoSaveLayer save_layer = + Layer::AutoSaveLayer::Create(context, paint_bounds(), &paint); + PaintChildren(context); +} + +} // namespace flutter diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h new file mode 100644 index 0000000000000..30ec99935ff0a --- /dev/null +++ b/flow/layers/image_filter_layer.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ + +#include "flutter/flow/layers/container_layer.h" + +#include "third_party/skia/include/core/SkImageFilter.h" + +namespace flutter { + +class ImageFilterLayer : public ContainerLayer { + public: + ImageFilterLayer(sk_sp filter); + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + + void Paint(PaintContext& context) const override; + + private: + sk_sp filter_; + + FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc new file mode 100644 index 0000000000000..f1ce8a98fffcf --- /dev/null +++ b/flow/layers/image_filter_layer_unittests.cc @@ -0,0 +1,236 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/image_filter_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkImageFilter.h" + +namespace flutter { +namespace testing { + +using ImageFilterLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ImageFilterLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(sk_sp()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ImageFilterLayerTest, PaintBeforePrerollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(sk_sp()); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ImageFilterLayerTest, EmptyFilter) { + 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); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setImageFilter(nullptr); + 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, SimpleFilter) { + 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); + auto layer_filter = SkImageFilter::MakeMatrixFilter( + SkMatrix(), 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); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_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, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + 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_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->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{children_bounds, filter_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ImageFilterLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto layer_filter2 = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1); + auto layer2 = std::make_shared(layer_filter2); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + 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_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setImageFilter(layer_filter1); + filter_paint2.setImageFilter(layer_filter2); + layer1->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{children_bounds, filter_paint1, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{2, MockCanvas::SaveData{3}}, + MockCanvas::DrawCall{3, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 3, MockCanvas::SaveLayerData{child_path2.getBounds(), + filter_paint2, nullptr, 4}}, + MockCanvas::DrawCall{ + 4, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{4, MockCanvas::RestoreData{3}}, + MockCanvas::DrawCall{3, MockCanvas::RestoreData{2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(ImageFilterLayerTest, Readback) { + auto layer_filter = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto initial_transform = SkMatrix(); + + // ColorFilterLayer does not read from surface + auto layer = std::make_shared(layer_filter); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // ColorFilterLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + +} // namespace testing +} // namespace flutter diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index f2b36ca95212c..8222a6d6f3668 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -139,6 +139,15 @@ class ColorFilterEngineLayer extends _EngineLayerWrapper { ColorFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer); } +/// An opaque handle to an image filter engine layer. +/// +/// Instances of this class are created by [SceneBuilder.pushImageFilter]. +/// +/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} +class ImageFilterEngineLayer extends _EngineLayerWrapper { + ImageFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer); +} + /// An opaque handle to a backdrop filter engine layer. /// /// Instances of this class are created by [SceneBuilder.pushBackdropFilter]. @@ -431,6 +440,27 @@ class SceneBuilder extends NativeFieldWrapperClass2 { EngineLayer _pushColorFilter(_ColorFilter filter) native 'SceneBuilder_pushColorFilter'; + /// Pushes an image filter operation onto the operation stack. + /// + /// The given filter is applied to the children's rasterization before compositing them into + /// the scene. + /// + /// {@macro dart.ui.sceneBuilder.oldLayer} + /// + /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} + /// + /// See [pop] for details about the operation stack. + ImageFilterEngineLayer pushImageFilter(ImageFilter filter, { ImageFilterEngineLayer oldLayer }) { + assert(filter != null); + assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushImageFilter')); + final _ImageFilter nativeFilter = filter._toNativeImageFilter(); + assert(nativeFilter != null); + final ImageFilterEngineLayer layer = ImageFilterEngineLayer._(_pushImageFilter(nativeFilter)); + assert(_debugPushLayer(layer)); + return layer; + } + EngineLayer _pushImageFilter(_ImageFilter filter) native 'SceneBuilder_pushImageFilter'; + /// Pushes a backdrop filter operation onto the operation stack. /// /// The given filter is applied to the current contents of the scene prior to diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 216e1c9284874..467f187400255 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -10,6 +10,7 @@ #include "flutter/flow/layers/clip_rrect_layer.h" #include "flutter/flow/layers/color_filter_layer.h" #include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/image_filter_layer.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/layers/opacity_layer.h" @@ -49,6 +50,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, SceneBuilder); V(SceneBuilder, pushClipPath) \ V(SceneBuilder, pushOpacity) \ V(SceneBuilder, pushColorFilter) \ + V(SceneBuilder, pushImageFilter) \ V(SceneBuilder, pushBackdropFilter) \ V(SceneBuilder, pushShaderMask) \ V(SceneBuilder, pushPhysicalShape) \ @@ -152,6 +154,14 @@ fml::RefPtr SceneBuilder::pushColorFilter( return EngineLayer::MakeRetained(layer); } +fml::RefPtr SceneBuilder::pushImageFilter( + const ImageFilter* image_filter) { + auto layer = + std::make_shared(image_filter->filter()); + PushLayer(layer); + return EngineLayer::MakeRetained(layer); +} + fml::RefPtr SceneBuilder::pushBackdropFilter(ImageFilter* filter) { auto layer = std::make_shared(filter->filter()); PushLayer(layer); diff --git a/lib/ui/compositing/scene_builder.h b/lib/ui/compositing/scene_builder.h index a634087174e2f..51ae76e651ce0 100644 --- a/lib/ui/compositing/scene_builder.h +++ b/lib/ui/compositing/scene_builder.h @@ -50,6 +50,7 @@ class SceneBuilder : public RefCountedDartWrappable { int clipBehavior); fml::RefPtr pushOpacity(int alpha, double dx = 0, double dy = 0); fml::RefPtr pushColorFilter(const ColorFilter* color_filter); + fml::RefPtr pushImageFilter(const ImageFilter* image_filter); fml::RefPtr pushBackdropFilter(ImageFilter* filter); fml::RefPtr pushShaderMask(Shader* shader, double maskRectLeft, diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h index f95430f2c5460..c44243016b9cc 100644 --- a/lib/ui/painting/image_filter.h +++ b/lib/ui/painting/image_filter.h @@ -26,7 +26,7 @@ class ImageFilter : public RefCountedDartWrappable { void initBlur(double sigma_x, double sigma_y); void initMatrix(const tonic::Float64List& matrix4, int filter_quality); - const sk_sp& filter() { return filter_; } + const sk_sp& filter() const { return filter_; } static void RegisterNatives(tonic::DartLibraryNatives* natives); From aa3ccb8ae9486fd96662162385f4675dce396aa1 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 13 Dec 2019 18:04:54 -0800 Subject: [PATCH 2/8] Fix license list for new files. --- ci/licenses_golden/licenses_flutter | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ed5ca81f020ab..9abff13f9e5a8 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -52,6 +52,9 @@ FILE: ../../../flutter/flow/layers/elevated_container_layer.cc FILE: ../../../flutter/flow/layers/elevated_container_layer.h FILE: ../../../flutter/flow/layers/fuchsia_system_composited_layer.cc FILE: ../../../flutter/flow/layers/fuchsia_system_composited_layer.h +FILE: ../../../flutter/flow/layers/image_filter_layer.cc +FILE: ../../../flutter/flow/layers/image_filter_layer.h +FILE: ../../../flutter/flow/layers/image_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/layer.cc FILE: ../../../flutter/flow/layers/layer.h FILE: ../../../flutter/flow/layers/layer_tree.cc From 4bcb136170353778ec2b3e3c42aaea21ba9e1cf7 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 13 Dec 2019 18:30:11 -0800 Subject: [PATCH 3/8] Implement (unimplemented) web_ui stubs for ImageFilterLayer. --- flow/layers/image_filter_layer_unittests.cc | 4 ++-- .../compositor/layer_scene_builder.dart | 8 ++++++++ .../lib/src/engine/surface/scene_builder.dart | 17 ++++++++++++++++ lib/web_ui/lib/src/ui/compositing.dart | 20 +++++++++++++++++++ testing/dart/compositing_test.dart | 17 ++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index f1ce8a98fffcf..63357fbd89ce8 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -217,13 +217,13 @@ TEST_F(ImageFilterLayerTest, Readback) { SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); auto initial_transform = SkMatrix(); - // ColorFilterLayer does not read from surface + // ImageFilterLayer does not read from surface auto layer = std::make_shared(layer_filter); preroll_context()->surface_needs_readback = false; layer->Preroll(preroll_context(), initial_transform); EXPECT_FALSE(preroll_context()->surface_needs_readback); - // ColorFilterLayer blocks child with readback + // ImageFilterLayer blocks child with readback auto mock_layer = std::make_shared(SkPath(), SkPaint(), false, false, true); layer->Add(mock_layer); diff --git a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart index ff71900d5fe31..bcef97cc35222 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart @@ -143,6 +143,14 @@ class LayerSceneBuilder implements ui.SceneBuilder { throw UnimplementedError(); } + ui.ImageFilterEngineLayer pushImageFilter( + ui.ImageFilter filter, { + ui.ImageFilterEngineLayer oldLayer, + }) { + assert(filter != null); + throw UnimplementedError(); + } + @override ui.OffsetEngineLayer pushOffset( double dx, diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index 52e8d01420a3d..aa58e6ea7150f 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -173,6 +173,23 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { throw UnimplementedError(); } + /// Pushes an image filter operation onto the operation stack. + /// + /// The given filter is applied to the children's rasterization before compositing them into + /// the scene. + /// + /// {@macro dart.ui.sceneBuilder.oldLayer} + /// + /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} + /// + /// See [pop] for details about the operation stack. + @override + ui.ImageFilterEngineLayer pushImageFilter(ui.ImageFilter filter, + {ui.ImageFilterEngineLayer oldLayer}) { + assert(filter != null); + throw UnimplementedError(); + } + /// Pushes a backdrop filter operation onto the operation stack. /// /// The given filter is applied to the current contents of the scene prior to diff --git a/lib/web_ui/lib/src/ui/compositing.dart b/lib/web_ui/lib/src/ui/compositing.dart index d77249eb51775..d93cc3f7dbfa8 100644 --- a/lib/web_ui/lib/src/ui/compositing.dart +++ b/lib/web_ui/lib/src/ui/compositing.dart @@ -74,6 +74,13 @@ abstract class OpacityEngineLayer implements EngineLayer {} /// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} abstract class ColorFilterEngineLayer implements EngineLayer {} +/// An opaque handle to an image filter engine layer. +/// +/// Instances of this class are created by [SceneBuilder.pushImageFilter]. +/// +/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} +abstract class ImageFilterEngineLayer implements EngineLayer {} + /// An opaque handle to a backdrop filter engine layer. /// /// Instances of this class are created by [SceneBuilder.pushBackdropFilter]. @@ -196,6 +203,19 @@ abstract class SceneBuilder { ColorFilterEngineLayer oldLayer, }); + /// Pushes an image filter operation onto the operation stack. + /// + /// The given filter is applied to the children's rasterization before compositing them into + /// the scene. + /// + /// {@macro dart.ui.sceneBuilder.oldLayer} + /// + /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} + /// + /// See [pop] for details about the operation stack. + ImageFilterEngineLayer pushImageFilter(ImageFilter filter, + {ImageFilterEngineLayer oldLayer}); + /// Pushes a backdrop filter operation onto the operation stack. /// /// The given filter is applied to the current contents of the scene prior to diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index 53812d9be4bfb..10ee1c0419f72 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -360,6 +360,23 @@ void main() { ); }); }); + testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { + return builder.pushImageFilter( + ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + oldLayer: oldLayer, + ); + }); + testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { + return builder.pushImageFilter( + ImageFilter.matrix(Float64List.fromList([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ])), + oldLayer: oldLayer, + ); + }); } typedef _TestNoSharingFunction = EngineLayer Function(SceneBuilder builder, EngineLayer oldLayer); From e83dc5cf744489303f8b960877ff122ea67f9df6 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 13 Dec 2019 18:38:32 -0800 Subject: [PATCH 4/8] Fix lint error. --- testing/dart/compositing_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index 10ee1c0419f72..1248bd0f1795d 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -368,7 +368,7 @@ void main() { }); testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { return builder.pushImageFilter( - ImageFilter.matrix(Float64List.fromList([ + ImageFilter.matrix(Float64List.fromList([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, From 1d06c2b100d3025af4161c6bc723f58dd46b1bac Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 13 Dec 2019 18:56:26 -0800 Subject: [PATCH 5/8] Move new tests to proper location in test framework. --- testing/dart/compositing_test.dart | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index 1248bd0f1795d..42f47c10e504a 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -359,23 +359,23 @@ void main() { oldLayer: oldLayer, ); }); - }); - testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { - return builder.pushImageFilter( - ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - oldLayer: oldLayer, - ); - }); - testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { - return builder.pushImageFilter( - ImageFilter.matrix(Float64List.fromList([ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, - ])), - oldLayer: oldLayer, - ); + testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { + return builder.pushImageFilter( + ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + oldLayer: oldLayer, + ); + }); + testNoSharing((SceneBuilder builder, EngineLayer oldLayer) { + return builder.pushImageFilter( + ImageFilter.matrix(Float64List.fromList([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ])), + oldLayer: oldLayer, + ); + }); }); } From 415095670ff7125fb005555ec76a81fada7da70e Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 16 Dec 2019 15:30:19 -0800 Subject: [PATCH 6/8] Rename local variable for layer cache. --- flow/layers/image_filter_layer.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 8718551b08f70..119fddc181fb8 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -37,10 +37,10 @@ void ImageFilterLayer::Paint(PaintContext& context) const { if (context.raster_cache) { const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); - RasterCacheResult child_cache = + RasterCacheResult layer_cache = context.raster_cache->Get((Layer*)this, ctm); - if (child_cache.is_valid()) { - child_cache.draw(*context.leaf_nodes_canvas); + if (layer_cache.is_valid()) { + layer_cache.draw(*context.leaf_nodes_canvas); return; } } From 27deefebd852c256951ac0a28ba9986fb05bb559 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 19 Dec 2019 15:39:26 -0800 Subject: [PATCH 7/8] Adjust new pushImageFilter methods to match new SceneBuilder coding style. --- lib/ui/compositing.dart | 6 +++++- lib/web_ui/lib/src/engine/surface/scene_builder.dart | 6 ++++-- lib/web_ui/lib/src/ui/compositing.dart | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 8222a6d6f3668..cf73966e06e20 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -450,7 +450,10 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - ImageFilterEngineLayer pushImageFilter(ImageFilter filter, { ImageFilterEngineLayer oldLayer }) { + ImageFilterEngineLayer pushImageFilter( + ImageFilter filter, { + ImageFilterEngineLayer oldLayer, + }) { assert(filter != null); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushImageFilter')); final _ImageFilter nativeFilter = filter._toNativeImageFilter(); @@ -459,6 +462,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { assert(_debugPushLayer(layer)); return layer; } + EngineLayer _pushImageFilter(_ImageFilter filter) native 'SceneBuilder_pushImageFilter'; /// Pushes a backdrop filter operation onto the operation stack. diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index aa58e6ea7150f..bda572b06145d 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -184,8 +184,10 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.ImageFilterEngineLayer pushImageFilter(ui.ImageFilter filter, - {ui.ImageFilterEngineLayer oldLayer}) { + ui.ImageFilterEngineLayer pushImageFilter( + ui.ImageFilter filter, { + ui.ImageFilterEngineLayer oldLayer, + }) { assert(filter != null); throw UnimplementedError(); } diff --git a/lib/web_ui/lib/src/ui/compositing.dart b/lib/web_ui/lib/src/ui/compositing.dart index d93cc3f7dbfa8..4574304add353 100644 --- a/lib/web_ui/lib/src/ui/compositing.dart +++ b/lib/web_ui/lib/src/ui/compositing.dart @@ -213,8 +213,10 @@ abstract class SceneBuilder { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - ImageFilterEngineLayer pushImageFilter(ImageFilter filter, - {ImageFilterEngineLayer oldLayer}); + ImageFilterEngineLayer pushImageFilter( + ImageFilter filter, { + ImageFilterEngineLayer oldLayer, + }); /// Pushes a backdrop filter operation onto the operation stack. /// From 347dfb91f1347f2e5eb399de41c93e1932bc6d84 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 19 Dec 2019 16:09:18 -0800 Subject: [PATCH 8/8] Mention use of ImageFilter from new ImageFiltered widget. --- lib/ui/painting.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 33d209486f6d1..9c6d3f9df3c95 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -2746,8 +2746,11 @@ class _ColorFilter extends NativeFieldWrapperClass2 { /// See also: /// /// * [BackdropFilter], a widget that applies [ImageFilter] to its rendering. +/// * [ImageFiltered], a widget that applies [ImageFilter] to its children. /// * [SceneBuilder.pushBackdropFilter], which is the low-level API for using -/// this class. +/// this class as a backdrop filter. +/// * [SceneBuilder.pushImageFilter], which is the low-level API for using +/// this class as a child layer filter. class ImageFilter { /// Creates an image filter that applies a Gaussian blur. ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 })