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

Commit 5cc7416

Browse files
author
Harry Terkelsen
authored
Update CanvasKit to 0.7.0 and flesh out painting (#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
1 parent 5e65445 commit 5cc7416

File tree

9 files changed

+280
-69
lines changed

9 files changed

+280
-69
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart
354354
FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart
355355
FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart
356356
FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart
357+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart
357358
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas.dart
359+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart
358360
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart
359361
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/fonts.dart
360362
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/image.dart

lib/web_ui/lib/src/engine.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ part 'engine/assets.dart';
2323
part 'engine/bitmap_canvas.dart';
2424
part 'engine/browser_detection.dart';
2525
part 'engine/browser_location.dart';
26+
part 'engine/color_filter.dart';
2627
part 'engine/compositor/canvas.dart';
28+
part 'engine/compositor/color_filter.dart';
2729
part 'engine/compositor/engine_delegate.dart';
2830
part 'engine/compositor/fonts.dart';
2931
part 'engine/compositor/image.dart';
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
part of engine;
6+
7+
/// A description of a color filter to apply when drawing a shape or compositing
8+
/// a layer with a particular [Paint]. A color filter is a function that takes
9+
/// two colors, and outputs one color. When applied during compositing, it is
10+
/// independently applied to each pixel of the layer being drawn before the
11+
/// entire layer is merged with the destination.
12+
///
13+
/// Instances of this class are used with [Paint.colorFilter] on [Paint]
14+
/// objects.
15+
class EngineColorFilter implements ui.ColorFilter {
16+
/// Creates a color filter that applies the blend mode given as the second
17+
/// argument. The source color is the one given as the first argument, and the
18+
/// destination color is the one from the layer being composited.
19+
///
20+
/// The output of this filter is then composited into the background according
21+
/// to the [Paint.blendMode], using the output of this filter as the source
22+
/// and the background as the destination.
23+
const EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode)
24+
: _color = color,
25+
_blendMode = blendMode,
26+
_matrix = null,
27+
_type = _TypeMode;
28+
29+
/// Construct a color filter that transforms a color by a 5x5 matrix, where
30+
/// the fifth row is implicitly added in an identity configuration.
31+
///
32+
/// Every pixel's color value, repsented as an `[R, G, B, A]`, is matrix
33+
/// multiplied to create a new color:
34+
///
35+
/// ```text
36+
/// | R' | | a00 a01 a02 a03 a04 | | R |
37+
/// | G' | | a10 a11 a22 a33 a44 | | G |
38+
/// | B' | = | a20 a21 a22 a33 a44 | * | B |
39+
/// | A' | | a30 a31 a22 a33 a44 | | A |
40+
/// | 1 | | 0 0 0 0 1 | | 1 |
41+
/// ```
42+
///
43+
/// The matrix is in row-major order and the translation column is specified
44+
/// in unnormalized, 0...255, space. For example, the identity matrix is:
45+
///
46+
/// ```
47+
/// const ColorMatrix identity = ColorFilter.matrix(<double>[
48+
/// 1, 0, 0, 0, 0,
49+
/// 0, 1, 0, 0, 0,
50+
/// 0, 0, 1, 0, 0,
51+
/// 0, 0, 0, 1, 0,
52+
/// ]);
53+
/// ```
54+
///
55+
/// ## Examples
56+
///
57+
/// An inversion color matrix:
58+
///
59+
/// ```
60+
/// const ColorFilter invert = ColorFilter.matrix(<double>[
61+
/// -1, 0, 0, 0, 255,
62+
/// 0, -1, 0, 0, 255,
63+
/// 0, 0, -1, 0, 255,
64+
/// 0, 0, 0, 1, 0,
65+
/// ]);
66+
/// ```
67+
///
68+
/// A sepia-toned color matrix (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent)):
69+
///
70+
/// ```
71+
/// const ColorFilter sepia = ColorFilter.matrix(<double>[
72+
/// 0.393, 0.769, 0.189, 0, 0,
73+
/// 0.349, 0.686, 0.168, 0, 0,
74+
/// 0.272, 0.534, 0.131, 0, 0,
75+
/// 0, 0, 0, 1, 0,
76+
/// ]);
77+
/// ```
78+
///
79+
/// A greyscale color filter (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent)):
80+
///
81+
/// ```
82+
/// const ColorFilter greyscale = ColorFilter.matrix(<double>[
83+
/// 0.2126, 0.7152, 0.0722, 0, 0,
84+
/// 0.2126, 0.7152, 0.0722, 0, 0,
85+
/// 0.2126, 0.7152, 0.0722, 0, 0,
86+
/// 0, 0, 0, 1, 0,
87+
/// ]);
88+
/// ```
89+
const EngineColorFilter.matrix(List<double> matrix)
90+
: _color = null,
91+
_blendMode = null,
92+
_matrix = matrix,
93+
_type = _TypeMatrix;
94+
95+
/// Construct a color filter that applies the sRGB gamma curve to the RGB
96+
/// channels.
97+
const EngineColorFilter.linearToSrgbGamma()
98+
: _color = null,
99+
_blendMode = null,
100+
_matrix = null,
101+
_type = _TypeLinearToSrgbGamma;
102+
103+
/// Creates a color filter that applies the inverse of the sRGB gamma curve
104+
/// to the RGB channels.
105+
const EngineColorFilter.srgbToLinearGamma()
106+
: _color = null,
107+
_blendMode = null,
108+
_matrix = null,
109+
_type = _TypeSrgbToLinearGamma;
110+
111+
final ui.Color _color;
112+
final ui.BlendMode _blendMode;
113+
final List<double> _matrix;
114+
final int _type;
115+
116+
// The type of SkColorFilter class to create for Skia.
117+
// These constants must be kept in sync with ColorFilterType in paint.cc.
118+
static const int _TypeNone = 0; // null
119+
static const int _TypeMode = 1; // MakeModeFilter
120+
static const int _TypeMatrix = 2; // MakeMatrixFilterRowMajor255
121+
static const int _TypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma
122+
static const int _TypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma
123+
124+
@override
125+
bool operator ==(dynamic other) {
126+
if (other is! EngineColorFilter) {
127+
return false;
128+
}
129+
final EngineColorFilter typedOther = other;
130+
131+
if (_type != typedOther._type) {
132+
return false;
133+
}
134+
if (!_listEquals<double>(_matrix, typedOther._matrix)) {
135+
return false;
136+
}
137+
138+
return _color == typedOther._color && _blendMode == typedOther._blendMode;
139+
}
140+
141+
SkColorFilter _toSkColorFilter() {
142+
switch (_type) {
143+
case _TypeMode:
144+
if (_color == null || _blendMode == null) {
145+
return null;
146+
}
147+
return SkColorFilter.mode(this);
148+
case _TypeMatrix:
149+
if (_matrix == null) {
150+
return null;
151+
}
152+
assert(_matrix.length == 20, 'Color Matrix must have 20 entries.');
153+
return SkColorFilter.matrix(this);
154+
case _TypeLinearToSrgbGamma:
155+
return SkColorFilter.linearToSrgbGamma(this);
156+
case _TypeSrgbToLinearGamma:
157+
return SkColorFilter.srgbToLinearGamma(this);
158+
default:
159+
throw StateError('Unknown mode $_type for ColorFilter.');
160+
}
161+
}
162+
163+
@override
164+
int get hashCode => ui.hashValues(_color, _blendMode, ui.hashList(_matrix), _type);
165+
166+
@override
167+
String toString() {
168+
switch (_type) {
169+
case _TypeMode:
170+
return 'ColorFilter.mode($_color, $_blendMode)';
171+
case _TypeMatrix:
172+
return 'ColorFilter.matrix($_matrix)';
173+
case _TypeLinearToSrgbGamma:
174+
return 'ColorFilter.linearToSrgbGamma()';
175+
case _TypeSrgbToLinearGamma:
176+
return 'ColorFilter.srgbToLinearGamma()';
177+
default:
178+
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.';
179+
}
180+
}
181+
182+
List<dynamic> webOnlySerializeToCssPaint() {
183+
throw UnsupportedError('ColorFilter for CSS paint not yet supported');
184+
}
185+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
part of engine;
6+
7+
/// A [ui.ColorFilter] backed by Skia's [SkColorFilter].
8+
class SkColorFilter {
9+
js.JsObject skColorFilter;
10+
11+
SkColorFilter.mode(EngineColorFilter filter) {
12+
skColorFilter =
13+
canvasKit['SkColorFilter'].callMethod('MakeBlend', <dynamic>[
14+
filter._color.value,
15+
makeSkBlendMode(filter._blendMode),
16+
]);
17+
}
18+
19+
SkColorFilter.matrix(EngineColorFilter filter) {
20+
// TODO(het): Find a way to remove these array conversions.
21+
final js.JsArray colorMatrix = js.JsArray();
22+
colorMatrix.length = 20;
23+
for (int i = 0; i < 20; i++) {
24+
colorMatrix[i] = filter._matrix[i];
25+
}
26+
skColorFilter = canvasKit['SkColorFilter']
27+
.callMethod('MakeMatrix', <js.JsArray>[colorMatrix]);
28+
}
29+
30+
SkColorFilter.linearToSrgbGamma(EngineColorFilter filter) {
31+
skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeLinearToSRGBGamma');
32+
}
33+
34+
SkColorFilter.srgbToLinearGamma(EngineColorFilter filter) {
35+
skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeSRGBToLinearGamma');
36+
}
37+
}

lib/web_ui/lib/src/engine/compositor/initialization.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const bool experimentalUseSkia =
99
bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false);
1010

