Skip to content

Commit c99deb0

Browse files
authored
CkPaint uses SkPaint (flutter#19562)
1 parent 8063923 commit c99deb0

File tree

8 files changed

+445
-226
lines changed

8 files changed

+445
-226
lines changed

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

+166-5
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,65 @@
55
/// Bindings for CanvasKit JavaScript API.
66
part of engine;
77

8+
final js.JsObject _jsWindow = js.JsObject.fromBrowserObject(html.window);
9+
10+
/// This and [_jsObjectWrapper] below are used to convert `@JS`-backed
11+
/// objects to [js.JsObject]s. To do that we use `@JS` to pass the object
12+
/// to JavaScript (see [JsObjectWrapper]), then use this variable (which
13+
/// uses `dart:js`) to read the value back, causing it to be wrapped in
14+
/// [js.JsObject].
15+
///
16+
// TODO(yjbanov): this is a temporary hack until we fully migrate to @JS.
17+
final js.JsObject _jsObjectWrapperLegacy = js.JsObject(js.context['Object']);
18+
19+
@JS('window.flutter_js_object_wrapper')
20+
external JsObjectWrapper get _jsObjectWrapper;
21+
822
void initializeCanvasKitBindings(js.JsObject canvasKit) {
923
// Because JsObject cannot be cast to a @JS type, we stash CanvasKit into
1024
// a global and use the [canvasKitJs] getter to access it.
11-
js.JsObject.fromBrowserObject(html.window)['flutter_canvas_kit'] = canvasKit;
25+
_jsWindow['flutter_canvas_kit'] = canvasKit;
26+
_jsWindow['flutter_js_object_wrapper'] = _jsObjectWrapperLegacy;
27+
}
28+
29+
@JS()
30+
class JsObjectWrapper {
31+
external set skPaint(SkPaint? paint);
32+
external set skMaskFilter(SkMaskFilter? filter);
33+
external set skColorFilter(SkColorFilter? filter);
34+
external set skImageFilter(SkImageFilter? filter);
35+
}
36+
37+
/// Specific methods that wrap `@JS`-backed objects into a [js.JsObject]
38+
/// for use with legacy `dart:js` API.
39+
extension JsObjectWrappers on JsObjectWrapper {
40+
js.JsObject wrapSkPaint(SkPaint paint) {
41+
_jsObjectWrapper.skPaint = paint;
42+
js.JsObject wrapped = _jsObjectWrapperLegacy['skPaint'];
43+
_jsObjectWrapper.skPaint = null;
44+
return wrapped;
45+
}
46+
47+
js.JsObject wrapSkMaskFilter(SkMaskFilter filter) {
48+
_jsObjectWrapper.skMaskFilter = filter;
49+
js.JsObject wrapped = _jsObjectWrapperLegacy['skMaskFilter'];
50+
_jsObjectWrapper.skMaskFilter = null;
51+
return wrapped;
52+
}
53+
54+
js.JsObject wrapSkColorFilter(SkColorFilter filter) {
55+
_jsObjectWrapper.skColorFilter = filter;
56+
js.JsObject wrapped = _jsObjectWrapperLegacy['skColorFilter'];
57+
_jsObjectWrapper.skColorFilter = null;
58+
return wrapped;
59+
}
60+
61+
js.JsObject wrapSkImageFilter(SkImageFilter filter) {
62+
_jsObjectWrapper.skImageFilter = filter;
63+
js.JsObject wrapped = _jsObjectWrapperLegacy['skImageFilter'];
64+
_jsObjectWrapper.skImageFilter = null;
65+
return wrapped;
66+
}
1267
}
1368

1469
@JS('window.flutter_canvas_kit')
@@ -317,12 +372,12 @@ class SkPaint {
317372
external void setStrokeJoin(SkStrokeJoin join);
318373
external void setAntiAlias(bool isAntiAlias);
319374
external void setColorInt(int color);
320-
external void setShader(SkShader shader);
321-
external void setMaskFilter(SkMaskFilter maskFilter);
375+
external void setShader(SkShader? shader);
376+
external void setMaskFilter(SkMaskFilter? maskFilter);
322377
external void setFilterQuality(SkFilterQuality filterQuality);
323-
external void setColorFilter(SkColorFilter colorFilter);
378+
external void setColorFilter(SkColorFilter? colorFilter);
324379
external void setStrokeMiter(double miterLimit);
325-
external void setImageFilter(SkImageFilter imageFilter);
380+
external void setImageFilter(SkImageFilter? imageFilter);
326381
}
327382

328383
@JS()
@@ -373,3 +428,109 @@ Float32List toSkMatrixFromFloat32(Float32List matrix4) {
373428
}
374429
return skMatrix;
375430
}
431+
432+
/// Converts an [offset] into an `[x, y]` pair stored in a `Float32List`.
433+
///
434+
/// The returned list can be passed to CanvasKit API that take points.
435+
Float32List toSkPoint(ui.Offset offset) {
436+
final Float32List point = Float32List(2);
437+
point[0] = offset.dx;
438+
point[1] = offset.dy;
439+
return point;
440+
}
441+
442+
/// Color stops used when the framework specifies `null`.
443+
final Float32List _kDefaultSkColorStops = Float32List(2)
444+
..[0] = 0
445+
..[1] = 1;
446+
447+
/// Converts a list of color stops into a Skia-compatible JS array or color stops.
448+
///
449+
/// In Flutter `null` means two color stops `[0, 1]` that in Skia must be specified explicitly.
450+
Float32List toSkColorStops(List<double>? colorStops) {
451+
if (colorStops == null) {
452+
return _kDefaultSkColorStops;
453+
}
454+
455+
final int len = colorStops.length;
456+
final Float32List skColorStops = Float32List(len);
457+
for (int i = 0; i < len; i++) {
458+
skColorStops[i] = colorStops[i];
459+
}
460+
return skColorStops;
461+
}
462+
463+
@JS('Float32Array')
464+
external _NativeFloat32ArrayType get _nativeFloat32ArrayType;
465+
466+
@JS()
467+
class _NativeFloat32ArrayType {}
468+
469+
@JS('window.flutter_canvas_kit.Malloc')
470+
external SkFloat32List _mallocFloat32List(
471+
_NativeFloat32ArrayType float32ListType,
472+
int size,
473+
);
474+
475+
/// Allocates a [Float32List] backed by WASM memory, managed by
476+
/// a [SkFloat32List].
477+
SkFloat32List mallocFloat32List(int size) {
478+
return _mallocFloat32List(_nativeFloat32ArrayType, size);
479+
}
480+
481+
/// Wraps a [Float32List] backed by WASM memory.
482+
///
483+
/// This wrapper is necessary because the raw [Float32List] will get detached
484+
/// when WASM grows its memory. Call [toTypedArray] to get a new instance
485+
/// that's attached to the current WASM memory block.
486+
@JS()
487+
class SkFloat32List {
488+
/// Returns the [Float32List] object backed by WASM memory.
489+
///
490+
/// Do not reuse the returned list across multiple WASM function/method
491+
/// invocations that may lead to WASM memory to grow. When WASM memory
492+
/// grows the [Float32List] object becomes "detached" and is no longer
493+
/// usable. Instead, call this method every time you need to read from
494+
/// or write to the list.
495+
external Float32List toTypedArray();
496+
}
497+
498+
/// Writes [color] information into the given [skColor] buffer.
499+
Float32List _populateSkColor(SkFloat32List skColor, ui.Color color) {
500+
final Float32List array = skColor.toTypedArray();
501+
array[0] = color.red / 255.0;
502+
array[1] = color.green / 255.0;
503+
array[2] = color.blue / 255.0;
504+
array[3] = color.alpha / 255.0;
505+
return array;
506+
}
507+
508+
/// Unpacks the [color] into CanvasKit-compatible representation stored
509+
/// in a shared memory location #1.
510+
///
511+
/// Use this only for passing transient data to CanvasKit. Because the
512+
/// memory is shared the value will not persist.
513+
Float32List toSharedSkColor1(ui.Color color) {
514+
return _populateSkColor(_sharedSkColor1, color);
515+
}
516+
final SkFloat32List _sharedSkColor1 = mallocFloat32List(4);
517+
518+
/// Unpacks the [color] into CanvasKit-compatible representation stored
519+
/// in a shared memory location #2.
520+
///
521+
/// Use this only for passing transient data to CanvasKit. Because the
522+
/// memory is shared the value will not persist.
523+
Float32List toSharedSkColor2(ui.Color color) {
524+
return _populateSkColor(_sharedSkColor2, color);
525+
}
526+
final SkFloat32List _sharedSkColor2 = mallocFloat32List(4);
527+
528+
/// Unpacks the [color] into CanvasKit-compatible representation stored
529+
/// in a shared memory location #3.
530+
///
531+
/// Use this only for passing transient data to CanvasKit. Because the
532+
/// memory is shared the value will not persist.
533+
Float32List toSharedSkColor3(ui.Color color) {
534+
return _populateSkColor(_sharedSkColor3, color);
535+
}
536+
final SkFloat32List _sharedSkColor3 = mallocFloat32List(4);

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

