diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart index 2032ccdd88670..8ce379fb1b064 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart @@ -40,6 +40,9 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible { factory CkImageFilter.matrix( {required Float64List matrix, required ui.FilterQuality filterQuality}) = _CkMatrixImageFilter; + factory CkImageFilter.compose( + {required CkImageFilter outer, + required CkImageFilter inner}) = _CkComposeImageFilter; CkImageFilter._(); @@ -194,3 +197,47 @@ class _CkMatrixImageFilter extends CkImageFilter { @override Matrix4 get transform => _transform; } + +class _CkComposeImageFilter extends CkImageFilter { + _CkComposeImageFilter({required this.outer, required this.inner}) + : super._() { + outer.imageFilter((SkImageFilter outerFilter) { + inner.imageFilter((SkImageFilter innerFilter) { + final SkImageFilter skImageFilter = canvasKit.ImageFilter.MakeCompose( + outerFilter, + innerFilter, + ); + _ref = UniqueRef( + this, skImageFilter, 'ImageFilter.compose'); + }); + }); + } + + final CkImageFilter outer; + final CkImageFilter inner; + + late final UniqueRef _ref; + + @override + void imageFilter(SkImageFilterBorrow borrow) { + borrow(_ref.nativeObject); + } + + @override + bool operator ==(Object other) { + if (runtimeType != other.runtimeType) { + return false; + } + return other is _CkComposeImageFilter && + other.outer == outer && + other.inner == inner; + } + + @override + int get hashCode => Object.hash(outer, inner); + + @override + String toString() { + return 'ImageFilter.compose($outer, $inner)'; + } +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index a948b5b55f08c..1caf88ffaa80e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -192,9 +192,18 @@ class CanvasKitRenderer implements Renderer { }) => CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); @override - ui.ImageFilter composeImageFilters({required ui.ImageFilter outer, required ui.ImageFilter inner}) { - // TODO(ferhat): add implementation - throw UnimplementedError('ImageFilter.compose not implemented for CanvasKit.'); + ui.ImageFilter composeImageFilters( + {required ui.ImageFilter outer, required ui.ImageFilter inner}) { + if (outer is EngineColorFilter) { + final CkColorFilter colorFilter = createCkColorFilter(outer)!; + outer = CkColorFilterImageFilter(colorFilter: colorFilter); + } + if (inner is EngineColorFilter) { + final CkColorFilter colorFilter = createCkColorFilter(inner)!; + inner = CkColorFilterImageFilter(colorFilter: colorFilter); + } + return CkImageFilter.compose( + outer: outer as CkImageFilter, inner: inner as CkImageFilter); } @override diff --git a/lib/web_ui/test/canvaskit/filter_test.dart b/lib/web_ui/test/canvaskit/filter_test.dart index 5ca14f831a03c..750df247d85ec 100644 --- a/lib/web_ui/test/canvaskit/filter_test.dart +++ b/lib/web_ui/test/canvaskit/filter_test.dart @@ -38,12 +38,15 @@ void testMain() { } List createImageFilters() { - return [ + final List filters = [ CkImageFilter.blur(sigmaX: 5, sigmaY: 6, tileMode: ui.TileMode.clamp), CkImageFilter.blur(sigmaX: 6, sigmaY: 5, tileMode: ui.TileMode.clamp), CkImageFilter.blur(sigmaX: 6, sigmaY: 5, tileMode: ui.TileMode.decal), for (final CkColorFilter colorFilter in createColorFilters()) CkImageFilter.color(colorFilter: colorFilter), ]; + filters.add(CkImageFilter.compose(outer: filters[0], inner: filters[1])); + filters.add(CkImageFilter.compose(outer: filters[1], inner: filters[3])); + return filters; } setUpCanvasKitTest(); @@ -156,7 +159,47 @@ void testMain() { builder.addPicture(ui.Offset.zero, redCircle1); // The drawn red circle should actually be green with the colorFilter. - await matchSceneGolden('canvaskit_imageFilter_using_colorFilter.png', builder.build(), region: region); + await matchSceneGolden( + 'canvaskit_imageFilter_using_colorFilter.png', builder.build(), + region: region); + }); + + test('using a compose filter', () async { + final CkImageFilter blurFilter = CkImageFilter.blur( + sigmaX: 5, + sigmaY: 5, + tileMode: ui.TileMode.clamp, + ); + final CkColorFilter colorFilter = createCkColorFilter( + const EngineColorFilter.mode( + ui.Color.fromARGB(255, 0, 255, 0), ui.BlendMode.srcIn))!; + final CkImageFilter colorImageFilter = + CkImageFilter.color(colorFilter: colorFilter); + final CkImageFilter composeFilter = + CkImageFilter.compose(outer: blurFilter, inner: colorImageFilter); + + const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250); + + final LayerSceneBuilder builder = LayerSceneBuilder(); + builder.pushOffset(0, 0); + + builder.pushImageFilter(composeFilter); + + 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 redCircle1 = recorder.endRecording(); + builder.addPicture(ui.Offset.zero, redCircle1); + // The drawn red circle should actually be green and blurred. + + await matchSceneGolden( + 'canvaskit_composeImageFilter.png', builder.build(), + region: region); }); }); diff --git a/lib/web_ui/test/ui/filters_test.dart b/lib/web_ui/test/ui/filters_test.dart index a2151002c8e92..b486447c6186c 100644 --- a/lib/web_ui/test/ui/filters_test.dart +++ b/lib/web_ui/test/ui/filters_test.dart @@ -101,7 +101,7 @@ Future testMain() async { ); await drawTestImageWithPaint(ui.Paint()..imageFilter = filter); await matchGoldenFile('ui_filter_composed_imagefilters.png', region: region); - }, skip: !isSkwasm); // Only Skwasm implements composable filters right now. + }, skip: isHtml); // Only Skwasm and CanvasKit implement composable filters right now. test('compose with colorfilter', () async { final ui.ImageFilter filter = ui.ImageFilter.compose( @@ -116,7 +116,7 @@ Future testMain() async { ); await drawTestImageWithPaint(ui.Paint()..imageFilter = filter); await matchGoldenFile('ui_filter_composed_colorfilter.png', region: region); - }, skip: !isSkwasm); // Only Skwasm implements composable filters right now. + }, skip: isHtml); // Only Skwasm and CanvasKit implements composable filters right now. test('color filter as image filter', () async { const ui.ColorFilter colorFilter = ui.ColorFilter.mode(