1111
/// The URL to use when downloading the CanvasKit script and associated wasm.
12-
const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.6.0/bin/';
12+
const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.7.0/bin/';
1313

1414
/// Initialize the Skia backend.
1515
///

lib/web_ui/lib/src/engine/compositor/path.dart

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -114,26 +114,15 @@ class SkPath implements ui.Path {
114114
double rotation = 0.0,
115115
bool largeArc = false,
116116
bool clockwise = true}) {
117-
assert(rotation == 0.0,
118-
'Skia backend does not support `arcToPoint` rotation.');
119-
assert(!largeArc, 'Skia backend does not support `arcToPoint` largeArc.');
120-
assert(radius.x == radius.y,
121-
'Skia backend does not support `arcToPoint` with elliptical radius.');
122-
123-
// TODO(het): Remove asserts above and use the correct override of `arcTo`
124-
// when it is available in CanvasKit.
125-
// The only `arcTo` method exposed in CanvasKit is:
126-
// arcTo(x1, y1, x2, y2, radius)
127-
final ui.Offset lastPoint = _getCurrentPoint();
128-
_skPath.callMethod('arcTo',
129-
<double>[lastPoint.dx, lastPoint.dy, arcEnd.dx, arcEnd.dy, radius.x]);
130-
}
131-
132-
ui.Offset _getCurrentPoint() {
133-
final int pointCount = _skPath.callMethod('countPoints');
134-
final js.JsObject lastPoint =
135-
_skPath.callMethod('getPoint', <int>[pointCount - 1]);
136-
return ui.Offset(lastPoint[0], lastPoint[1]);
117+
_skPath.callMethod('arcTo', <dynamic>[
118+
radius.x,
119+
radius.y,
120+
rotation,
121+
!largeArc,
122+
!clockwise,
123+
arcEnd.dx,
124+
arcEnd.dy,
125+
]);
137126
}
138127

