From f36e26fd5d02c55e8e6c6b2945283f4dba71f936 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 3 Oct 2024 17:35:34 -0700 Subject: [PATCH 1/4] Honor blur tile mode in BackdropFilter widget on Skia backend --- display_list/skia/dl_sk_canvas.cc | 7 ++- display_list/skia/dl_sk_dispatcher.cc | 7 ++- testing/dart/painting_test.dart | 71 +++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/display_list/skia/dl_sk_canvas.cc b/display_list/skia/dl_sk_canvas.cc index 73191964230f8..178725d87a53c 100644 --- a/display_list/skia/dl_sk_canvas.cc +++ b/display_list/skia/dl_sk_canvas.cc @@ -58,8 +58,11 @@ void DlSkCanvasAdapter::SaveLayer(const SkRect* bounds, sk_sp sk_backdrop = ToSk(backdrop); SkOptionalPaint sk_paint(paint); TRACE_EVENT0("flutter", "Canvas::saveLayer"); - delegate_->saveLayer( - SkCanvas::SaveLayerRec{bounds, sk_paint(), sk_backdrop.get(), 0}); + const SkTileMode tile_mode = backdrop && backdrop->asBlur() + ? ToSk(backdrop->asBlur()->tile_mode()) + : SkTileMode::kClamp; + delegate_->saveLayer(SkCanvas::SaveLayerRec{ + bounds, sk_paint(), sk_backdrop.get(), tile_mode, nullptr, 0}); } void DlSkCanvasAdapter::Restore() { diff --git a/display_list/skia/dl_sk_dispatcher.cc b/display_list/skia/dl_sk_dispatcher.cc index 30f79f09f0163..ff7eac7ec65a5 100644 --- a/display_list/skia/dl_sk_dispatcher.cc +++ b/display_list/skia/dl_sk_dispatcher.cc @@ -70,8 +70,11 @@ void DlSkCanvasDispatcher::saveLayer(const DlRect& bounds, const sk_sp sk_backdrop = ToSk(backdrop); const SkRect* sl_bounds = options.bounds_from_caller() ? &ToSkRect(bounds) : nullptr; - canvas_->saveLayer( - SkCanvas::SaveLayerRec(sl_bounds, paint, sk_backdrop.get(), 0)); + const SkTileMode tile_mode = backdrop && backdrop->asBlur() + ? ToSk(backdrop->asBlur()->tile_mode()) + : SkTileMode::kClamp; + canvas_->saveLayer(SkCanvas::SaveLayerRec( + sl_bounds, paint, sk_backdrop.get(), tile_mode, nullptr, 0)); // saveLayer will apply the current opacity on behalf of the children // so they will inherit an opaque opacity. save_opacity(SK_Scalar1); diff --git a/testing/dart/painting_test.dart b/testing/dart/painting_test.dart index 68388a09a4071..c4ee8cc8e63f8 100644 --- a/testing/dart/painting_test.dart +++ b/testing/dart/painting_test.dart @@ -8,6 +8,8 @@ import 'dart:ui'; import 'package:test/test.dart'; import 'package:vector_math/vector_math_64.dart'; +import 'goldens.dart'; + typedef CanvasCallback = void Function(Canvas canvas); void main() { @@ -107,6 +109,75 @@ void main() { redClippedPicture.dispose(); }); + Image BackdropBlurWithTileMode(TileMode tileMode) { + Picture makePicture(CanvasCallback callback) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + callback(canvas); + return recorder.endRecording(); + } + final SceneBuilder sceneBuilder = SceneBuilder(); + + final Picture blueGreenGridPicture = makePicture((Canvas canvas) { + const Color white = const Color(0xFFFFFFFF); + const Color blue = const Color(0xFF0000FF); + const Color green = const Color(0xFF00FF00); + canvas.drawColor(white, BlendMode.src); + for (int i = 0; i < 100; i++) { + canvas.drawRect(Rect.fromLTRB(i * 5, 0, i * 5, 1000), + Paint()..color = (i & 1) == 0 ? green : blue); + canvas.drawRect(Rect.fromLTRB(0, i * 5, 1000, i * 5), + Paint()..color = (i & 1) == 0 ? blue : green); + } + }); + sceneBuilder.addPicture(Offset.zero, blueGreenGridPicture); + sceneBuilder.pushBackdropFilter(ImageFilter.blur(sigmaX: 10, sigmaY: 10, tileMode: tileMode)); + + final Scene scene = sceneBuilder.build(); + final Image image = scene.toImageSync(501, 501); + + scene.dispose(); + blueGreenGridPicture.dispose(); + + return image; + } + + test('BackdropFilter with Blur honors TileMode.decal', () async { + Image image = BackdropBlurWithTileMode(TileMode.decal); + + final ImageComparer comparer = await ImageComparer.create(); + await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_decal_tile_mode.png'); + + image.dispose(); + }); + + test('BackdropFilter with Blur honors TileMode.clamp', () async { + Image image = BackdropBlurWithTileMode(TileMode.clamp); + + final ImageComparer comparer = await ImageComparer.create(); + await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_clamp_tile_mode.png'); + + image.dispose(); + }); + + test('BackdropFilter with Blur honors TileMode.mirror', () async { + Image image = BackdropBlurWithTileMode(TileMode.mirror); + + final ImageComparer comparer = await ImageComparer.create(); + await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_mirror_tile_mode.png'); + + image.dispose(); + }); + + test('BackdropFilter with Blur honors TileMode.repeated', () async { + Image image = BackdropBlurWithTileMode(TileMode.repeated); + + final ImageComparer comparer = await ImageComparer.create(); + await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_repeated_tile_mode.png'); + + image.dispose(); + }); + test('ImageFilter.matrix defaults to FilterQuality.medium', () { final Float64List data = Matrix4.identity().storage; expect( From 4dafa896696184b3ce20b493bb16fb14185b726d Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 3 Oct 2024 18:16:02 -0700 Subject: [PATCH 2/4] leave SaveLayerRec defaults alone if not needed and update rendering unit tests --- display_list/skia/dl_sk_canvas.cc | 10 +++++----- display_list/skia/dl_sk_dispatcher.cc | 10 +++++----- display_list/testing/dl_rendering_unittests.cc | 9 ++++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/display_list/skia/dl_sk_canvas.cc b/display_list/skia/dl_sk_canvas.cc index 178725d87a53c..2dfd11e6547d4 100644 --- a/display_list/skia/dl_sk_canvas.cc +++ b/display_list/skia/dl_sk_canvas.cc @@ -58,11 +58,11 @@ void DlSkCanvasAdapter::SaveLayer(const SkRect* bounds, sk_sp sk_backdrop = ToSk(backdrop); SkOptionalPaint sk_paint(paint); TRACE_EVENT0("flutter", "Canvas::saveLayer"); - const SkTileMode tile_mode = backdrop && backdrop->asBlur() - ? ToSk(backdrop->asBlur()->tile_mode()) - : SkTileMode::kClamp; - delegate_->saveLayer(SkCanvas::SaveLayerRec{ - bounds, sk_paint(), sk_backdrop.get(), tile_mode, nullptr, 0}); + SkCanvas::SaveLayerRec params(bounds, sk_paint(), sk_backdrop.get(), 0); + if (sk_backdrop && backdrop->asBlur()) { + params.fBackdropTileMode = ToSk(backdrop->asBlur()->tile_mode()); + } + delegate_->saveLayer(params); } void DlSkCanvasAdapter::Restore() { diff --git a/display_list/skia/dl_sk_dispatcher.cc b/display_list/skia/dl_sk_dispatcher.cc index ff7eac7ec65a5..33b11166cd5ae 100644 --- a/display_list/skia/dl_sk_dispatcher.cc +++ b/display_list/skia/dl_sk_dispatcher.cc @@ -70,11 +70,11 @@ void DlSkCanvasDispatcher::saveLayer(const DlRect& bounds, const sk_sp sk_backdrop = ToSk(backdrop); const SkRect* sl_bounds = options.bounds_from_caller() ? &ToSkRect(bounds) : nullptr; - const SkTileMode tile_mode = backdrop && backdrop->asBlur() - ? ToSk(backdrop->asBlur()->tile_mode()) - : SkTileMode::kClamp; - canvas_->saveLayer(SkCanvas::SaveLayerRec( - sl_bounds, paint, sk_backdrop.get(), tile_mode, nullptr, 0)); + SkCanvas::SaveLayerRec params(sl_bounds, paint, sk_backdrop.get(), 0); + if (sk_backdrop && backdrop->asBlur()) { + params.fBackdropTileMode = ToSk(backdrop->asBlur()->tile_mode()); + } + canvas_->saveLayer(params); // saveLayer will apply the current opacity on behalf of the children // so they will inherit an opaque opacity. save_opacity(SK_Scalar1); diff --git a/display_list/testing/dl_rendering_unittests.cc b/display_list/testing/dl_rendering_unittests.cc index 1cde7bdd7f4da..05b6ecc627561 100644 --- a/display_list/testing/dl_rendering_unittests.cc +++ b/display_list/testing/dl_rendering_unittests.cc @@ -1330,7 +1330,8 @@ class CanvasCompareTester { [=](const SkSetupContext& ctx) { sk_backdrop_setup(ctx); ctx.canvas->saveLayer(SkCanvas::SaveLayerRec( - nullptr, nullptr, sk_backdrop.get(), 0)); + nullptr, nullptr, sk_backdrop.get(), + SkTileMode::kDecal, nullptr, 0)); sk_content_setup(ctx); }, [=](const DlSetupContext& ctx) { @@ -1345,7 +1346,8 @@ class CanvasCompareTester { [=](const SkSetupContext& ctx) { sk_backdrop_setup(ctx); ctx.canvas->saveLayer(SkCanvas::SaveLayerRec( - &layer_bounds, nullptr, sk_backdrop.get(), 0)); + &layer_bounds, nullptr, sk_backdrop.get(), + SkTileMode::kDecal, nullptr, 0)); sk_content_setup(ctx); }, [=](const DlSetupContext& ctx) { @@ -1362,7 +1364,8 @@ class CanvasCompareTester { sk_backdrop_setup(ctx); ctx.canvas->clipRect(layer_bounds); ctx.canvas->saveLayer(SkCanvas::SaveLayerRec( - nullptr, nullptr, sk_backdrop.get(), 0)); + nullptr, nullptr, sk_backdrop.get(), + SkTileMode::kDecal, nullptr, 0)); sk_content_setup(ctx); }, [=](const DlSetupContext& ctx) { From 4b44a2f68b776ab236b72e4036009d8af25c7b7c Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 4 Oct 2024 00:56:55 -0700 Subject: [PATCH 3/4] analyzer and fix test case --- testing/dart/painting_test.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/testing/dart/painting_test.dart b/testing/dart/painting_test.dart index c4ee8cc8e63f8..5991171262d40 100644 --- a/testing/dart/painting_test.dart +++ b/testing/dart/painting_test.dart @@ -109,7 +109,7 @@ void main() { redClippedPicture.dispose(); }); - Image BackdropBlurWithTileMode(TileMode tileMode) { + Image backdropBlurWithTileMode(TileMode tileMode) { Picture makePicture(CanvasCallback callback) { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -119,14 +119,14 @@ void main() { final SceneBuilder sceneBuilder = SceneBuilder(); final Picture blueGreenGridPicture = makePicture((Canvas canvas) { - const Color white = const Color(0xFFFFFFFF); - const Color blue = const Color(0xFF0000FF); - const Color green = const Color(0xFF00FF00); + const Color white = Color(0xFFFFFFFF); + const Color blue = Color(0xFF0000FF); + const Color green = Color(0xFF00FF00); canvas.drawColor(white, BlendMode.src); for (int i = 0; i < 100; i++) { - canvas.drawRect(Rect.fromLTRB(i * 5, 0, i * 5, 1000), + canvas.drawRect(Rect.fromLTWH(i * 5, 0, 1, 1000), Paint()..color = (i & 1) == 0 ? green : blue); - canvas.drawRect(Rect.fromLTRB(0, i * 5, 1000, i * 5), + canvas.drawRect(Rect.fromLTWH(0, i * 5, 1000, 1), Paint()..color = (i & 1) == 0 ? blue : green); } }); @@ -143,7 +143,7 @@ void main() { } test('BackdropFilter with Blur honors TileMode.decal', () async { - Image image = BackdropBlurWithTileMode(TileMode.decal); + final Image image = backdropBlurWithTileMode(TileMode.decal); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_decal_tile_mode.png'); @@ -152,7 +152,7 @@ void main() { }); test('BackdropFilter with Blur honors TileMode.clamp', () async { - Image image = BackdropBlurWithTileMode(TileMode.clamp); + final Image image = backdropBlurWithTileMode(TileMode.clamp); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_clamp_tile_mode.png'); @@ -161,7 +161,7 @@ void main() { }); test('BackdropFilter with Blur honors TileMode.mirror', () async { - Image image = BackdropBlurWithTileMode(TileMode.mirror); + final Image image = backdropBlurWithTileMode(TileMode.mirror); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_mirror_tile_mode.png'); @@ -170,7 +170,7 @@ void main() { }); test('BackdropFilter with Blur honors TileMode.repeated', () async { - Image image = BackdropBlurWithTileMode(TileMode.repeated); + final Image image = backdropBlurWithTileMode(TileMode.repeated); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_repeated_tile_mode.png'); From b861995c99ee98a758ddee70d99c18dd4569663b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 4 Oct 2024 03:14:57 -0700 Subject: [PATCH 4/4] more obvious golden images --- testing/dart/painting_test.dart | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/testing/dart/painting_test.dart b/testing/dart/painting_test.dart index 5991171262d40..5ba22945c3767 100644 --- a/testing/dart/painting_test.dart +++ b/testing/dart/painting_test.dart @@ -116,25 +116,41 @@ void main() { callback(canvas); return recorder.endRecording(); } - final SceneBuilder sceneBuilder = SceneBuilder(); + + const double rectSize = 10; + const int count = 50; + const double imgSize = rectSize * count; final Picture blueGreenGridPicture = makePicture((Canvas canvas) { const Color white = Color(0xFFFFFFFF); + const Color purple = Color(0xFFFF00FF); const Color blue = Color(0xFF0000FF); const Color green = Color(0xFF00FF00); + const Color yellow = Color(0xFFFFFF00); + const Color red = Color(0xFFFF0000); canvas.drawColor(white, BlendMode.src); - for (int i = 0; i < 100; i++) { - canvas.drawRect(Rect.fromLTWH(i * 5, 0, 1, 1000), - Paint()..color = (i & 1) == 0 ? green : blue); - canvas.drawRect(Rect.fromLTWH(0, i * 5, 1000, 1), - Paint()..color = (i & 1) == 0 ? blue : green); + for (int i = 0; i < count; i++) { + for (int j = 0; j < count; j++) { + final bool rectOdd = (i + j) & 1 == 0; + final Color fg = (i < count / 2) + ? ((j < count / 2) ? green : blue) + : ((j < count / 2) ? yellow : red); + canvas.drawRect(Rect.fromLTWH(i * rectSize, j * rectSize, rectSize, rectSize), + Paint()..color = rectOdd ? fg : white); + } } + canvas.drawRect(const Rect.fromLTWH(0, 0, imgSize, 1), Paint()..color = purple); + canvas.drawRect(const Rect.fromLTWH(0, 0, 1, imgSize), Paint()..color = purple); + canvas.drawRect(const Rect.fromLTWH(0, imgSize - 1, imgSize, 1), Paint()..color = purple); + canvas.drawRect(const Rect.fromLTWH(imgSize - 1, 0, 1, imgSize), Paint()..color = purple); }); + + final SceneBuilder sceneBuilder = SceneBuilder(); sceneBuilder.addPicture(Offset.zero, blueGreenGridPicture); - sceneBuilder.pushBackdropFilter(ImageFilter.blur(sigmaX: 10, sigmaY: 10, tileMode: tileMode)); + sceneBuilder.pushBackdropFilter(ImageFilter.blur(sigmaX: 20, sigmaY: 20, tileMode: tileMode)); final Scene scene = sceneBuilder.build(); - final Image image = scene.toImageSync(501, 501); + final Image image = scene.toImageSync(imgSize.round(), imgSize.round()); scene.dispose(); blueGreenGridPicture.dispose();