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

colorFilter as imageFilter for web #37522

Merged
merged 8 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/web_ui/lib/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ abstract class Image {
String toString() => '[$width\u00D7$height]';
}

abstract class ColorFilter {
class ColorFilter implements ImageFilter {
const factory ColorFilter.mode(Color color, BlendMode blendMode) = engine.EngineColorFilter.mode;
const factory ColorFilter.matrix(List<double> matrix) = engine.EngineColorFilter.matrix;
const factory ColorFilter.linearToSrgbGamma() = engine.EngineColorFilter.linearToSrgbGamma;
Expand Down
18 changes: 14 additions & 4 deletions lib/web_ui/lib/src/engine/canvaskit/canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import 'dart:typed_data';

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

import '../color_filter.dart';
import 'canvaskit_api.dart';
import 'color_filter.dart';
import 'image.dart';
import 'image_filter.dart';
import 'painting.dart';
Expand Down Expand Up @@ -290,8 +292,12 @@ class CkCanvas {

void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter,
[CkPaint? paint]) {
final CkManagedSkImageFilterConvertible convertible =
filter as CkManagedSkImageFilterConvertible;
final CkManagedSkImageFilterConvertible convertible;
if (filter is ui.ColorFilter) {
convertible = createCkColorFilter(filter as EngineColorFilter)!;
} else {
convertible = filter as CkManagedSkImageFilterConvertible;
}
return skCanvas.saveLayer(
paint?.skiaObject,
toSkRect(bounds),
Expand Down Expand Up @@ -1163,8 +1169,12 @@ class CkSaveLayerWithFilterCommand extends CkPaintCommand {

@override
void apply(SkCanvas canvas) {
final CkManagedSkImageFilterConvertible convertible =
filter as CkManagedSkImageFilterConvertible;
final CkManagedSkImageFilterConvertible convertible;
if (filter is ui.ColorFilter) {
convertible = createCkColorFilter(filter as EngineColorFilter)!;
} else {
convertible = filter as CkManagedSkImageFilterConvertible;
}
return canvas.saveLayer(
paint?.skiaObject,
toSkRect(bounds),
Expand Down
29 changes: 28 additions & 1 deletion lib/web_ui/lib/src/engine/canvaskit/color_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class ManagedSkColorFilter extends ManagedSkiaObject<SkColorFilter> {
/// Additionally, this class provides the interface for converting itself to a
/// [ManagedSkiaObject] that manages a skia image filter.
abstract class CkColorFilter
implements CkManagedSkImageFilterConvertible, EngineColorFilter {
implements CkManagedSkImageFilterConvertible {
const CkColorFilter();

/// Called by [ManagedSkiaObject.createDefault] and
Expand Down Expand Up @@ -227,3 +227,30 @@ class CkComposeColorFilter extends CkColorFilter {
@override
String toString() => 'ColorFilter.compose($outer, $inner)';
}

/// Convert the current [ColorFilter] to a CkColorFilter.
///
/// This workaround allows ColorFilter to be const constructbile and
/// efficiently comparable, so that widgets can check for ColorFilter equality to
/// avoid repainting.
CkColorFilter? createCkColorFilter(EngineColorFilter colorFilter) {
switch (colorFilter.type) {
case ColorFilterType.mode:
if (colorFilter.color == null || colorFilter.blendMode == null) {
return null;
}
return CkBlendModeColorFilter(colorFilter.color!, colorFilter.blendMode!);
case ColorFilterType.matrix:
if (colorFilter.matrix == null) {
return null;
}
assert(colorFilter.matrix!.length == 20, 'Color Matrix must have 20 entries.');
return CkMatrixColorFilter(colorFilter.matrix!);
case ColorFilterType.linearToSrgbGamma:
return const CkLinearToSrgbGammaColorFilter();
case ColorFilterType.srgbToLinearGamma:
return const CkSrgbToLinearGammaColorFilter();
default:
throw StateError('Unknown mode $colorFilter.type for ColorFilter.');
}
}
29 changes: 19 additions & 10 deletions lib/web_ui/lib/src/engine/canvaskit/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:typed_data';

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

import '../color_filter.dart';
import 'canvaskit_api.dart';
import 'color_filter.dart';
import 'image_filter.dart';
Expand Down Expand Up @@ -131,7 +132,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
_effectiveColorFilter = _invertColorFilter;
} else {
_effectiveColorFilter = ManagedSkColorFilter(
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!));
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!)
);
}
}
skiaObject.setColorFilter(_effectiveColorFilter?.skiaObject);
Expand Down Expand Up @@ -201,20 +203,23 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
}

ui.FilterQuality _filterQuality = ui.FilterQuality.none;
EngineColorFilter? _engineColorFilter;

@override
ui.ColorFilter? get colorFilter => _effectiveColorFilter?.colorFilter;
ui.ColorFilter? get colorFilter => _engineColorFilter;

@override
set colorFilter(ui.ColorFilter? value) {
if (colorFilter == value) {
if (_engineColorFilter == value) {
return;
}

_engineColorFilter = value as EngineColorFilter?;
_originalColorFilter = null;
if (value == null) {
_effectiveColorFilter = null;
} else {
_effectiveColorFilter = ManagedSkColorFilter(value as CkColorFilter);
final CkColorFilter ckColorFilter = createCkColorFilter(value)!;
_effectiveColorFilter = ManagedSkColorFilter(ckColorFilter);
}

if (invertColors) {
Expand All @@ -223,7 +228,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
_effectiveColorFilter = _invertColorFilter;
} else {
_effectiveColorFilter = ManagedSkColorFilter(
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!));
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!)
);
}
}

Expand Down Expand Up @@ -255,8 +261,12 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
if (_imageFilter == value) {
return;
}

_imageFilter = value as CkManagedSkImageFilterConvertible?;
if (value is ui.ColorFilter) {
_imageFilter = createCkColorFilter(value as EngineColorFilter);
}
else {
_imageFilter = value as CkManagedSkImageFilterConvertible?;
}
_managedImageFilter = _imageFilter?.imageFilter;
skiaObject.setImageFilter(_managedImageFilter?.skiaObject);
}
Expand Down Expand Up @@ -305,8 +315,7 @@ final Float32List _invertColorMatrix = Float32List.fromList(const <double>[
1.0, 1.0, 1.0, 1.0, 0
]);

final ManagedSkColorFilter _invertColorFilter =
ManagedSkColorFilter(CkMatrixColorFilter(_invertColorMatrix));
final ManagedSkColorFilter _invertColorFilter = ManagedSkColorFilter(CkMatrixColorFilter(_invertColorMatrix));

class UniformData {
const UniformData({
Expand Down
37 changes: 28 additions & 9 deletions lib/web_ui/lib/src/engine/color_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

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

import 'canvaskit/color_filter.dart';
enum ColorFilterType {
mode,
matrix,
linearToSrgbGamma,
srgbToLinearGamma,
}

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

/// Construct a color filter that transforms a color by a 5x5 matrix, where
/// the fifth row is implicitly added in an identity configuration.
Expand Down Expand Up @@ -85,16 +91,29 @@ class EngineColorFilter implements ui.ColorFilter {
/// 0, 0, 0, 1, 0,
/// ]);
/// ```
const factory EngineColorFilter.matrix(List<double> matrix) =
CkMatrixColorFilter;
const EngineColorFilter.matrix(List<double> this.matrix)
: color = null,
blendMode = null,
type = ColorFilterType.matrix;

/// Construct a color filter that applies the sRGB gamma curve to the RGB
/// channels.
const factory EngineColorFilter.linearToSrgbGamma() =
CkLinearToSrgbGammaColorFilter;
const EngineColorFilter.linearToSrgbGamma()
: color = null,
blendMode = null,
matrix = null,
type = ColorFilterType.linearToSrgbGamma;

/// Creates a color filter that applies the inverse of the sRGB gamma curve
/// to the RGB channels.
const factory EngineColorFilter.srgbToLinearGamma() =
CkSrgbToLinearGammaColorFilter;
const EngineColorFilter.srgbToLinearGamma()
: color = null,
blendMode = null,
matrix = null,
type = ColorFilterType.srgbToLinearGamma;

final ui.Color? color;
final ui.BlendMode? blendMode;
final List<double>? matrix;
final ColorFilterType type;
}
30 changes: 27 additions & 3 deletions lib/web_ui/lib/src/engine/html/backdrop_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import 'package:ui/ui.dart' as ui;

import '../browser_detection.dart';
import '../color_filter.dart';
import '../dom.dart';
import '../embedder.dart';
import '../util.dart';
import '../vector_math.dart';
import 'shaders/shader.dart';
Expand All @@ -17,7 +19,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface
implements ui.BackdropFilterEngineLayer {
PersistedBackdropFilter(PersistedBackdropFilter? super.oldLayer, this.filter);

final EngineImageFilter filter;
final ui.ImageFilter filter;

/// The dedicated child container element that's separate from the
/// [rootElement] is used to host child in front of [filterElement] that
Expand All @@ -26,6 +28,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface
DomElement? get childContainer => _childContainer;
DomElement? _childContainer;
DomElement? _filterElement;
DomElement? _svgFilter;
ui.Rect? _activeClipBounds;
// Cached inverted transform for [transform].
late Matrix4 _invertedTransform;
Expand All @@ -37,6 +40,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface
super.adoptElements(oldSurface);
_childContainer = oldSurface._childContainer;
_filterElement = oldSurface._filterElement;
_svgFilter = oldSurface._svgFilter;
oldSurface._childContainer = null;
}

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

@override
void apply() {
EngineImageFilter backendFilter;
if (filter is ui.ColorFilter) {
backendFilter = createHtmlColorFilter(filter as EngineColorFilter)!;
} else {
backendFilter = filter as EngineImageFilter;
}
flutterViewEmbedder.removeResource(_svgFilter);
_svgFilter = null;
if (_previousTransform != transform) {
_invertedTransform = Matrix4.inverted(transform!);
_previousTransform = transform;
Expand Down Expand Up @@ -115,14 +129,24 @@ class PersistedBackdropFilter extends PersistedContainerSurface
..backgroundColor = '#000'
..opacity = '0.2';
} else {
if (backendFilter is ModeHtmlColorFilter) {
_svgFilter = backendFilter.makeSvgFilter(_filterElement);
/// Some blendModes do not make an svgFilter. See [EngineHtmlColorFilter.makeSvgFilter()]
if (_svgFilter == null) {
return;
}
} else if (backendFilter is MatrixHtmlColorFilter) {
_svgFilter = backendFilter.makeSvgFilter(_filterElement);
}

// CSS uses pixel radius for blur. Flutter & SVG use sigma parameters. For
// Gaussian blur with standard deviation (normal distribution),
// the blur will fall within 2 * sigma pixels.
if (browserEngine == BrowserEngine.webkit) {
setElementStyle(_filterElement!, '-webkit-backdrop-filter',
filter.filterAttribute);
backendFilter.filterAttribute);
}
setElementStyle(_filterElement!, 'backdrop-filter', filter.filterAttribute);
setElementStyle(_filterElement!, 'backdrop-filter', backendFilter.filterAttribute);
}
}

Expand Down
10 changes: 4 additions & 6 deletions lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import 'package:ui/ui.dart' as ui;

import '../browser_detection.dart';
import '../canvas_pool.dart';
import '../canvaskit/color_filter.dart';
import '../color_filter.dart';
import '../dom.dart';
import '../engine_canvas.dart';
import '../frame_reference.dart';
Expand All @@ -28,6 +26,7 @@ import 'path/path.dart';
import 'recording_canvas.dart';
import 'render_vertices.dart';
import 'shaders/image_shader.dart';
import 'shaders/shader.dart';

/// A raw HTML canvas that is directly written to.
class BitmapCanvas extends EngineCanvas {
Expand Down Expand Up @@ -647,13 +646,12 @@ class BitmapCanvas extends EngineCanvas {
ui.Image image, ui.Offset p, SurfacePaintData paint) {
final HtmlImage htmlImage = image as HtmlImage;
final ui.BlendMode? blendMode = paint.blendMode;
final EngineColorFilter? colorFilter =
paint.colorFilter as EngineColorFilter?;
final EngineHtmlColorFilter? colorFilter = createHtmlColorFilter(paint.colorFilter);
DomHTMLElement imgElement;
if (colorFilter is CkBlendModeColorFilter) {
if (colorFilter is ModeHtmlColorFilter) {
imgElement = _createImageElementWithBlend(
image, colorFilter.color, colorFilter.blendMode, paint);
} else if (colorFilter is CkMatrixColorFilter) {
} else if (colorFilter is MatrixHtmlColorFilter) {
imgElement = _createImageElementWithSvgColorMatrixFilter(
image, colorFilter.matrix, paint);
} else {
Expand Down
Loading