Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 63484b3

Browse files
[canvaskit] Add ImageFilter.compose (#48546)
Adds ImageFilter.compose support in CanvasKit. Previously this would crash. Fixes flutter/flutter#120123 ## 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
1 parent 0895dee commit 63484b3

4 files changed

Lines changed: 106 additions & 7 deletions

File tree

lib/web_ui/lib/src/engine/canvaskit/image_filter.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible {
4040
factory CkImageFilter.matrix(
4141
{required Float64List matrix,
4242
required ui.FilterQuality filterQuality}) = _CkMatrixImageFilter;
43+
factory CkImageFilter.compose(
44+
{required CkImageFilter outer,
45+
required CkImageFilter inner}) = _CkComposeImageFilter;
4346

4447
CkImageFilter._();
4548

@@ -194,3 +197,47 @@ class _CkMatrixImageFilter extends CkImageFilter {
194197
@override
195198
Matrix4 get transform => _transform;
196199
}
200+
201+
class _CkComposeImageFilter extends CkImageFilter {
202+
_CkComposeImageFilter({required this.outer, required this.inner})
203+
: super._() {
204+
outer.imageFilter((SkImageFilter outerFilter) {
205+
inner.imageFilter((SkImageFilter innerFilter) {
206+
final SkImageFilter skImageFilter = canvasKit.ImageFilter.MakeCompose(
207+
outerFilter,
208+
innerFilter,
209+
);
210+
_ref = UniqueRef<SkImageFilter>(
211+
this, skImageFilter, 'ImageFilter.compose');
212+
});
213+
});
214+
}
215+
216+
final CkImageFilter outer;
217+
final CkImageFilter inner;
218+
219+
late final UniqueRef<SkImageFilter> _ref;
220+
221+
@override
222+
void imageFilter(SkImageFilterBorrow borrow) {
223+
borrow(_ref.nativeObject);
224+
}
225+
226+
@override
227+
bool operator ==(Object other) {
228+
if (runtimeType != other.runtimeType) {
229+
return false;
230+
}
231+
return other is _CkComposeImageFilter &&
232+
other.outer == outer &&
233+
other.inner == inner;
234+
}
235+
236+
@override
237+
int get hashCode => Object.hash(outer, inner);
238+
239+
@override
240+
String toString() {
241+
return 'ImageFilter.compose($outer, $inner)';
242+
}
243+
}

lib/web_ui/lib/src/engine/canvaskit/renderer.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,18 @@ class CanvasKitRenderer implements Renderer {
192192
}) => CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality);
193193

