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

Commit a1dd335

Browse files
authored
colorFilter as imageFilter for web (#37522)
* colorFilter as imageFilter * misplaced early return in backdrop filter * fixed test that did not make sense * addressed comments * removed creator and toRendererColorFilter method * fixed typo and comment * formatting * more formatting
1 parent eedb93e commit a1dd335

16 files changed

+432
-113
lines changed

lib/web_ui/lib/painting.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ abstract class Image {
359359
String toString() => '[$width\u00D7$height]';
360360
}
361361

362-
abstract class ColorFilter {
362+
class ColorFilter implements ImageFilter {
363363
const factory ColorFilter.mode(Color color, BlendMode blendMode) = engine.EngineColorFilter.mode;
364364
const factory ColorFilter.matrix(List<double> matrix) = engine.EngineColorFilter.matrix;
365365
const factory ColorFilter.linearToSrgbGamma() = engine.EngineColorFilter.linearToSrgbGamma;

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import 'dart:typed_data';
77

88
import 'package:ui/ui.dart' as ui;
99

10+
import '../color_filter.dart';
1011
import 'canvaskit_api.dart';
12+
import 'color_filter.dart';
1113
import 'image.dart';
1214
import 'image_filter.dart';
1315
import 'painting.dart';
@@ -290,8 +292,12 @@ class CkCanvas {
290292

291293
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter,
292294
[CkPaint? paint]) {
293-
final CkManagedSkImageFilterConvertible convertible =
294-
filter as CkManagedSkImageFilterConvertible;
295+
final CkManagedSkImageFilterConvertible convertible;
296+
if (filter is ui.ColorFilter) {
297+
convertible = createCkColorFilter(filter as EngineColorFilter)!;
298+
} else {
299+
convertible = filter as CkManagedSkImageFilterConvertible;
300+
}
295301
return skCanvas.saveLayer(
296302
paint?.skiaObject,
297303
toSkRect(bounds),
@@ -1163,8 +1169,12 @@ class CkSaveLayerWithFilterCommand extends CkPaintCommand {
11631169

11641170
@override
11651171
void apply(SkCanvas canvas) {
1166-
final CkManagedSkImageFilterConvertible convertible =
1167-
filter as CkManagedSkImageFilterConvertible;
1172+
final CkManagedSkImageFilterConvertible convertible;
1173+
if (filter is ui.ColorFilter) {
1174+
convertible = createCkColorFilter(filter as EngineColorFilter)!;
1175+
} else {
1176+
convertible = filter as CkManagedSkImageFilterConvertible;
1177+
}
11681178
return canvas.saveLayer(
11691179
paint?.skiaObject,
11701180
toSkRect(bounds),

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class ManagedSkColorFilter extends ManagedSkiaObject<SkColorFilter> {
5656
/// Additionally, this class provides the interface for converting itself to a
5757
/// [ManagedSkiaObject] that manages a skia image filter.
5858
abstract class CkColorFilter
59-
implements CkManagedSkImageFilterConvertible, EngineColorFilter {
59+
implements CkManagedSkImageFilterConvertible {
6060
const CkColorFilter();
6161

6262
/// Called by [ManagedSkiaObject.createDefault] and
@@ -227,3 +227,30 @@ class CkComposeColorFilter extends CkColorFilter {
227227
@override
228228
String toString() => 'ColorFilter.compose($outer, $inner)';
229229
}
230+
231+
/// Convert the current [ColorFilter] to a CkColorFilter.
232+
///
233+
/// This workaround allows ColorFilter to be const constructbile and
234+
/// efficiently comparable, so that widgets can check for ColorFilter equality to
235+
/// avoid repainting.
236+
CkColorFilter? createCkColorFilter(EngineColorFilter colorFilter) {
237+
switch (colorFilter.type) {
238+
case ColorFilterType.mode:
239+
if (colorFilter.color == null || colorFilter.blendMode == null) {
240+
return null;
241+
}
242+
return CkBlendModeColorFilter(colorFilter.color!, colorFilter.blendMode!);
243+
case ColorFilterType.matrix:
244+
if (colorFilter.matrix == null) {
245+
return null;
246+
}
247+
assert(colorFilter.matrix!.length == 20, 'Color Matrix must have 20 entries.');
248+
return CkMatrixColorFilter(colorFilter.matrix!);
249+
case ColorFilterType.linearToSrgbGamma:
250+
return const CkLinearToSrgbGammaColorFilter();
251+
case ColorFilterType.srgbToLinearGamma:
252+
return const CkSrgbToLinearGammaColorFilter();
253+
default:
254+
throw StateError('Unknown mode $colorFilter.type for ColorFilter.');
255+
}
256+
}

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:typed_data';
77

88
import 'package:ui/ui.dart' as ui;
99

10+
import '../color_filter.dart';
1011
import 'canvaskit_api.dart';
1112
import 'color_filter.dart';
1213
import 'image_filter.dart';
@@ -131,7 +132,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
131132
_effectiveColorFilter = _invertColorFilter;
132133
} else {
133134
_effectiveColorFilter = ManagedSkColorFilter(
134-
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!));
135+
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!)
136+
);
135137
}
136138
}
137139
skiaObject.setColorFilter(_effectiveColorFilter?.skiaObject);
@@ -201,20 +203,23 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
201203
}
202204

203205
ui.FilterQuality _filterQuality = ui.FilterQuality.none;
206+
EngineColorFilter? _engineColorFilter;
204207

205208
@override
206-
ui.ColorFilter? get colorFilter => _effectiveColorFilter?.colorFilter;
209+
ui.ColorFilter? get colorFilter => _engineColorFilter;
210+
207211
@override
208212
set colorFilter(ui.ColorFilter? value) {
209-
if (colorFilter == value) {
213+
if (_engineColorFilter == value) {
210214
return;
211215
}
212-
216+
_engineColorFilter = value as EngineColorFilter?;
213217
_originalColorFilter = null;
214218
if (value == null) {
215219
_effectiveColorFilter = null;
216220
} else {
217-
_effectiveColorFilter = ManagedSkColorFilter(value as CkColorFilter);
221+
final CkColorFilter ckColorFilter = createCkColorFilter(value)!;
222+
_effectiveColorFilter = ManagedSkColorFilter(ckColorFilter);
218223
}
219224

220225
if (invertColors) {
@@ -223,7 +228,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
223228
_effectiveColorFilter = _invertColorFilter;
224229
} else {
225230
_effectiveColorFilter = ManagedSkColorFilter(
226-
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!));
231+
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!)
232+
);
227233
}
228234
}
229235

@@ -255,8 +261,12 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
255261
if (_imageFilter == value) {
256262
return;
257263
}
258-
259-
_imageFilter = value as CkManagedSkImageFilterConvertible?;
264+
if (value is ui.ColorFilter) {
265+
_imageFilter = createCkColorFilter(value as EngineColorFilter);
266+
}
267+
else {
268+
_imageFilter = value as CkManagedSkImageFilterConvertible?;
269+
}
260270
_managedImageFilter = _imageFilter?.imageFilter;
261271
skiaObject.setImageFilter(_managedImageFilter?.skiaObject);
262272
}
@@ -305,8 +315,7 @@ final Float32List _invertColorMatrix = Float32List.fromList(const <double>[
305315
1.0, 1.0, 1.0, 1.0, 0
306316
]);
307317

308-
final ManagedSkColorFilter _invertColorFilter =
309-
ManagedSkColorFilter(CkMatrixColorFilter(_invertColorMatrix));
318+
final ManagedSkColorFilter _invertColorFilter = ManagedSkColorFilter(CkMatrixColorFilter(_invertColorMatrix));
310319

311320
class UniformData {
312321
const UniformData({

lib/web_ui/lib/src/engine/color_filter.dart

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
import 'package:ui/ui.dart' as ui;
66

7-
import 'canvaskit/color_filter.dart';
7+
enum ColorFilterType {
8+
mode,
9+
matrix,
10+
linearToSrgbGamma,
11+
srgbToLinearGamma,
12+
}
813

914
/// A description of a color filter to apply when drawing a shape or compositing
1015
/// a layer with a particular [Paint]. A color filter is a function that takes
@@ -22,8 +27,9 @@ class EngineColorFilter implements ui.ColorFilter {
2227
/// The output of this filter is then composited into the background according
2328
/// to the [Paint.blendMode], using the output of this filter as the source
2429
/// and the background as the destination.
25-
const factory EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode) =
26-
CkBlendModeColorFilter;
30+
const EngineColorFilter.mode(ui.Color this.color, ui.BlendMode this.blendMode)
31+
: matrix = null,
32+
type = ColorFilterType.mode;
2733

2834
/// Construct a color filter that transforms a color by a 5x5 matrix, where
2935
/// the fifth row is implicitly added in an identity configuration.
@@ -85,16 +91,29 @@ class EngineColorFilter implements ui.ColorFilter {
8591
/// 0, 0, 0, 1, 0,
8692
/// ]);
8793
/// ```
88-
const factory EngineColorFilter.matrix(List<double> matrix) =
89-
CkMatrixColorFilter;
94+
const EngineColorFilter.matrix(List<double> this.matrix)
95+
: color = null,
96+
blendMode = null,
97+
type = ColorFilterType.matrix;
9098

9199
/// Construct a color filter that applies the sRGB gamma curve to the RGB
92100
/// channels.
93-
const factory EngineColorFilter.linearToSrgbGamma() =
94-
CkLinearToSrgbGammaColorFilter;
101+
const EngineColorFilter.linearToSrgbGamma()
102+
: color = null,
103+
blendMode = null,
104+
matrix = null,
105+
type = ColorFilterType.linearToSrgbGamma;
95106

96107
/// Creates a color filter that applies the inverse of the sRGB gamma curve
97108
/// to the RGB channels.
98-
const factory EngineColorFilter.srgbToLinearGamma() =
99-
CkSrgbToLinearGammaColorFilter;
109+
const EngineColorFilter.srgbToLinearGamma()
110+
: color = null,
111+
blendMode = null,
112+
matrix = null,
113+
type = ColorFilterType.srgbToLinearGamma;
114+
115+
final ui.Color? color;
116+
final ui.BlendMode? blendMode;
117+
final List<double>? matrix;
118+
final ColorFilterType type;
100119
}

lib/web_ui/lib/src/engine/html/backdrop_filter.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import 'package:ui/ui.dart' as ui;
66

77
import '../browser_detection.dart';
8+
import '../color_filter.dart';
89
import '../dom.dart';
10+
import '../embedder.dart';
911
import '../util.dart';
1012
import '../vector_math.dart';
1113
import 'shaders/shader.dart';
@@ -17,7 +19,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface
1719
implements ui.BackdropFilterEngineLayer {
1820
PersistedBackdropFilter(PersistedBackdropFilter? super.oldLayer, this.filter);
1921

20-
final EngineImageFilter filter;
22+
final ui.ImageFilter filter;
2123

2224
/// The dedicated child container element that's separate from the
2325
/// [rootElement] is used to host child in front of [filterElement] that
@@ -26,6 +28,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface
2628
DomElement? get childContainer => _childContainer;
2729
DomElement? _childContainer;
2830
DomElement? _filterElement;
31+
DomElement? _svgFilter;
2932
ui.Rect? _activeClipBounds;
3033
// Cached inverted transform for [transform].
3134
late Matrix4 _invertedTransform;
@@ -37,6 +40,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface
3740
super.adoptElements(oldSurface);
3841
_childContainer = oldSurface._childContainer;
3942
_filterElement = oldSurface._filterElement;
43+
_svgFilter = oldSurface._svgFilter;
4044
oldSurface._childContainer = null;
4145
}
4246

@@ -62,12 +66,22 @@ class PersistedBackdropFilter extends PersistedContainerSurface
6266
// Do not detach the child container from the root. It is permanently
6367
// attached. The elements are reused together and are detached from the DOM
6468
// together.
69+
flutterViewEmbedder.removeResource(_svgFilter);
70+
_svgFilter = null;
6571
_childContainer = null;
6672
_filterElement = null;
6773
}
6874

6975
@override
7076
void apply() {
77+
EngineImageFilter backendFilter;
78+
if (filter is ui.ColorFilter) {
79+
backendFilter = createHtmlColorFilter(filter as EngineColorFilter)!;
80+
} else {
81+
backendFilter = filter as EngineImageFilter;
82+
}
83+
flutterViewEmbedder.removeResource(_svgFilter);
84+
_svgFilter = null;
7185
if (_previousTransform != transform) {
7286
_invertedTransform = Matrix4.inverted(transform!);
7387
_previousTransform = transform;
@@ -115,14 +129,24 @@ class PersistedBackdropFilter extends PersistedContainerSurface
115129
..backgroundColor = '#000'
116130
..opacity = '0.2';
117131
} else {
132+
if (backendFilter is ModeHtmlColorFilter) {
133+
_svgFilter = backendFilter.makeSvgFilter(_filterElement);
134+
/// Some blendModes do not make an svgFilter. See [EngineHtmlColorFilter.makeSvgFilter()]
135+
if (_svgFilter == null) {
136+
return;
137+
}
138+
} else if (backendFilter is MatrixHtmlColorFilter) {
139+
_svgFilter = backendFilter.makeSvgFilter(_filterElement);
140+
}
141+
118142
// CSS uses pixel radius for blur. Flutter & SVG use sigma parameters. For
119143
// Gaussian blur with standard deviation (normal distribution),
120144
// the blur will fall within 2 * sigma pixels.
121145
if (browserEngine == BrowserEngine.webkit) {
122146
setElementStyle(_filterElement!, '-webkit-backdrop-filter',
123-
filter.filterAttribute);
147+
backendFilter.filterAttribute);
124148
}
125-
setElementStyle(_filterElement!, 'backdrop-filter', filter.filterAttribute);
149+
setElementStyle(_filterElement!, 'backdrop-filter', backendFilter.filterAttribute);
126150
}
127151
}
128152

lib/web_ui/lib/src/engine/html/bitmap_canvas.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import 'package:ui/ui.dart' as ui;
99

1010
import '../browser_detection.dart';
1111
import '../canvas_pool.dart';
12-
import '../canvaskit/color_filter.dart';
13-
import '../color_filter.dart';
1412
import '../dom.dart';
1513
import '../engine_canvas.dart';
1614
import '../frame_reference.dart';
@@ -28,6 +26,7 @@ import 'path/path.dart';
2826
import 'recording_canvas.dart';
2927
import 'render_vertices.dart';
3028
import 'shaders/image_shader.dart';
29+
import 'shaders/shader.dart';
3130

3231
/// A raw HTML canvas that is directly written to.
3332
class BitmapCanvas extends EngineCanvas {
@@ -647,13 +646,12 @@ class BitmapCanvas extends EngineCanvas {
647646
ui.Image image, ui.Offset p, SurfacePaintData paint) {
648647
final HtmlImage htmlImage = image as HtmlImage;
649648
final ui.BlendMode? blendMode = paint.blendMode;
650-
final EngineColorFilter? colorFilter =
651-
paint.colorFilter as EngineColorFilter?;
649+
final EngineHtmlColorFilter? colorFilter = createHtmlColorFilter(paint.colorFilter);
652650
DomHTMLElement imgElement;
653-
if (colorFilter is CkBlendModeColorFilter) {
651+
if (colorFilter is ModeHtmlColorFilter) {
654652
imgElement = _createImageElementWithBlend(
655653
image, colorFilter.color, colorFilter.blendMode, paint);
656-
} else if (colorFilter is CkMatrixColorFilter) {
654+
} else if (colorFilter is MatrixHtmlColorFilter) {
657655
imgElement = _createImageElementWithSvgColorMatrixFilter(
658656
image, colorFilter.matrix, paint);
659657
} else {

0 commit comments

Comments
 (0)