+19-12
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,37 @@ class CkColorFilter extends ResurrectableSkiaObject {
1919
CkColorFilter.srgbToLinearGamma(EngineColorFilter filter)
2020
: _engineFilter = filter;
2121

22+
SkColorFilter? _skColorFilter;
23+
2224
js.JsObject _createSkiaObjectFromFilter() {
25+
SkColorFilter skColorFilter;
2326
switch (_engineFilter._type) {
2427
case EngineColorFilter._TypeMode:
25-
setSharedSkColor1(_engineFilter._color!);
26-
return canvasKit['SkColorFilter'].callMethod('MakeBlend', <dynamic>[
27-
sharedSkColor1,
28-
makeSkBlendMode(_engineFilter._blendMode),
29-
]);
28+
skColorFilter = canvasKitJs.SkColorFilter.MakeBlend(
29+
toSharedSkColor1(_engineFilter._color!),
30+
toSkBlendMode(_engineFilter._blendMode!),
31+
);
32+
break;
3033
case EngineColorFilter._TypeMatrix:
31-
final js.JsArray<double> colorMatrix = js.JsArray<double>();
32-
colorMatrix.length = 20;
34+
final Float32List colorMatrix = Float32List(20);
35+
final List<double> matrix = _engineFilter._matrix!;
3336
for (int i = 0; i < 20; i++) {
34-
colorMatrix[i] = _engineFilter._matrix![i];
37+
colorMatrix[i] = matrix[i];
3538
}
36-
return canvasKit['SkColorFilter']
37-
.callMethod('MakeMatrix', <js.JsArray>[colorMatrix]);
39+
skColorFilter = canvasKitJs.SkColorFilter.MakeMatrix(colorMatrix);
40+
break;
3841
case EngineColorFilter._TypeLinearToSrgbGamma:
39-
return canvasKit['SkColorFilter'].callMethod('MakeLinearToSRGBGamma');
42+
skColorFilter = canvasKitJs.SkColorFilter.MakeLinearToSRGBGamma();
43+
break;
4044
case EngineColorFilter._TypeSrgbToLinearGamma:
41-
return canvasKit['SkColorFilter'].callMethod('MakeSRGBToLinearGamma');
45+
skColorFilter = canvasKitJs.SkColorFilter.MakeSRGBToLinearGamma();
46+
break;
4247
default:
4348
throw StateError(
4449
'Unknown mode ${_engineFilter._type} for ColorFilter.');
4550
}
51+
_skColorFilter = skColorFilter;
52+
return _jsObjectWrapper.wrapSkColorFilter(skColorFilter);
4653
}
4754

4855
@override

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

+15-19
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,42 @@ part of engine;
88
/// Instantiates a [ui.Codec] backed by an `SkImage` from Skia.
99
void skiaInstantiateImageCodec(Uint8List list, Callback<ui.Codec> callback,
1010
[int? width, int? height, int? format, int? rowBytes]) {
11-
final js.JsObject? skAnimatedImage =
12-
canvasKit.callMethod('MakeAnimatedImageFromEncoded', <Uint8List>[list]);
11+
final SkAnimatedImage skAnimatedImage = canvasKitJs.MakeAnimatedImageFromEncoded(list);
1312
final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage);
1413
final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage);
1514
callback(codec);
1615
}
1716