194194
@override
195-
ui.ImageFilter composeImageFilters({required ui.ImageFilter outer, required ui.ImageFilter inner}) {
196-
// TODO(ferhat): add implementation
197-
throw UnimplementedError('ImageFilter.compose not implemented for CanvasKit.');
195+
ui.ImageFilter composeImageFilters(
196+
{required ui.ImageFilter outer, required ui.ImageFilter inner}) {
197+
if (outer is EngineColorFilter) {
198+
final CkColorFilter colorFilter = createCkColorFilter(outer)!;
199+
outer = CkColorFilterImageFilter(colorFilter: colorFilter);
200+
}
201+
if (inner is EngineColorFilter) {
202+
final CkColorFilter colorFilter = createCkColorFilter(inner)!;
203+
inner = CkColorFilterImageFilter(colorFilter: colorFilter);
204+
}
205+
return CkImageFilter.compose(
206+
outer: outer as CkImageFilter, inner: inner as CkImageFilter);
198207
}
199208

200209
@override

lib/web_ui/test/canvaskit/filter_test.dart

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@ void testMain() {
3838
}
3939

4040
List<CkImageFilter> createImageFilters() {
41-
return <CkImageFilter>[
41+
final List<CkImageFilter> filters = <CkImageFilter>[
4242
CkImageFilter.blur(sigmaX: 5, sigmaY: 6, tileMode: ui.TileMode.clamp),
4343
CkImageFilter.blur(sigmaX: 6, sigmaY: 5, tileMode: ui.TileMode.clamp),
4444
CkImageFilter.blur(sigmaX: 6, sigmaY: 5, tileMode: ui.TileMode.decal),
4545
for (final CkColorFilter colorFilter in createColorFilters()) CkImageFilter.color(colorFilter: colorFilter),
4646
];
47+
filters.add(CkImageFilter.compose(outer: filters[0], inner: filters[1]));
48+
filters.add(CkImageFilter.compose(outer: filters[1], inner: filters[3]));
49+
return filters;
4750
}
4851

4952
setUpCanvasKitTest();
@@ -156,7 +159,47 @@ void testMain() {
156159
builder.addPicture(ui.Offset.zero, redCircle1);
157160
// The drawn red circle should actually be green with the colorFilter.
158161

159-
await matchSceneGolden('canvaskit_imageFilter_using_colorFilter.png', builder.build(), region: region);
162+
await matchSceneGolden(
163+
'canvaskit_imageFilter_using_colorFilter.png', builder.build(),
164+
region: region);
165+
});
166+
167+
test('using a compose filter', () async {
168+
final CkImageFilter blurFilter = CkImageFilter.blur(
169+
sigmaX: 5,
170+
sigmaY: 5,
171+
tileMode: ui.TileMode.clamp,
172+
);
173+
final CkColorFilter colorFilter = createCkColorFilter(
174+
const EngineColorFilter.mode(
175+
ui.Color.fromARGB(255, 0, 255, 0), ui.BlendMode.srcIn))!;
176+
final CkImageFilter colorImageFilter =
177+
CkImageFilter.color(colorFilter: colorFilter);
178+
final CkImageFilter composeFilter =
179+
CkImageFilter.compose(outer: blurFilter, inner: colorImageFilter);
180+
181+
const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250);
182+
183+
final LayerSceneBuilder builder = LayerSceneBuilder();
184+
builder.pushOffset(0, 0);
185+
186+
builder.pushImageFilter(composeFilter);
187+
188+
final CkPictureRecorder recorder = CkPictureRecorder();
189+
final CkCanvas canvas = recorder.beginRecording(region);
190+
191+
canvas.drawCircle(
192+
const ui.Offset(75, 125),
193+
50,
194+
CkPaint()..color = const ui.Color.fromARGB(255, 255, 0, 0),
195+
);
196+
final CkPicture redCircle1 = recorder.endRecording();
197+
builder.addPicture(ui.Offset.zero, redCircle1);
198+
// The drawn red circle should actually be green and blurred.
199+
200+
await matchSceneGolden(
201+
'canvaskit_composeImageFilter.png', builder.build(),
202+
region: region);
160203
});
161204
});
162205

lib/web_ui/test/ui/filters_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ Future<void> testMain() async {
101101
);
102102
await drawTestImageWithPaint(ui.Paint()..imageFilter = filter);
103103
await matchGoldenFile('ui_filter_composed_imagefilters.png', region: region);
104-
}, skip: !isSkwasm); // Only Skwasm implements composable filters right now.
104+
}, skip: isHtml); // Only Skwasm and CanvasKit implement composable filters right now.
105105

106106
test('compose with colorfilter', () async {
107107
final ui.ImageFilter filter = ui.ImageFilter.compose(
@@ -116,7 +116,7 @@ Future<void> testMain() async {
116116
);
117117
await drawTestImageWithPaint(ui.Paint()..imageFilter = filter);
118118
await matchGoldenFile('ui_filter_composed_colorfilter.png', region: region);
119-
}, skip: !isSkwasm); // Only Skwasm implements composable filters right now.
119+
}, skip: isHtml); // Only Skwasm and CanvasKit implements composable filters right now.
120120

121121
test('color filter as image filter', () async {
122122
const ui.ColorFilter colorFilter = ui.ColorFilter.mode(

0 commit comments

Comments
 (0)