diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 060e93041ca..051baad13e8 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -354,7 +354,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/image.dart diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine.dart b/engine/src/flutter/lib/web_ui/lib/src/engine.dart index a456ac89992..0579928cfdd 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine.dart @@ -23,7 +23,9 @@ part 'engine/assets.dart'; part 'engine/bitmap_canvas.dart'; part 'engine/browser_detection.dart'; part 'engine/browser_location.dart'; +part 'engine/color_filter.dart'; part 'engine/compositor/canvas.dart'; +part 'engine/compositor/color_filter.dart'; part 'engine/compositor/engine_delegate.dart'; part 'engine/compositor/fonts.dart'; part 'engine/compositor/image.dart'; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/color_filter.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/color_filter.dart new file mode 100644 index 00000000000..48e4b8bb608 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/color_filter.dart @@ -0,0 +1,185 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of engine; + +/// 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 +/// two colors, and outputs one color. When applied during compositing, it is +/// independently applied to each pixel of the layer being drawn before the +/// entire layer is merged with the destination. +/// +/// Instances of this class are used with [Paint.colorFilter] on [Paint] +/// objects. +class EngineColorFilter implements ui.ColorFilter { + /// Creates a color filter that applies the blend mode given as the second + /// argument. The source color is the one given as the first argument, and the + /// destination color is the one from the layer being composited. + /// + /// 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 EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode) + : _color = color, + _blendMode = blendMode, + _matrix = null, + _type = _TypeMode; + + /// Construct a color filter that transforms a color by a 5x5 matrix, where + /// the fifth row is implicitly added in an identity configuration. + /// + /// Every pixel's color value, repsented as an `[R, G, B, A]`, is matrix + /// multiplied to create a new color: + /// + /// ```text + /// | R' | | a00 a01 a02 a03 a04 | | R | + /// | G' | | a10 a11 a22 a33 a44 | | G | + /// | B' | = | a20 a21 a22 a33 a44 | * | B | + /// | A' | | a30 a31 a22 a33 a44 | | A | + /// | 1 | | 0 0 0 0 1 | | 1 | + /// ``` + /// + /// The matrix is in row-major order and the translation column is specified + /// in unnormalized, 0...255, space. For example, the identity matrix is: + /// + /// ``` + /// const ColorMatrix identity = ColorFilter.matrix([ + /// 1, 0, 0, 0, 0, + /// 0, 1, 0, 0, 0, + /// 0, 0, 1, 0, 0, + /// 0, 0, 0, 1, 0, + /// ]); + /// ``` + /// + /// ## Examples + /// + /// An inversion color matrix: + /// + /// ``` + /// const ColorFilter invert = ColorFilter.matrix([ + /// -1, 0, 0, 0, 255, + /// 0, -1, 0, 0, 255, + /// 0, 0, -1, 0, 255, + /// 0, 0, 0, 1, 0, + /// ]); + /// ``` + /// + /// A sepia-toned color matrix (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent)): + /// + /// ``` + /// const ColorFilter sepia = ColorFilter.matrix([ + /// 0.393, 0.769, 0.189, 0, 0, + /// 0.349, 0.686, 0.168, 0, 0, + /// 0.272, 0.534, 0.131, 0, 0, + /// 0, 0, 0, 1, 0, + /// ]); + /// ``` + /// + /// A greyscale color filter (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent)): + /// + /// ``` + /// const ColorFilter greyscale = ColorFilter.matrix([ + /// 0.2126, 0.7152, 0.0722, 0, 0, + /// 0.2126, 0.7152, 0.0722, 0, 0, + /// 0.2126, 0.7152, 0.0722, 0, 0, + /// 0, 0, 0, 1, 0, + /// ]); + /// ``` + const EngineColorFilter.matrix(List matrix) + : _color = null, + _blendMode = null, + _matrix = matrix, + _type = _TypeMatrix; + + /// Construct a color filter that applies the sRGB gamma curve to the RGB + /// channels. + const EngineColorFilter.linearToSrgbGamma() + : _color = null, + _blendMode = null, + _matrix = null, + _type = _TypeLinearToSrgbGamma; + + /// Creates a color filter that applies the inverse of the sRGB gamma curve + /// to the RGB channels. + const EngineColorFilter.srgbToLinearGamma() + : _color = null, + _blendMode = null, + _matrix = null, + _type = _TypeSrgbToLinearGamma; + + final ui.Color _color; + final ui.BlendMode _blendMode; + final List _matrix; + final int _type; + + // The type of SkColorFilter class to create for Skia. + // These constants must be kept in sync with ColorFilterType in paint.cc. + static const int _TypeNone = 0; // null + static const int _TypeMode = 1; // MakeModeFilter + static const int _TypeMatrix = 2; // MakeMatrixFilterRowMajor255 + static const int _TypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma + static const int _TypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma + + @override + bool operator ==(dynamic other) { + if (other is! EngineColorFilter) { + return false; + } + final EngineColorFilter typedOther = other; + + if (_type != typedOther._type) { + return false; + } + if (!_listEquals(_matrix, typedOther._matrix)) { + return false; + } + + return _color == typedOther._color && _blendMode == typedOther._blendMode; + } + + SkColorFilter _toSkColorFilter() { + switch (_type) { + case _TypeMode: + if (_color == null || _blendMode == null) { + return null; + } + return SkColorFilter.mode(this); + case _TypeMatrix: + if (_matrix == null) { + return null; + } + assert(_matrix.length == 20, 'Color Matrix must have 20 entries.'); + return SkColorFilter.matrix(this); + case _TypeLinearToSrgbGamma: + return SkColorFilter.linearToSrgbGamma(this); + case _TypeSrgbToLinearGamma: + return SkColorFilter.srgbToLinearGamma(this); + default: + throw StateError('Unknown mode $_type for ColorFilter.'); + } + } + + @override + int get hashCode => ui.hashValues(_color, _blendMode, ui.hashList(_matrix), _type); + + @override + String toString() { + switch (_type) { + case _TypeMode: + return 'ColorFilter.mode($_color, $_blendMode)'; + case _TypeMatrix: + return 'ColorFilter.matrix($_matrix)'; + case _TypeLinearToSrgbGamma: + return 'ColorFilter.linearToSrgbGamma()'; + case _TypeSrgbToLinearGamma: + return 'ColorFilter.srgbToLinearGamma()'; + default: + return 'Unknown ColorFilter type. This is an error. If you\'re seeing this, please file an issue at https://github.com/flutter/flutter/issues/new.'; + } + } + + List webOnlySerializeToCssPaint() { + throw UnsupportedError('ColorFilter for CSS paint not yet supported'); + } +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart new file mode 100644 index 00000000000..947041e96e4 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of engine; + +/// A [ui.ColorFilter] backed by Skia's [SkColorFilter]. +class SkColorFilter { + js.JsObject skColorFilter; + + SkColorFilter.mode(EngineColorFilter filter) { + skColorFilter = + canvasKit['SkColorFilter'].callMethod('MakeBlend', [ + filter._color.value, + makeSkBlendMode(filter._blendMode), + ]); + } + + SkColorFilter.matrix(EngineColorFilter filter) { + // TODO(het): Find a way to remove these array conversions. + final js.JsArray colorMatrix = js.JsArray(); + colorMatrix.length = 20; + for (int i = 0; i < 20; i++) { + colorMatrix[i] = filter._matrix[i]; + } + skColorFilter = canvasKit['SkColorFilter'] + .callMethod('MakeMatrix', [colorMatrix]); + } + + SkColorFilter.linearToSrgbGamma(EngineColorFilter filter) { + skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeLinearToSRGBGamma'); + } + + SkColorFilter.srgbToLinearGamma(EngineColorFilter filter) { + skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeSRGBToLinearGamma'); + } +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/initialization.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/initialization.dart index 792db2ebcfc..5ac06638edd 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/initialization.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/initialization.dart @@ -9,7 +9,7 @@ const bool experimentalUseSkia = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); /// The URL to use when downloading the CanvasKit script and associated wasm. -const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.6.0/bin/'; +const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.7.0/bin/'; /// Initialize the Skia backend. /// diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/path.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/path.dart index 5e21d8e4fbb..9bd6871fea8 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/path.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/path.dart @@ -114,26 +114,15 @@ class SkPath implements ui.Path { double rotation = 0.0, bool largeArc = false, bool clockwise = true}) { - assert(rotation == 0.0, - 'Skia backend does not support `arcToPoint` rotation.'); - assert(!largeArc, 'Skia backend does not support `arcToPoint` largeArc.'); - assert(radius.x == radius.y, - 'Skia backend does not support `arcToPoint` with elliptical radius.'); - - // TODO(het): Remove asserts above and use the correct override of `arcTo` - // when it is available in CanvasKit. - // The only `arcTo` method exposed in CanvasKit is: - // arcTo(x1, y1, x2, y2, radius) - final ui.Offset lastPoint = _getCurrentPoint(); - _skPath.callMethod('arcTo', - [lastPoint.dx, lastPoint.dy, arcEnd.dx, arcEnd.dy, radius.x]); - } - - ui.Offset _getCurrentPoint() { - final int pointCount = _skPath.callMethod('countPoints'); - final js.JsObject lastPoint = - _skPath.callMethod('getPoint', [pointCount - 1]); - return ui.Offset(lastPoint[0], lastPoint[1]); + _skPath.callMethod('arcTo', [ + radius.x, + radius.y, + rotation, + !largeArc, + !clockwise, + arcEnd.dx, + arcEnd.dy, + ]); } @override diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart index a0951862979..c097e6323a0 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart @@ -99,12 +99,12 @@ class SkRecordingCanvas implements RecordingCanvas { @override void drawCircle(ui.Offset c, double radius, ui.Paint paint) { - final js.JsObject skPaint = makeSkPaint(paint); - // TODO(het): Use `drawCircle` when CanvasKit makes it available. - // Since CanvasKit does not expose `drawCircle`, use `drawOval` instead. - final js.JsObject skRect = makeSkRect(ui.Rect.fromLTWH( - c.dx - radius, c.dy - radius, 2.0 * radius, 2.0 * radius)); - skCanvas.callMethod('drawOval', [skRect, skPaint]); + skCanvas.callMethod('drawCircle', [ + c.dx, + c.dy, + radius, + makeSkPaint(paint), + ]); } @override @@ -114,7 +114,11 @@ class SkRecordingCanvas implements RecordingCanvas { @override void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { - throw 'drawDRRect'; + skCanvas.callMethod('drawDRRect', [ + makeSkRRect(outer), + makeSkRRect(inner), + makeSkPaint(paint), + ]); } @override @@ -190,19 +194,8 @@ class SkRecordingCanvas implements RecordingCanvas { @override void drawRRect(ui.RRect rrect, ui.Paint paint) { - // Since CanvasKit does not expose `drawRRect` we have to make do with - // `drawRoundRect`. The downside of `drawRoundRect` is that all of the - // corner radii must be the same. - assert( - rrect.tlRadius == rrect.trRadius && - rrect.tlRadius == rrect.brRadius && - rrect.tlRadius == rrect.blRadius, - 'CanvasKit only supports drawing RRects where the radii are all the same.', - ); - skCanvas.callMethod('drawRoundRect', [ - makeSkRect(rrect.outerRect), - rrect.tlRadiusX, - rrect.tlRadiusY, + skCanvas.callMethod('drawRRect', [ + makeSkRRect(rrect), makeSkPaint(paint), ]); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/util.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/util.dart index c7457556e9e..079a824a1b2 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/util.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/compositor/util.dart @@ -9,6 +9,20 @@ js.JsObject makeSkRect(ui.Rect rect) { [rect.left, rect.top, rect.right, rect.bottom]); } +js.JsObject makeSkRRect(ui.RRect rrect) { + return js.JsObject.jsify({ + 'rect': makeSkRect(rrect.outerRect), + 'rx1': rrect.tlRadiusX, + 'ry1': rrect.tlRadiusY, + 'rx2': rrect.trRadiusX, + 'ry2': rrect.trRadiusY, + 'rx3': rrect.brRadiusX, + 'ry3': rrect.brRadiusY, + 'rx4': rrect.blRadiusX, + 'ry4': rrect.blRadiusY, + }); +} + js.JsArray makeSkPoint(ui.Offset point) { final js.JsArray skPoint = js.JsArray(); skPoint.length = 2; @@ -142,6 +156,12 @@ js.JsObject makeSkPaint(ui.Paint paint) { skPaint.callMethod('setMaskFilter', [skMaskFilter]); } + if (paint.colorFilter != null) { + EngineColorFilter engineFilter = paint.colorFilter; + SkColorFilter skFilter = engineFilter._toSkColorFilter(); + skPaint.callMethod('setColorFilter', [skFilter.skColorFilter]); + } + return skPaint; } diff --git a/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart b/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart index c994b84848b..74bb88387eb 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart @@ -1388,7 +1388,7 @@ abstract class Image { /// /// Instances of this class are used with [Paint.colorFilter] on [Paint] /// objects. -class ColorFilter { +abstract class ColorFilter { /// Creates a color filter that applies the blend mode given as the second /// argument. The source color is the one given as the first argument, and the /// destination color is the one from the layer being composited. @@ -1396,9 +1396,7 @@ class 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 ColorFilter.mode(Color color, BlendMode blendMode) - : _color = color, - _blendMode = blendMode; + const factory ColorFilter.mode(Color color, BlendMode blendMode) = engine.EngineColorFilter.mode; /// Construct a color filter that transforms a color by a 4x5 matrix. /// @@ -1458,43 +1456,28 @@ class ColorFilter { /// 0, 0, 0, 1, 0, /// ]); /// ``` - const ColorFilter.matrix(List matrix) - : _color = null, - _blendMode = null; + const factory ColorFilter.matrix(List matrix) = engine.EngineColorFilter.matrix; /// Construct a color filter that applies the sRGB gamma curve to the RGB /// channels. - const ColorFilter.linearToSrgbGamma() - : _color = null, - _blendMode = null; + const factory ColorFilter.linearToSrgbGamma() = engine.EngineColorFilter.linearToSrgbGamma; /// Creates a color filter that applies the inverse of the sRGB gamma curve /// to the RGB channels. - const ColorFilter.srgbToLinearGamma() - : _color = null, - _blendMode = null; - - final Color _color; - final BlendMode _blendMode; - - @override - bool operator ==(dynamic other) { - if (other is! ColorFilter) { - return false; - } - final ColorFilter typedOther = other; - return _color == typedOther._color && _blendMode == typedOther._blendMode; - } - - @override - int get hashCode => hashValues(_color, _blendMode); + const factory ColorFilter.srgbToLinearGamma() = engine.EngineColorFilter.srgbToLinearGamma; List webOnlySerializeToCssPaint() { throw UnsupportedError('ColorFilter for CSS paint not yet supported'); } @override - String toString() => 'ColorFilter($_color, $_blendMode)'; + bool operator==(dynamic other); + + @override + int get hashCode; + + @override + String toString(); } /// Styles to use for blurs in [MaskFilter] objects.