diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 39608d26cc652..8c0eba1f3daa3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -1352,7 +1352,7 @@ extension SkPaintExtension on SkPaint { @JS('setColorInt') external JSVoid _setColorInt(JSNumber color); - void setColorInt(double color) => _setColorInt(color.toJS); + void setColorInt(int color) => _setColorInt(color.toJS); external JSVoid setShader(SkShader? shader); external JSVoid setMaskFilter(SkMaskFilter? maskFilter); diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 143ff865840a1..325322d799ab4 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -4,9 +4,11 @@ import 'package:ui/ui.dart' as ui; +import '../color_filter.dart'; import '../vector_math.dart'; import 'canvas.dart'; import 'canvaskit_api.dart'; +import 'color_filter.dart'; import 'embedded_views.dart'; import 'image_filter.dart'; import 'n_way_canvas.dart'; @@ -409,12 +411,23 @@ class ImageFilterEngineLayer extends ContainerLayer childMatrix.translate(_offset.dx, _offset.dy); prerollContext.mutatorsStack .pushTransform(Matrix4.translationValues(_offset.dx, _offset.dy, 0.0)); + final CkManagedSkImageFilterConvertible convertible; + if (_filter is ui.ColorFilter) { + convertible = createCkColorFilter(_filter as EngineColorFilter)!; + } else { + convertible = _filter as CkManagedSkImageFilterConvertible; + } final ui.Rect childPaintBounds = prerollChildren(prerollContext, childMatrix); - (_filter as CkManagedSkImageFilterConvertible) - .imageFilter((SkImageFilter filter) { - paintBounds = - rectFromSkIRect(filter.getOutputBounds(toSkRect(childPaintBounds))); + convertible.imageFilter((SkImageFilter filter) { + // If the filter is a ColorFilter, the extended paint bounds will be the + // entire screen, which is not what we want. + if (_filter is ui.ColorFilter) { + paintBounds = childPaintBounds; + } else { + paintBounds = + rectFromSkIRect(filter.getOutputBounds(toSkRect(childPaintBounds))); + } }); prerollContext.mutatorsStack.pop(); } @@ -424,6 +437,8 @@ class ImageFilterEngineLayer extends ContainerLayer assert(needsPainting); paintContext.internalNodesCanvas.save(); paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy); + paintContext.internalNodesCanvas + .clipRect(paintBounds, ui.ClipOp.intersect, false); final CkPaint paint = CkPaint(); paint.imageFilter = _filter; paintContext.internalNodesCanvas.saveLayer(paintBounds, paint); @@ -518,10 +533,21 @@ class ColorFilterEngineLayer extends ContainerLayer final CkPaint paint = CkPaint(); paint.colorFilter = filter; + // We need to clip because if the ColorFilter affects transparent black, + // then it will fill the entire `cullRect` of the picture, ignoring the + // `paintBounds` passed to `saveLayer`. See: + // https://github.com/flutter/flutter/issues/88866 + paintContext.internalNodesCanvas.save(); + + // TODO(hterkelsen): Only clip if the ColorFilter affects transparent black. + paintContext.internalNodesCanvas + .clipRect(paintBounds, ui.ClipOp.intersect, false); + paintContext.internalNodesCanvas.saveLayer(paintBounds, paint); - paint.dispose(); paintChildren(paintContext); paintContext.internalNodesCanvas.restore(); + paintContext.internalNodesCanvas.restore(); + paint.dispose(); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/painting.dart b/lib/web_ui/lib/src/engine/canvaskit/painting.dart index 00bead7240fb6..3bd9f79968edf 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/painting.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/painting.dart @@ -24,7 +24,7 @@ import 'shader.dart'; class CkPaint implements ui.Paint { CkPaint() : skiaObject = SkPaint() { skiaObject.setAntiAlias(_isAntiAlias); - skiaObject.setColorInt(_defaultPaintColor.toDouble()); + skiaObject.setColorInt(_defaultPaintColor); _ref = UniqueRef(this, skiaObject, 'Paint'); } @@ -127,7 +127,7 @@ class CkPaint implements ui.Paint { return; } _color = value.value; - skiaObject.setColorInt(value.value.toDouble()); + skiaObject.setColorInt(value.value); } int _color = _defaultPaintColor; diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 4a928cb3092fb..bdcd666505e5e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -1187,7 +1187,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { SkPaint? foreground = skStyle.foreground?.skiaObject; if (foreground == null) { _defaultTextForeground.setColorInt( - (skStyle.color?.value ?? 0xFF000000).toDouble(), + skStyle.color?.value ?? 0xFF000000, ); foreground = _defaultTextForeground; } diff --git a/lib/web_ui/test/canvaskit/color_filter_golden_test.dart b/lib/web_ui/test/canvaskit/color_filter_golden_test.dart index cf5ba125bbe18..49b8c895f1693 100644 --- a/lib/web_ui/test/canvaskit/color_filter_golden_test.dart +++ b/lib/web_ui/test/canvaskit/color_filter_golden_test.dart @@ -195,6 +195,90 @@ void testMain() { await matchSceneGolden('canvaskit_dst_colorfilter.png', builder.build(), region: region); }); + + test('ColorFilter only applies to child bounds', () async { + final LayerSceneBuilder builder = LayerSceneBuilder(); + + builder.pushOffset(0, 0); + + // Draw a red circle and add it to the scene. + final CkPictureRecorder recorder = CkPictureRecorder(); + final CkCanvas canvas = recorder.beginRecording(region); + + canvas.drawCircle( + const ui.Offset(75, 125), + 50, + CkPaint()..color = const ui.Color.fromARGB(255, 255, 0, 0), + ); + final CkPicture redCircle = recorder.endRecording(); + + builder.addPicture(ui.Offset.zero, redCircle); + + // Apply a green color filter. + builder.pushColorFilter( + const ui.ColorFilter.mode(ui.Color(0xff00ff00), ui.BlendMode.color)); + // Draw another red circle and apply it to the scene. + // This one should be green since we have the color filter. + final CkPictureRecorder recorder2 = CkPictureRecorder(); + final CkCanvas canvas2 = recorder2.beginRecording(region); + + canvas2.drawCircle( + const ui.Offset(425, 125), + 50, + CkPaint()..color = const ui.Color.fromARGB(255, 255, 0, 0), + ); + final CkPicture greenCircle = recorder2.endRecording(); + + builder.addPicture(ui.Offset.zero, greenCircle); + + await matchSceneGolden( + 'canvaskit_colorfilter_bounds.png', + builder.build(), + region: region, + ); + }); + + test('ColorFilter works as an ImageFilter', () async { + final LayerSceneBuilder builder = LayerSceneBuilder(); + + builder.pushOffset(0, 0); + + // Draw a red circle and add it to the scene. + final CkPictureRecorder recorder = CkPictureRecorder(); + final CkCanvas canvas = recorder.beginRecording(region); + + canvas.drawCircle( + const ui.Offset(75, 125), + 50, + CkPaint()..color = const ui.Color.fromARGB(255, 255, 0, 0), + ); + final CkPicture redCircle = recorder.endRecording(); + + builder.addPicture(ui.Offset.zero, redCircle); + + // Apply a green color filter as an ImageFilter. + builder.pushImageFilter( + const ui.ColorFilter.mode(ui.Color(0xff00ff00), ui.BlendMode.color)); + // Draw another red circle and apply it to the scene. + // This one should be green since we have the color filter. + final CkPictureRecorder recorder2 = CkPictureRecorder(); + final CkCanvas canvas2 = recorder2.beginRecording(region); + + canvas2.drawCircle( + const ui.Offset(425, 125), + 50, + CkPaint()..color = const ui.Color.fromARGB(255, 255, 0, 0), + ); + final CkPicture greenCircle = recorder2.endRecording(); + + builder.addPicture(ui.Offset.zero, greenCircle); + + await matchSceneGolden( + 'canvaskit_colorfilter_as_imagefilter.png', + builder.build(), + region: region, + ); + }); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); }