From 4776107d0a7abe7328c505eda76750df22db6e56 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Mon, 6 Jul 2020 09:22:29 -0700 Subject: [PATCH] CkPaint uses SkPaint --- .../src/engine/compositor/canvaskit_api.dart | 171 ++++++++++++++- .../src/engine/compositor/color_filter.dart | 31 +-- .../lib/src/engine/compositor/image.dart | 34 ++- .../src/engine/compositor/image_filter.dart | 21 +- .../src/engine/compositor/mask_filter.dart | 27 +-- .../lib/src/engine/compositor/painting.dart | 201 +++++++----------- lib/web_ui/lib/src/engine/shader.dart | 68 +++--- .../test/canvaskit/canvaskit_api_test.dart | 118 +++++++++- 8 files changed, 445 insertions(+), 226 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart b/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart index b0323c192f582..91e6f03a2bcbf 100644 --- a/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart @@ -5,10 +5,65 @@ /// Bindings for CanvasKit JavaScript API. part of engine; +final js.JsObject _jsWindow = js.JsObject.fromBrowserObject(html.window); + +/// This and [_jsObjectWrapper] below are used to convert `@JS`-backed +/// objects to [js.JsObject]s. To do that we use `@JS` to pass the object +/// to JavaScript (see [JsObjectWrapper]), then use this variable (which +/// uses `dart:js`) to read the value back, causing it to be wrapped in +/// [js.JsObject]. +/// +// TODO(yjbanov): this is a temporary hack until we fully migrate to @JS. +final js.JsObject _jsObjectWrapperLegacy = js.JsObject(js.context['Object']); + +@JS('window.flutter_js_object_wrapper') +external JsObjectWrapper get _jsObjectWrapper; + void initializeCanvasKitBindings(js.JsObject canvasKit) { // Because JsObject cannot be cast to a @JS type, we stash CanvasKit into // a global and use the [canvasKitJs] getter to access it. - js.JsObject.fromBrowserObject(html.window)['flutter_canvas_kit'] = canvasKit; + _jsWindow['flutter_canvas_kit'] = canvasKit; + _jsWindow['flutter_js_object_wrapper'] = _jsObjectWrapperLegacy; +} + +@JS() +class JsObjectWrapper { + external set skPaint(SkPaint? paint); + external set skMaskFilter(SkMaskFilter? filter); + external set skColorFilter(SkColorFilter? filter); + external set skImageFilter(SkImageFilter? filter); +} + +/// Specific methods that wrap `@JS`-backed objects into a [js.JsObject] +/// for use with legacy `dart:js` API. +extension JsObjectWrappers on JsObjectWrapper { + js.JsObject wrapSkPaint(SkPaint paint) { + _jsObjectWrapper.skPaint = paint; + js.JsObject wrapped = _jsObjectWrapperLegacy['skPaint']; + _jsObjectWrapper.skPaint = null; + return wrapped; + } + + js.JsObject wrapSkMaskFilter(SkMaskFilter filter) { + _jsObjectWrapper.skMaskFilter = filter; + js.JsObject wrapped = _jsObjectWrapperLegacy['skMaskFilter']; + _jsObjectWrapper.skMaskFilter = null; + return wrapped; + } + + js.JsObject wrapSkColorFilter(SkColorFilter filter) { + _jsObjectWrapper.skColorFilter = filter; + js.JsObject wrapped = _jsObjectWrapperLegacy['skColorFilter']; + _jsObjectWrapper.skColorFilter = null; + return wrapped; + } + + js.JsObject wrapSkImageFilter(SkImageFilter filter) { + _jsObjectWrapper.skImageFilter = filter; + js.JsObject wrapped = _jsObjectWrapperLegacy['skImageFilter']; + _jsObjectWrapper.skImageFilter = null; + return wrapped; + } } @JS('window.flutter_canvas_kit') @@ -317,12 +372,12 @@ class SkPaint { external void setStrokeJoin(SkStrokeJoin join); external void setAntiAlias(bool isAntiAlias); external void setColorInt(int color); - external void setShader(SkShader shader); - external void setMaskFilter(SkMaskFilter maskFilter); + external void setShader(SkShader? shader); + external void setMaskFilter(SkMaskFilter? maskFilter); external void setFilterQuality(SkFilterQuality filterQuality); - external void setColorFilter(SkColorFilter colorFilter); + external void setColorFilter(SkColorFilter? colorFilter); external void setStrokeMiter(double miterLimit); - external void setImageFilter(SkImageFilter imageFilter); + external void setImageFilter(SkImageFilter? imageFilter); } @JS() @@ -373,3 +428,109 @@ Float32List toSkMatrixFromFloat32(Float32List matrix4) { } return skMatrix; } + +/// Converts an [offset] into an `[x, y]` pair stored in a `Float32List`. +/// +/// The returned list can be passed to CanvasKit API that take points. +Float32List toSkPoint(ui.Offset offset) { + final Float32List point = Float32List(2); + point[0] = offset.dx; + point[1] = offset.dy; + return point; +} + +/// Color stops used when the framework specifies `null`. +final Float32List _kDefaultSkColorStops = Float32List(2) + ..[0] = 0 + ..[1] = 1; + +/// Converts a list of color stops into a Skia-compatible JS array or color stops. +/// +/// In Flutter `null` means two color stops `[0, 1]` that in Skia must be specified explicitly. +Float32List toSkColorStops(List? colorStops) { + if (colorStops == null) { + return _kDefaultSkColorStops; + } + + final int len = colorStops.length; + final Float32List skColorStops = Float32List(len); + for (int i = 0; i < len; i++) { + skColorStops[i] = colorStops[i]; + } + return skColorStops; +} + +@JS('Float32Array') +external _NativeFloat32ArrayType get _nativeFloat32ArrayType; + +@JS() +class _NativeFloat32ArrayType {} + +@JS('window.flutter_canvas_kit.Malloc') +external SkFloat32List _mallocFloat32List( + _NativeFloat32ArrayType float32ListType, + int size, +); + +/// Allocates a [Float32List] backed by WASM memory, managed by +/// a [SkFloat32List]. +SkFloat32List mallocFloat32List(int size) { + return _mallocFloat32List(_nativeFloat32ArrayType, size); +} + +/// Wraps a [Float32List] backed by WASM memory. +/// +/// This wrapper is necessary because the raw [Float32List] will get detached +/// when WASM grows its memory. Call [toTypedArray] to get a new instance +/// that's attached to the current WASM memory block. +@JS() +class SkFloat32List { + /// Returns the [Float32List] object backed by WASM memory. + /// + /// Do not reuse the returned list across multiple WASM function/method + /// invocations that may lead to WASM memory to grow. When WASM memory + /// grows the [Float32List] object becomes "detached" and is no longer + /// usable. Instead, call this method every time you need to read from + /// or write to the list. + external Float32List toTypedArray(); +} + +/// Writes [color] information into the given [skColor] buffer. +Float32List _populateSkColor(SkFloat32List skColor, ui.Color color) { + final Float32List array = skColor.toTypedArray(); + array[0] = color.red / 255.0; + array[1] = color.green / 255.0; + array[2] = color.blue / 255.0; + array[3] = color.alpha / 255.0; + return array; +} + +/// Unpacks the [color] into CanvasKit-compatible representation stored +/// in a shared memory location #1. +/// +/// Use this only for passing transient data to CanvasKit. Because the +/// memory is shared the value will not persist. +Float32List toSharedSkColor1(ui.Color color) { + return _populateSkColor(_sharedSkColor1, color); +} +final SkFloat32List _sharedSkColor1 = mallocFloat32List(4); + +/// Unpacks the [color] into CanvasKit-compatible representation stored +/// in a shared memory location #2. +/// +/// Use this only for passing transient data to CanvasKit. Because the +/// memory is shared the value will not persist. +Float32List toSharedSkColor2(ui.Color color) { + return _populateSkColor(_sharedSkColor2, color); +} +final SkFloat32List _sharedSkColor2 = mallocFloat32List(4); + +/// Unpacks the [color] into CanvasKit-compatible representation stored +/// in a shared memory location #3. +/// +/// Use this only for passing transient data to CanvasKit. Because the +/// memory is shared the value will not persist. +Float32List toSharedSkColor3(ui.Color color) { + return _populateSkColor(_sharedSkColor3, color); +} +final SkFloat32List _sharedSkColor3 = mallocFloat32List(4); diff --git a/lib/web_ui/lib/src/engine/compositor/color_filter.dart b/lib/web_ui/lib/src/engine/compositor/color_filter.dart index 8a8d10671e295..d80cac8f3034e 100644 --- a/lib/web_ui/lib/src/engine/compositor/color_filter.dart +++ b/lib/web_ui/lib/src/engine/compositor/color_filter.dart @@ -19,30 +19,37 @@ class CkColorFilter extends ResurrectableSkiaObject { CkColorFilter.srgbToLinearGamma(EngineColorFilter filter) : _engineFilter = filter; + SkColorFilter? _skColorFilter; + js.JsObject _createSkiaObjectFromFilter() { + SkColorFilter skColorFilter; switch (_engineFilter._type) { case EngineColorFilter._TypeMode: - setSharedSkColor1(_engineFilter._color!); - return canvasKit['SkColorFilter'].callMethod('MakeBlend', [ - sharedSkColor1, - makeSkBlendMode(_engineFilter._blendMode), - ]); + skColorFilter = canvasKitJs.SkColorFilter.MakeBlend( + toSharedSkColor1(_engineFilter._color!), + toSkBlendMode(_engineFilter._blendMode!), + ); + break; case EngineColorFilter._TypeMatrix: - final js.JsArray colorMatrix = js.JsArray(); - colorMatrix.length = 20; + final Float32List colorMatrix = Float32List(20); + final List matrix = _engineFilter._matrix!; for (int i = 0; i < 20; i++) { - colorMatrix[i] = _engineFilter._matrix![i]; + colorMatrix[i] = matrix[i]; } - return canvasKit['SkColorFilter'] - .callMethod('MakeMatrix', [colorMatrix]); + skColorFilter = canvasKitJs.SkColorFilter.MakeMatrix(colorMatrix); + break; case EngineColorFilter._TypeLinearToSrgbGamma: - return canvasKit['SkColorFilter'].callMethod('MakeLinearToSRGBGamma'); + skColorFilter = canvasKitJs.SkColorFilter.MakeLinearToSRGBGamma(); + break; case EngineColorFilter._TypeSrgbToLinearGamma: - return canvasKit['SkColorFilter'].callMethod('MakeSRGBToLinearGamma'); + skColorFilter = canvasKitJs.SkColorFilter.MakeSRGBToLinearGamma(); + break; default: throw StateError( 'Unknown mode ${_engineFilter._type} for ColorFilter.'); } + _skColorFilter = skColorFilter; + return _jsObjectWrapper.wrapSkColorFilter(skColorFilter); } @override diff --git a/lib/web_ui/lib/src/engine/compositor/image.dart b/lib/web_ui/lib/src/engine/compositor/image.dart index b0c81035da8d0..55492aeceb363 100644 --- a/lib/web_ui/lib/src/engine/compositor/image.dart +++ b/lib/web_ui/lib/src/engine/compositor/image.dart @@ -8,8 +8,7 @@ part of engine; /// Instantiates a [ui.Codec] backed by an `SkImage` from Skia. void skiaInstantiateImageCodec(Uint8List list, Callback callback, [int? width, int? height, int? format, int? rowBytes]) { - final js.JsObject? skAnimatedImage = - canvasKit.callMethod('MakeAnimatedImageFromEncoded', [list]); + final SkAnimatedImage skAnimatedImage = canvasKitJs.MakeAnimatedImageFromEncoded(list); final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage); final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage); callback(codec); @@ -17,36 +16,34 @@ void skiaInstantiateImageCodec(Uint8List list, Callback callback, /// A wrapper for `SkAnimatedImage`. class CkAnimatedImage implements ui.Image { - final js.JsObject? _skAnimatedImage; + final SkAnimatedImage _skAnimatedImage; CkAnimatedImage(this._skAnimatedImage); @override void dispose() { - _skAnimatedImage!.callMethod('delete'); + _skAnimatedImage.delete(); } - int? get frameCount => _skAnimatedImage!.callMethod('getFrameCount'); + int get frameCount => _skAnimatedImage.getFrameCount(); /// Decodes the next frame and returns the frame duration. Duration decodeNextFrame() { - final int durationMillis = _skAnimatedImage!.callMethod('decodeNextFrame'); + final int durationMillis = _skAnimatedImage.decodeNextFrame(); return Duration(milliseconds: durationMillis); } - int? get repetitionCount => _skAnimatedImage!.callMethod('getRepetitionCount'); + int get repetitionCount => _skAnimatedImage.getRepetitionCount(); CkImage get currentFrameAsImage { - final js.JsObject? _currentFrame = - _skAnimatedImage!.callMethod('getCurrentFrame'); - return CkImage(_currentFrame); + return CkImage(_skAnimatedImage.getCurrentFrame()); } @override - int get width => _skAnimatedImage!.callMethod('width'); + int get width => _skAnimatedImage.width(); @override - int get height => _skAnimatedImage!.callMethod('height'); + int get height => _skAnimatedImage.height(); @override Future toByteData( @@ -57,21 +54,20 @@ class CkAnimatedImage implements ui.Image { /// A [ui.Image] backed by an `SkImage` from Skia. class CkImage implements ui.Image { - js.JsObject? skImage; + SkImage skImage; CkImage(this.skImage); @override void dispose() { - skImage!.callMethod('delete'); - skImage = null; + skImage.delete(); } @override - int get width => skImage!.callMethod('width'); + int get width => skImage.width(); @override - int get height => skImage!.callMethod('height'); + int get height => skImage.height(); @override Future toByteData( @@ -93,10 +89,10 @@ class CkAnimatedImageCodec implements ui.Codec { } @override - int get frameCount => animatedImage!.frameCount!; + int get frameCount => animatedImage!.frameCount; @override - int get repetitionCount => animatedImage!.repetitionCount!; + int get repetitionCount => animatedImage!.repetitionCount; @override Future getNextFrame() { diff --git a/lib/web_ui/lib/src/engine/compositor/image_filter.dart b/lib/web_ui/lib/src/engine/compositor/image_filter.dart index e7da018db7971..f0f46af81f2c8 100644 --- a/lib/web_ui/lib/src/engine/compositor/image_filter.dart +++ b/lib/web_ui/lib/src/engine/compositor/image_filter.dart @@ -15,21 +15,24 @@ class CkImageFilter extends ResurrectableSkiaObject implements ui.ImageFilter { final double _sigmaX; final double _sigmaY; + SkImageFilter? _skImageFilter; + @override js.JsObject createDefault() => _initSkiaObject(); @override js.JsObject resurrect() => _initSkiaObject(); - js.JsObject _initSkiaObject() => canvasKit['SkImageFilter'].callMethod( - 'MakeBlur', - [ - _sigmaX, - _sigmaY, - canvasKit['TileMode']['Clamp'], - null, - ], - ); + js.JsObject _initSkiaObject() { + final SkImageFilter skImageFilter = canvasKitJs.SkImageFilter.MakeBlur( + _sigmaX, + _sigmaY, + canvasKitJs.TileMode.Clamp, + null, + ); + _skImageFilter = skImageFilter; + return _jsObjectWrapper.wrapSkImageFilter(skImageFilter); + } @override bool operator ==(Object other) { diff --git a/lib/web_ui/lib/src/engine/compositor/mask_filter.dart b/lib/web_ui/lib/src/engine/compositor/mask_filter.dart index c47672ec3ee0e..e88040f17e82f 100644 --- a/lib/web_ui/lib/src/engine/compositor/mask_filter.dart +++ b/lib/web_ui/lib/src/engine/compositor/mask_filter.dart @@ -13,6 +13,8 @@ class CkMaskFilter extends ResurrectableSkiaObject { final ui.BlurStyle _blurStyle; final double _sigma; + SkMaskFilter? _skMaskFilter; + @override js.JsObject createDefault() => _initSkiaObject(); @@ -20,23 +22,12 @@ class CkMaskFilter extends ResurrectableSkiaObject { js.JsObject resurrect() => _initSkiaObject(); js.JsObject _initSkiaObject() { - js.JsObject skBlurStyle; - switch (_blurStyle) { - case ui.BlurStyle.normal: - skBlurStyle = canvasKit['BlurStyle']['Normal']; - break; - case ui.BlurStyle.solid: - skBlurStyle = canvasKit['BlurStyle']['Solid']; - break; - case ui.BlurStyle.outer: - skBlurStyle = canvasKit['BlurStyle']['Outer']; - break; - case ui.BlurStyle.inner: - skBlurStyle = canvasKit['BlurStyle']['Inner']; - break; - } - - return canvasKit - .callMethod('MakeBlurMaskFilter', [skBlurStyle, _sigma, true]); + final SkMaskFilter skMaskFilter = canvasKitJs.MakeBlurMaskFilter( + toSkBlurStyle(_blurStyle), + _sigma, + true, + ); + _skMaskFilter = skMaskFilter; + return _jsObjectWrapper.wrapSkMaskFilter(skMaskFilter); } } diff --git a/lib/web_ui/lib/src/engine/compositor/painting.dart b/lib/web_ui/lib/src/engine/compositor/painting.dart index 79c9c97a96210..e3453e16fdcb9 100644 --- a/lib/web_ui/lib/src/engine/compositor/painting.dart +++ b/lib/web_ui/lib/src/engine/compositor/painting.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - part of engine; /// The implementation of [ui.Paint] used by the CanvasKit backend. @@ -13,21 +12,16 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { CkPaint(); static const ui.Color _defaultPaintColor = ui.Color(0xFF000000); - static final js.JsObject? _skPaintStyleStroke = - canvasKit['PaintStyle']['Stroke']; - static final js.JsObject? _skPaintStyleFill = canvasKit['PaintStyle']['Fill']; @override ui.BlendMode get blendMode => _blendMode; @override set blendMode(ui.BlendMode value) { + if (_blendMode == value) { + return; + } _blendMode = value; - _syncBlendMode(skiaObject); - } - - void _syncBlendMode(js.JsObject object) { - final js.JsObject? skBlendMode = makeSkBlendMode(_blendMode); - object.callMethod('setBlendMode', [skBlendMode]); + _skPaint.setBlendMode(toSkBlendMode(value)); } ui.BlendMode _blendMode = ui.BlendMode.srcOver; @@ -37,21 +31,11 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { @override set style(ui.PaintingStyle value) { - _style = value; - _syncStyle(skiaObject); - } - - void _syncStyle(js.JsObject object) { - js.JsObject? skPaintStyle; - switch (_style) { - case ui.PaintingStyle.stroke: - skPaintStyle = _skPaintStyleStroke; - break; - case ui.PaintingStyle.fill: - skPaintStyle = _skPaintStyleFill; - break; + if (_style == value) { + return; } - object.callMethod('setStyle', [skPaintStyle]); + _style = value; + _skPaint.setStyle(toSkPaintStyle(value)); } ui.PaintingStyle _style = ui.PaintingStyle.fill; @@ -60,32 +44,37 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { double get strokeWidth => _strokeWidth; @override set strokeWidth(double value) { + if (_strokeWidth == value) { + return; + } _strokeWidth = value; - _syncStrokeWidth(skiaObject); - } - - void _syncStrokeWidth(js.JsObject object) { - object.callMethod('setStrokeWidth', [strokeWidth]); + _skPaint.setStrokeWidth(value); } double _strokeWidth = 0.0; - // TODO(yjbanov): implement @override ui.StrokeCap get strokeCap => _strokeCap; @override set strokeCap(ui.StrokeCap value) { + if (_strokeCap == value) { + return; + } _strokeCap = value; + _skPaint.setStrokeCap(toSkStrokeCap(value)); } ui.StrokeCap _strokeCap = ui.StrokeCap.butt; - // TODO(yjbanov): implement @override ui.StrokeJoin get strokeJoin => _strokeJoin; @override set strokeJoin(ui.StrokeJoin value) { + if (_strokeJoin == value) { + return; + } _strokeJoin = value; + _skPaint.setStrokeJoin(toSkStrokeJoin(value)); } ui.StrokeJoin _strokeJoin = ui.StrokeJoin.miter; @@ -94,12 +83,11 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { bool get isAntiAlias => _isAntiAlias; @override set isAntiAlias(bool value) { + if (_isAntiAlias == value) { + return; + } _isAntiAlias = value; - _syncAntiAlias(skiaObject); - } - - void _syncAntiAlias(js.JsObject object) { - object.callMethod('setAntiAlias', [_isAntiAlias]); + _skPaint.setAntiAlias(value); } bool _isAntiAlias = true; @@ -108,14 +96,11 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { ui.Color get color => _color; @override set color(ui.Color value) { + if (_color == value) { + return; + } _color = value; - _syncColor(skiaObject); - } - - void _syncColor(js.JsObject object) { - object.callMethod('setColorInt', [ - _color.value, - ]); + _skPaint.setColorInt(value.value); } ui.Color _color = _defaultPaintColor; @@ -134,16 +119,11 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { ui.Shader? get shader => _shader as ui.Shader?; @override set shader(ui.Shader? value) { - _shader = value as EngineShader?; - _syncShader(skiaObject); - } - - void _syncShader(js.JsObject object) { - js.JsObject? skShader; - if (_shader != null) { - skShader = _shader!.createSkiaShader(); + if (_shader == value) { + return; } - object.callMethod('setShader', [skShader]); + _shader = value as EngineShader?; + _skPaint.setShader(_shader?.createSkiaShader()); } EngineShader? _shader; @@ -152,48 +132,33 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { ui.MaskFilter? get maskFilter => _maskFilter; @override set maskFilter(ui.MaskFilter? value) { + if (value == _maskFilter) { + return; + } _maskFilter = value; - _syncMaskFilter(skiaObject); - } - - void _syncMaskFilter(js.JsObject object) { - CkMaskFilter? skMaskFilter; - if (_maskFilter != null) { - final ui.BlurStyle blurStyle = _maskFilter!.webOnlyBlurStyle; - final double sigma = _maskFilter!.webOnlySigma; - - skMaskFilter = CkMaskFilter.blur(blurStyle, sigma); + if (value != null) { + _ckMaskFilter = CkMaskFilter.blur( + value.webOnlyBlurStyle, + value.webOnlySigma, + ); + } else { + _ckMaskFilter = null; } - object.callMethod('setMaskFilter', [skMaskFilter?.skiaObject]); + _skPaint.setMaskFilter(_ckMaskFilter?._skMaskFilter); } ui.MaskFilter? _maskFilter; + CkMaskFilter? _ckMaskFilter; @override ui.FilterQuality get filterQuality => _filterQuality; @override set filterQuality(ui.FilterQuality value) { - _filterQuality = value; - _syncFilterQuality(skiaObject); - } - - void _syncFilterQuality(js.JsObject? object) { - js.JsObject? skFilterQuality; - switch (_filterQuality) { - case ui.FilterQuality.none: - skFilterQuality = canvasKit['FilterQuality']['None']; - break; - case ui.FilterQuality.low: - skFilterQuality = canvasKit['FilterQuality']['Low']; - break; - case ui.FilterQuality.medium: - skFilterQuality = canvasKit['FilterQuality']['Medium']; - break; - case ui.FilterQuality.high: - skFilterQuality = canvasKit['FilterQuality']['High']; - break; + if (_filterQuality == value) { + return; } - object!.callMethod('setFilterQuality', [skFilterQuality]); + _filterQuality = value; + _skPaint.setFilterQuality(toSkFilterQuality(value)); } ui.FilterQuality _filterQuality = ui.FilterQuality.none; @@ -202,27 +167,27 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { ui.ColorFilter? get colorFilter => _colorFilter; @override set colorFilter(ui.ColorFilter? value) { - _colorFilter = value as EngineColorFilter?; - _syncColorFilter(skiaObject); - } - - void _syncColorFilter(js.JsObject object) { - js.JsObject? skColorFilterJs; - if (_colorFilter != null) { - CkColorFilter? skFilter = _colorFilter!._toCkColorFilter(); - skColorFilterJs = skFilter!.skiaObject; + if (_colorFilter == value) { + return; } - object.callMethod('setColorFilter', [skColorFilterJs]); + final EngineColorFilter? engineValue = value as EngineColorFilter?; + _colorFilter = engineValue; + _ckColorFilter = engineValue?._toCkColorFilter(); + _skPaint.setColorFilter(_ckColorFilter?._skColorFilter); } EngineColorFilter? _colorFilter; + CkColorFilter? _ckColorFilter; - // TODO(yjbanov): implement @override double get strokeMiterLimit => _strokeMiterLimit; @override set strokeMiterLimit(double value) { + if (_strokeMiterLimit == value) { + return; + } _strokeMiterLimit = value; + _skPaint.setStrokeMiter(value); } double _strokeMiterLimit = 0.0; @@ -231,42 +196,38 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { ui.ImageFilter? get imageFilter => _imageFilter; @override set imageFilter(ui.ImageFilter? value) { - _imageFilter = value as CkImageFilter?; - _syncImageFilter(skiaObject); - } - - void _syncImageFilter(js.JsObject object) { - js.JsObject? imageFilterJs; - if (_imageFilter != null) { - imageFilterJs = _imageFilter!.skiaObject; + if (_imageFilter == value) { + return; } - object.callMethod('setImageFilter', [imageFilterJs]); + _imageFilter = value as CkImageFilter?; + _skPaint.setImageFilter(_imageFilter?._skImageFilter); } CkImageFilter? _imageFilter; + late SkPaint _skPaint; + @override js.JsObject createDefault() { - final obj = js.JsObject(canvasKit['SkPaint']); - // Sync fields whose Skia defaults are different from Flutter's. - _syncAntiAlias(obj); - _syncColor(obj); - return obj; + _skPaint = SkPaint(); + _skPaint.setAntiAlias(_isAntiAlias); + _skPaint.setColorInt(_color.value); + return _jsObjectWrapper.wrapSkPaint(_skPaint); } @override js.JsObject resurrect() { - final obj = js.JsObject(canvasKit['SkPaint']); - _syncBlendMode(obj); - _syncStyle(obj); - _syncStrokeWidth(obj); - _syncAntiAlias(obj); - _syncColor(obj); - _syncShader(obj); - _syncMaskFilter(obj); - _syncColorFilter(obj); - _syncImageFilter(obj); - _syncFilterQuality(obj); - return obj; + _skPaint = SkPaint(); + _skPaint.setBlendMode(toSkBlendMode(_blendMode)); + _skPaint.setStyle(toSkPaintStyle(_style)); + _skPaint.setStrokeWidth(_strokeWidth); + _skPaint.setAntiAlias(_isAntiAlias); + _skPaint.setColorInt(_color.value); + _skPaint.setShader(_shader?.createSkiaShader()); + _skPaint.setMaskFilter(_ckMaskFilter?._skMaskFilter); + _skPaint.setColorFilter(_ckColorFilter?._skColorFilter); + _skPaint.setImageFilter(_imageFilter?._skImageFilter); + _skPaint.setFilterQuality(toSkFilterQuality(_filterQuality)); + return _jsObjectWrapper.wrapSkPaint(_skPaint); } } diff --git a/lib/web_ui/lib/src/engine/shader.dart b/lib/web_ui/lib/src/engine/shader.dart index 2d4e0f0b1b169..9db1c4e53a652 100644 --- a/lib/web_ui/lib/src/engine/shader.dart +++ b/lib/web_ui/lib/src/engine/shader.dart @@ -20,7 +20,7 @@ bool _matrix4IsValid(Float32List matrix4) { abstract class EngineShader { /// Create a shader for use in the Skia backend. - js.JsObject? createSkiaShader(); + SkShader createSkiaShader(); } abstract class EngineGradient implements ui.Gradient, EngineShader { @@ -63,7 +63,7 @@ class GradientSweep extends EngineGradient { final Float32List? matrix4; @override - js.JsObject createSkiaShader() { + SkShader createSkiaShader() { throw UnimplementedError(); } } @@ -155,18 +155,17 @@ class GradientLinear extends EngineGradient { } @override - js.JsObject? createSkiaShader() { + SkShader createSkiaShader() { assert(experimentalUseSkia); var jsColors = makeColorList(colors); - - return canvasKit['SkShader'].callMethod('MakeLinearGradient', [ - makeSkPoint(from), - makeSkPoint(to), + return canvasKitJs.SkShader.MakeLinearGradient( + toSkPoint(from), + toSkPoint(to), jsColors, - makeSkiaColorStops(colorStops), - tileMode.index, - ]); + toSkColorStops(colorStops), + toSkTileMode(tileMode), + ); } } @@ -211,20 +210,20 @@ class GradientRadial extends EngineGradient { } @override - js.JsObject? createSkiaShader() { + SkShader createSkiaShader() { assert(experimentalUseSkia); var jsColors = makeColorList(colors); - return canvasKit['SkShader'].callMethod('MakeRadialGradient', [ - makeSkPoint(center), + return canvasKitJs.SkShader.MakeRadialGradient( + toSkPoint(center), radius, jsColors, - makeSkiaColorStops(colorStops), - tileMode.index, - matrix4 != null ? makeSkMatrixFromFloat32(matrix4) : null, + toSkColorStops(colorStops), + toSkTileMode(tileMode), + matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, 0, - ]); + ); } } @@ -248,23 +247,22 @@ class GradientConical extends EngineGradient { } @override - js.JsObject? createSkiaShader() { + SkShader createSkiaShader() { assert(experimentalUseSkia); var jsColors = makeColorList(colors); - return canvasKit['SkShader'] - .callMethod('MakeTwoPointConicalGradient', [ - makeSkPoint(focal), + return canvasKitJs.SkShader.MakeTwoPointConicalGradient( + toSkPoint(focal), focalRadius, - makeSkPoint(center), + toSkPoint(center), radius, jsColors, - makeSkiaColorStops(colorStops), - tileMode.index, - matrix4 != null ? makeSkMatrixFromFloat32(matrix4) : null, + toSkColorStops(colorStops), + toSkTileMode(tileMode), + matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, 0, - ]); + ); } } @@ -293,18 +291,6 @@ class EngineImageFilter implements ui.ImageFilter { } } -js.JsObject? _skTileMode(ui.TileMode tileMode) { - switch (tileMode) { - case ui.TileMode.clamp: - return canvasKit['TileMode']['Clamp']; - case ui.TileMode.repeated: - return canvasKit['TileMode']['Repeat']; - case ui.TileMode.mirror: - default: - return canvasKit['TileMode']['Mirror']; - } -} - /// Backend implementation of [ui.ImageShader]. class EngineImageShader implements ui.ImageShader, EngineShader { EngineImageShader( @@ -316,6 +302,8 @@ class EngineImageShader implements ui.ImageShader, EngineShader { final Float64List matrix4; final CkImage _skImage; - js.JsObject? createSkiaShader() => _skImage.skImage!.callMethod( - 'makeShader', [_skTileMode(tileModeX), _skTileMode(tileModeY)]); + SkShader createSkiaShader() => _skImage.skImage.makeShader( + toSkTileMode(tileModeX), + toSkTileMode(tileModeY), + ); } diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index ba60d47ab4596..85d3ff7618264 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -33,6 +33,11 @@ void main() { _maskFilterTests(); _colorFilterTests(); _imageFilterTests(); + _mallocTests(); + _sharedColorTests(); + _toSkPointTests(); + _toSkColorStopsTests(); + _toSkMatrixFromFloat32Tests(); }, // This test failed on iOS Safari. // TODO: https://github.com/flutter/flutter/issues/60040 @@ -258,11 +263,20 @@ void _paintTests() { paint.setAntiAlias(true); paint.setColorInt(0x00FFCCAA); paint.setShader(_makeTestShader()); - // TODO(yjbanov): paint.setMaskFilter + paint.setMaskFilter(canvasKitJs.MakeBlurMaskFilter( + canvasKitJs.BlurStyle.Outer, + 2.0, + true, + )); paint.setFilterQuality(canvasKitJs.FilterQuality.High); - // TODO(yjbanov): paint.setColorFilter + paint.setColorFilter(canvasKitJs.SkColorFilter.MakeLinearToSRGBGamma()); paint.setStrokeMiter(1.4); - // TODO(yjbanov): paint.setImageFilter + paint.setImageFilter(canvasKitJs.SkImageFilter.MakeBlur( + 1, + 2, + canvasKitJs.TileMode.Repeat, + null, + )); }); } @@ -331,6 +345,104 @@ void _imageFilterTests() { }); } +void _mallocTests() { + test('SkFloat32List', () { + for (int size = 0; size < 1000; size++) { + final SkFloat32List skList = mallocFloat32List(4); + expect(skList, isNotNull); + expect(skList.toTypedArray().length, 4); + } + }); +} + +void _sharedColorTests() { + test('toSharedSkColor1', () { + expect( + toSharedSkColor1(const ui.Color(0xAABBCCDD)), + Float32List(4) + ..[0] = 0xBB / 255.0 + ..[1] = 0xCC / 255.0 + ..[2] = 0xDD / 255.0 + ..[3] = 0xAA / 255.0, + ); + }); + test('toSharedSkColor2', () { + expect( + toSharedSkColor2(const ui.Color(0xAABBCCDD)), + Float32List(4) + ..[0] = 0xBB / 255.0 + ..[1] = 0xCC / 255.0 + ..[2] = 0xDD / 255.0 + ..[3] = 0xAA / 255.0, + ); + }); + test('toSharedSkColor3', () { + expect( + toSharedSkColor3(const ui.Color(0xAABBCCDD)), + Float32List(4) + ..[0] = 0xBB / 255.0 + ..[1] = 0xCC / 255.0 + ..[2] = 0xDD / 255.0 + ..[3] = 0xAA / 255.0, + ); + }); +} + +void _toSkPointTests() { + test('toSkPoint', () { + expect( + toSkPoint(const ui.Offset(4, 5)), + Float32List(2) + ..[0] = 4.0 + ..[1] = 5.0, + ); + }); +} + +void _toSkColorStopsTests() { + test('toSkColorStops default', () { + expect( + toSkColorStops(null), + Float32List(2) + ..[0] = 0 + ..[1] = 1, + ); + }); + + test('toSkColorStops custom', () { + expect( + toSkColorStops([1, 2, 3, 4]), + Float32List(4) + ..[0] = 1 + ..[1] = 2 + ..[2] = 3 + ..[3] = 4, + ); + }); +} + +void _toSkMatrixFromFloat32Tests() { + test('toSkMatrixFromFloat32', () { + final Matrix4 matrix = Matrix4.identity() + ..translate(1, 2, 3) + ..rotateZ(4); + expect( + toSkMatrixFromFloat32(matrix.storage), + Float32List.fromList([ + -0.6536436080932617, + 0.756802499294281, + 1, + -0.756802499294281, + -0.6536436080932617, + 2, + -0.0, + 0, + 1, + ]) + ); + }); +} + final Uint8List kTransparentImage = Uint8List.fromList([ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,