Skip to content

Commit

Permalink
[canvaskit] Fix backdrop filter with platform views as children (flut…
Browse files Browse the repository at this point in the history
…ter#51442)

There is a bug in CanvasKit where a backdrop filter with a platform view
as a child will cause the backdrop filter to apply multiple times. This
change fixes it by only applying the backdrop filter once.

The bug case is more likely to happen since
flutter#47317 landed because when we
encounter an invisible platform view, we always create a new SkPicture
instead of continuing to use the same SkPicture.

BEFORE:

![canvaskit_backdropfilter_with_platformview_BAD](https://github.com/flutter/engine/assets/1961493/c1fa766f-ebf7-4468-9ea7-8ab0d51314c7)

AFTER:

![canvaskit_backdropfilter_with_platformview](https://github.com/flutter/engine/assets/1961493/57d27d62-ee49-43a2-af3f-e532aaba0ffb)


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
  • Loading branch information
harryterkelsen authored Mar 15, 2024
1 parent c958092 commit c2fd533
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 4 deletions.
11 changes: 8 additions & 3 deletions lib/web_ui/lib/src/engine/canvaskit/layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,16 @@ class BackdropFilterEngineLayer extends ContainerLayer
@override
void paint(PaintContext paintContext) {
final CkPaint paint = CkPaint()..blendMode = _blendMode;
paintContext.internalNodesCanvas
.saveLayerWithFilter(paintBounds, _filter, paint);

// Only apply the backdrop filter to the current canvas. If we apply the
// backdrop filter to every canvas (i.e. by applying it to the
// [internalNodesCanvas]), then later when we compose the canvases into a
// single canvas, the backdrop filter will be applied multiple times.
final CkCanvas currentCanvas = paintContext.leafNodesCanvas!;
currentCanvas.saveLayerWithFilter(paintBounds, _filter, paint);
paint.dispose();
paintChildren(paintContext);
paintContext.internalNodesCanvas.restore();
currentCanvas.restore();
}

// TODO(dnfield): dispose of the _filter
Expand Down
82 changes: 81 additions & 1 deletion lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

import 'common.dart';

Expand All @@ -18,7 +19,15 @@ const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 500);
void testMain() {
group('BackdropFilter', () {
setUpCanvasKitTest(withImplicitView: true);
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0);

setUp(() {
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1);
});

tearDown(() {
PlatformViewManager.instance.debugClear();
CanvasKitRenderer.instance.debugClear();
});

test('blur renders to the edges', () async {
// Make a checkerboard picture so we can see the blur.
Expand Down Expand Up @@ -76,6 +85,77 @@ void testMain() {
builder.build(),
region: region);
});

test('works with an invisible platform view inside', () async {
ui_web.platformViewRegistry.registerViewFactory(
'test-platform-view',
(int viewId) => createDomHTMLDivElement()..id = 'view-0',
isVisible: false,
);
await createPlatformView(0, 'test-platform-view');

// Make a checkerboard picture so we can see the blur.
final CkPictureRecorder recorder = CkPictureRecorder();
final CkCanvas canvas = recorder.beginRecording(region);
canvas.drawColor(const ui.Color(0xffffffff), ui.BlendMode.srcOver);
final double sideLength = region.width / 20;
final int rows = (region.height / sideLength).ceil();

for (int row = 0; row < rows; row++) {
for (int column = 0; column < 10; column++) {
final ui.Rect rect = ui.Rect.fromLTWH(
row.isEven
? (column * 2) * sideLength
: (column * 2 + 1) * sideLength,
row * sideLength,
sideLength,
sideLength,
);
canvas.drawRect(rect, CkPaint()..color = const ui.Color(0xffff0000));
}
}
final CkPicture checkerboard = recorder.endRecording();

final LayerSceneBuilder builder = LayerSceneBuilder();
builder.pushOffset(0, 0);
builder.addPicture(ui.Offset.zero, checkerboard);
builder.pushBackdropFilter(ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10));

// Draw a green rectangle, then an invisible platform view, then a blue
// rectangle. Both rectangles should not be blurred.
final CkPictureRecorder greenRectRecorder = CkPictureRecorder();
final CkCanvas greenRectCanvas = greenRectRecorder.beginRecording(region);
final CkPaint greenPaint = CkPaint()..color = const ui.Color(0xff00ff00);
greenRectCanvas.drawRect(
ui.Rect.fromCenter(
center: ui.Offset(region.width / 3, region.height / 2),
width: region.width / 6,
height: region.height / 6),
greenPaint);
final CkPicture greenRectPicture = greenRectRecorder.endRecording();

final CkPictureRecorder blueRectRecorder = CkPictureRecorder();
final CkCanvas blueRectCanvas = blueRectRecorder.beginRecording(region);
final CkPaint bluePaint = CkPaint()..color = const ui.Color(0xff0000ff);
blueRectCanvas.drawRect(
ui.Rect.fromCenter(
center: ui.Offset(2 * region.width / 3, region.height / 2),
width: region.width / 6,
height: region.height / 6),
bluePaint);
final CkPicture blueRectPicture = blueRectRecorder.endRecording();

builder.addPicture(ui.Offset.zero, greenRectPicture);
builder.addPlatformView(0, width: 10, height: 10);
builder.addPicture(ui.Offset.zero, blueRectPicture);

// Pop the backdrop filter layer.
builder.pop();

await matchSceneGolden(
'canvaskit_backdropfilter_with_platformview.png', builder.build(),
region: region);
});
// TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520
}, skip: isSafari || isFirefox);
}

0 comments on commit c2fd533

Please sign in to comment.