1817
/// A wrapper for `SkAnimatedImage`.
1918
class CkAnimatedImage implements ui.Image {
20-
final js.JsObject? _skAnimatedImage;
19+
final SkAnimatedImage _skAnimatedImage;
2120

2221
CkAnimatedImage(this._skAnimatedImage);
2322

2423
@override
2524
void dispose() {
26-
_skAnimatedImage!.callMethod('delete');
25+
_skAnimatedImage.delete();
2726
}
2827

29-
int? get frameCount => _skAnimatedImage!.callMethod('getFrameCount');
28+
int get frameCount => _skAnimatedImage.getFrameCount();
3029

3130
/// Decodes the next frame and returns the frame duration.
3231
Duration decodeNextFrame() {
33-
final int durationMillis = _skAnimatedImage!.callMethod('decodeNextFrame');
32+
final int durationMillis = _skAnimatedImage.decodeNextFrame();
3433
return Duration(milliseconds: durationMillis);
3534
}
3635

37-
int? get repetitionCount => _skAnimatedImage!.callMethod('getRepetitionCount');
36+
int get repetitionCount => _skAnimatedImage.getRepetitionCount();
3837

3938
CkImage get currentFrameAsImage {
40-
final js.JsObject? _currentFrame =
41-
_skAnimatedImage!.callMethod('getCurrentFrame');
42-
return CkImage(_currentFrame);
39+
return CkImage(_skAnimatedImage.getCurrentFrame());
4340
}
4441

4542
@override
46-
int get width => _skAnimatedImage!.callMethod('width');
43+
int get width => _skAnimatedImage.width();
4744

4845
@override
49-
int get height => _skAnimatedImage!.callMethod('height');
46+
int get height => _skAnimatedImage.height();
5047

5148
@override
5249
Future<ByteData> toByteData(
@@ -57,21 +54,20 @@ class CkAnimatedImage implements ui.Image {
5754

5855
/// A [ui.Image] backed by an `SkImage` from Skia.
5956
class CkImage implements ui.Image {
60-
js.JsObject? skImage;
57+
SkImage skImage;
6158

6259
CkImage(this.skImage);
6360

6461
@override
6562
void dispose() {
66-
skImage!.callMethod('delete');
67-
skImage = null;
63+
skImage.delete();
6864
}
6965

7066
@override
71-
int get width => skImage!.callMethod('width');
67+
int get width => skImage.width();
7268

7369
@override
74-
int get height => skImage!.callMethod('height');
70+
int get height => skImage.height();
7571

7672
@override
7773
Future<ByteData> toByteData(
@@ -93,10 +89,10 @@ class CkAnimatedImageCodec implements ui.Codec {
9389
}
9490

9591
@override
96-
int get frameCount => animatedImage!.frameCount!;
92+
int get frameCount => animatedImage!.frameCount;
9793

9894
@override
99-
int get repetitionCount => animatedImage!.repetitionCount!;
95+
int get repetitionCount => animatedImage!.repetitionCount;
10096

10197
@override
10298
Future<ui.FrameInfo> getNextFrame() {

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

+12-9
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,24 @@ class CkImageFilter extends ResurrectableSkiaObject implements ui.ImageFilter {
1515
final double _sigmaX;
1616
final double _sigmaY;
1717

18+
SkImageFilter? _skImageFilter;
19+
1820
@override
1921
js.JsObject createDefault() => _initSkiaObject();
2022

2123
@override
2224
js.JsObject resurrect() => _initSkiaObject();
2325

24-
js.JsObject _initSkiaObject() => canvasKit['SkImageFilter'].callMethod(
25-
'MakeBlur',
26-
<dynamic>[
27-
_sigmaX,
28-
_sigmaY,
29-
canvasKit['TileMode']['Clamp'],
30-
null,
31-
],
32-
);
26+
js.JsObject _initSkiaObject() {
27+
final SkImageFilter skImageFilter = canvasKitJs.SkImageFilter.MakeBlur(
28+
_sigmaX,
29+
_sigmaY,
30+
canvasKitJs.TileMode.Clamp,
31+
null,
32+
);
33+
_skImageFilter = skImageFilter;
34+
return _jsObjectWrapper.wrapSkImageFilter(skImageFilter);
35+
}
3336

3437
@override
3538
bool operator ==(Object other) {

0 commit comments

Comments
 (0)