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

CkPaint uses SkPaint #19562

Merged
merged 1 commit into from
Jul 11, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 166 additions & 5 deletions lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart
Original file line number Diff line number Diff line change
@@ -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<double>? 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);
31 changes: 19 additions & 12 deletions lib/web_ui/lib/src/engine/compositor/color_filter.dart
Original file line number Diff line number Diff line change
@@ -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', <dynamic>[
sharedSkColor1,
makeSkBlendMode(_engineFilter._blendMode),
]);
skColorFilter = canvasKitJs.SkColorFilter.MakeBlend(
toSharedSkColor1(_engineFilter._color!),
toSkBlendMode(_engineFilter._blendMode!),
);
break;
case EngineColorFilter._TypeMatrix:
final js.JsArray<double> colorMatrix = js.JsArray<double>();
colorMatrix.length = 20;
final Float32List colorMatrix = Float32List(20);
final List<double> matrix = _engineFilter._matrix!;
for (int i = 0; i < 20; i++) {
colorMatrix[i] = _engineFilter._matrix![i];
colorMatrix[i] = matrix[i];
}
return canvasKit['SkColorFilter']
.callMethod('MakeMatrix', <js.JsArray>[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
34 changes: 15 additions & 19 deletions lib/web_ui/lib/src/engine/compositor/image.dart
Original file line number Diff line number Diff line change
@@ -8,45 +8,42 @@ part of engine;
/// Instantiates a [ui.Codec] backed by an `SkImage` from Skia.
void skiaInstantiateImageCodec(Uint8List list, Callback<ui.Codec> callback,
[int? width, int? height, int? format, int? rowBytes]) {
final js.JsObject? skAnimatedImage =
canvasKit.callMethod('MakeAnimatedImageFromEncoded', <Uint8List>[list]);
final SkAnimatedImage skAnimatedImage = canvasKitJs.MakeAnimatedImageFromEncoded(list);
final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage);
final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage);
callback(codec);
}

/// 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<ByteData> 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<ByteData> 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<ui.FrameInfo> getNextFrame() {
21 changes: 12 additions & 9 deletions lib/web_ui/lib/src/engine/compositor/image_filter.dart
Original file line number Diff line number Diff line change
@@ -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',
<dynamic>[
_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) {
27 changes: 9 additions & 18 deletions lib/web_ui/lib/src/engine/compositor/mask_filter.dart
Original file line number Diff line number Diff line change
@@ -13,30 +13,21 @@ class CkMaskFilter extends ResurrectableSkiaObject {
final ui.BlurStyle _blurStyle;
final double _sigma;

SkMaskFilter? _skMaskFilter;

@override
js.JsObject createDefault() => _initSkiaObject();

@override
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', <dynamic>[skBlurStyle, _sigma, true]);
final SkMaskFilter skMaskFilter = canvasKitJs.MakeBlurMaskFilter(
toSkBlurStyle(_blurStyle),
_sigma,
true,
);
_skMaskFilter = skMaskFilter;
return _jsObjectWrapper.wrapSkMaskFilter(skMaskFilter);
}
}
201 changes: 81 additions & 120 deletions lib/web_ui/lib/src/engine/compositor/painting.dart
Original file line number Diff line number Diff line change
@@ -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', <js.JsObject?>[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', <js.JsObject?>[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', <double>[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', <bool>[_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', <int>[
_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', <js.JsObject?>[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', <js.JsObject?>[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', <js.JsObject?>[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', <js.JsObject?>[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', <js.JsObject?>[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);
}
}
68 changes: 28 additions & 40 deletions lib/web_ui/lib/src/engine/shader.dart
Original file line number Diff line number Diff line change
@@ -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', <dynamic>[
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', <dynamic>[
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', <dynamic>[
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', <dynamic>[_skTileMode(tileModeX), _skTileMode(tileModeY)]);
SkShader createSkiaShader() => _skImage.skImage.makeShader(
toSkTileMode(tileModeX),
toSkTileMode(tileModeY),
);
}
118 changes: 115 additions & 3 deletions lib/web_ui/test/canvaskit/canvaskit_api_test.dart
Original file line number Diff line number Diff line change
@@ -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(<double>[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(<double>[
-0.6536436080932617,
0.756802499294281,
1,
-0.756802499294281,
-0.6536436080932617,
2,
-0.0,
0,
1,
])
);
});
}

final Uint8List kTransparentImage = Uint8List.fromList(<int>[
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,