139128
@override

lib/web_ui/lib/src/engine/compositor/recording_canvas.dart

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ class SkRecordingCanvas implements RecordingCanvas {
9999

100100
@override
101101
void drawCircle(ui.Offset c, double radius, ui.Paint paint) {
102-
final js.JsObject skPaint = makeSkPaint(paint);
103-
// TODO(het): Use `drawCircle` when CanvasKit makes it available.
104-
// Since CanvasKit does not expose `drawCircle`, use `drawOval` instead.
105-
final js.JsObject skRect = makeSkRect(ui.Rect.fromLTWH(
106-
c.dx - radius, c.dy - radius, 2.0 * radius, 2.0 * radius));
107-
skCanvas.callMethod('drawOval', <js.JsObject>[skRect, skPaint]);
102+
skCanvas.callMethod('drawCircle', <dynamic>[
103+
c.dx,
104+
c.dy,
105+
radius,
106+
makeSkPaint(paint),
107+
]);
108108
}
109109

110110
@override
@@ -114,7 +114,11 @@ class SkRecordingCanvas implements RecordingCanvas {
114114

115115
@override
116116
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
117-
throw 'drawDRRect';
117+
skCanvas.callMethod('drawDRRect', <js.JsObject>[
118+
makeSkRRect(outer),
119+
makeSkRRect(inner),
120+
makeSkPaint(paint),
121+
]);
118122
}
119123

120124
@override
@@ -190,19 +194,8 @@ class SkRecordingCanvas implements RecordingCanvas {
190194

191195
@override
192196
void drawRRect(ui.RRect rrect, ui.Paint paint) {
193-
// Since CanvasKit does not expose `drawRRect` we have to make do with
194-
// `drawRoundRect`. The downside of `drawRoundRect` is that all of the
195-
// corner radii must be the same.
196-
assert(
197-
rrect.tlRadius == rrect.trRadius &&
198-
rrect.tlRadius == rrect.brRadius &&
199-
rrect.tlRadius == rrect.blRadius,
200-
'CanvasKit only supports drawing RRects where the radii are all the same.',
201-
);
202-
skCanvas.callMethod('drawRoundRect', <dynamic>[
203-
makeSkRect(rrect.outerRect),
204-
rrect.tlRadiusX,
205-
rrect.tlRadiusY,
197+
skCanvas.callMethod('drawRRect', <js.JsObject>[
198+
makeSkRRect(rrect),
206199
makeSkPaint(paint),
207200
]);
208201
}

lib/web_ui/lib/src/engine/compositor/util.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ js.JsObject makeSkRect(ui.Rect rect) {
99
<double>[rect.left, rect.top, rect.right, rect.bottom]);
1010
}
1111

12+
js.JsObject makeSkRRect(ui.RRect rrect) {
13+
return js.JsObject.jsify({
14+
'rect': makeSkRect(rrect.outerRect),
15+
'rx1': rrect.tlRadiusX,
16+
'ry1': rrect.tlRadiusY,
17+
'rx2': rrect.trRadiusX,
18+
'ry2': rrect.trRadiusY,
19+
'rx3': rrect.brRadiusX,
20+
'ry3': rrect.brRadiusY,
21+
'rx4': rrect.blRadiusX,
22+
'ry4': rrect.blRadiusY,
23+
});
24+
}
25+
1226
js.JsArray<double> makeSkPoint(ui.Offset point) {
1327
final js.JsArray<double> skPoint = js.JsArray<double>();
1428
skPoint.length = 2;
@@ -142,6 +156,12 @@ js.JsObject makeSkPaint(ui.Paint paint) {
142156
skPaint.callMethod('setMaskFilter', <js.JsObject>[skMaskFilter]);
143157
}
144158

159+
if (paint.colorFilter != null) {
160+
EngineColorFilter engineFilter = paint.colorFilter;
161+
SkColorFilter skFilter = engineFilter._toSkColorFilter();
162+
skPaint.callMethod('setColorFilter', <js.JsObject>[skFilter.skColorFilter]);
163+
}
164+
145165
return skPaint;
146166
}
147167

0 commit comments

Comments
 (0)