Update CanvasKit to 0.7.0 and flesh out painting (flutter/engine#13240)

* Update CanvasKit to 0.7.0 and flesh out painting

This allows us to fix some bugs in the CanvasKit backend.

- Implement RRect where the radii are different
- Implement drawDRRect
- Implement ColorFilter
- Implement the correct `arcTo` for `arcToPoint`

* update licenses

* Respond to review comments

- Add TODO to avoid unnecessary conversions
- Don't set CanvasKit to default
- Fix licenses file

* Add ==, hashCode, and toString back to ColorFilter API
This commit is contained in:
Harry Terkelsen 2019-10-21 11:28:55 -07:00 committed by GitHub
parent 3018e7bcac
commit 02dccbb30e
9 changed files with 281 additions and 70 deletions

View File

@ -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

View File

@ -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';

View File

@ -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(<double>[
/// 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(<double>[
/// -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(<double>[
/// 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(<double>[
/// 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<double> 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<double> _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<double>(_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<dynamic> webOnlySerializeToCssPaint() {
throw UnsupportedError('ColorFilter for CSS paint not yet supported');
}
}

View File

@ -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', <dynamic>[
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', <js.JsArray>[colorMatrix]);
}
SkColorFilter.linearToSrgbGamma(EngineColorFilter filter) {
skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeLinearToSRGBGamma');
}
SkColorFilter.srgbToLinearGamma(EngineColorFilter filter) {
skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeSRGBToLinearGamma');
}
}

View File

@ -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.
///

View File

@ -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',
<double>[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', <int>[pointCount - 1]);
return ui.Offset(lastPoint[0], lastPoint[1]);
_skPath.callMethod('arcTo', <dynamic>[
radius.x,
radius.y,
rotation,
!largeArc,
!clockwise,
arcEnd.dx,
arcEnd.dy,
]);
}
@override

View File

@ -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', <js.JsObject>[skRect, skPaint]);
skCanvas.callMethod('drawCircle', <dynamic>[
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', <js.JsObject>[
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', <dynamic>[
makeSkRect(rrect.outerRect),
rrect.tlRadiusX,
rrect.tlRadiusY,
skCanvas.callMethod('drawRRect', <js.JsObject>[
makeSkRRect(rrect),
makeSkPaint(paint),
]);
}

View File

@ -9,6 +9,20 @@ js.JsObject makeSkRect(ui.Rect rect) {
<double>[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<double> makeSkPoint(ui.Offset point) {
final js.JsArray<double> skPoint = js.JsArray<double>();
skPoint.length = 2;
@ -142,6 +156,12 @@ js.JsObject makeSkPaint(ui.Paint paint) {
skPaint.callMethod('setMaskFilter', <js.JsObject>[skMaskFilter]);
}
if (paint.colorFilter != null) {
EngineColorFilter engineFilter = paint.colorFilter;
SkColorFilter skFilter = engineFilter._toSkColorFilter();
skPaint.callMethod('setColorFilter', <js.JsObject>[skFilter.skColorFilter]);
}
return skPaint;
}

View File

@ -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<double> matrix)
: _color = null,
_blendMode = null;
const factory ColorFilter.matrix(List<double> 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<dynamic> 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.