Skip to content

Commit

Permalink
CkPaint uses SkPaint (#19562)
Browse files Browse the repository at this point in the history
  • Loading branch information
yjbanov committed Jul 11, 2020
1 parent 8063923 commit c99deb0
Show file tree
Hide file tree
Showing 8 changed files with 445 additions and 226 deletions.
171 changes: 166 additions & 5 deletions lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart
Expand Up @@ -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')
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Up @@ -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
Expand Down
34 changes: 15 additions & 19 deletions lib/web_ui/lib/src/engine/compositor/image.dart
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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() {
Expand Down
21 changes: 12 additions & 9 deletions lib/web_ui/lib/src/engine/compositor/image_filter.dart
Expand Up @@ -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) {
Expand Down

0 comments on commit c99deb0

Please sign in to comment.