blob: f8c28fb5122d8c6de35c07adf8baf9e929839157 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of engine;
/// An error related to the CanvasKit rendering backend.
class CanvasKitError extends Error {
CanvasKitError(this.message);
/// Describes this error.
final String message;
@override
String toString() => 'CanvasKitError: $message';
}
/// Converts a list of [ui.Color] into the 2d array expected by CanvasKit.
js.JsArray<Float32List> makeColorList(List<ui.Color> colors) {
var result = js.JsArray<Float32List>();
result.length = colors.length;
for (var i = 0; i < colors.length; i++) {
var color = colors[i];
var jsColor = Float32List(4);
jsColor[0] = color.red / 255.0;
jsColor[1] = color.green / 255.0;
jsColor[2] = color.blue / 255.0;
jsColor[3] = color.alpha / 255.0;
result[i] = jsColor;
}
return result;
}
js.JsObject _mallocColorArray() {
return canvasKit
.callMethod('Malloc', <dynamic>[js.context['Float32Array'], 4]);
}
js.JsObject? sharedSkColor1;
js.JsObject? sharedSkColor2;
js.JsObject? sharedSkColor3;
void _setSharedColor(js.JsObject sharedColor, ui.Color color) {
Float32List array = sharedColor.callMethod('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;
}
void setSharedSkColor1(ui.Color color) {
if (sharedSkColor1 == null) {
sharedSkColor1 = _mallocColorArray();
}
_setSharedColor(sharedSkColor1!, color);
}
void setSharedSkColor2(ui.Color color) {
if (sharedSkColor2 == null) {
sharedSkColor2 = _mallocColorArray();
}
_setSharedColor(sharedSkColor2!, color);
}
void setSharedSkColor3(ui.Color color) {
if (sharedSkColor3 == null) {
sharedSkColor3 = _mallocColorArray();
}
_setSharedColor(sharedSkColor3!, color);
}
/// Creates a new color array.
Float32List makeFreshSkColor(ui.Color color) {
final Float32List result = Float32List(4);
result[0] = color.red / 255.0;
result[1] = color.green / 255.0;
result[2] = color.blue / 255.0;
result[3] = color.alpha / 255.0;
return result;
}
js.JsObject makeSkRect(ui.Rect rect) {
return js.JsObject(canvasKit['LTRBRect'],
<double>[rect.left, rect.top, rect.right, rect.bottom]);
}
js.JsObject makeSkRRect(ui.RRect rrect) {
return js.JsObject.jsify({
'rect': makeSkRect(rrect.outerRect),
'rx1': rrect.tlRadiusX,
'ry1': rrect.tlRadiusY,
'rx2': rrect.trRadiusX,
'ry2': rrect.trRadiusY,
'rx3': rrect.brRadiusX,
'ry3': rrect.brRadiusY,
'rx4': rrect.blRadiusX,
'ry4': rrect.blRadiusY,
});
}
ui.Rect fromSkRect(js.JsObject skRect) {
return ui.Rect.fromLTRB(
skRect['fLeft'],
skRect['fTop'],
skRect['fRight'],
skRect['fBottom'],
);
}
ui.TextPosition fromPositionWithAffinity(js.JsObject positionWithAffinity) {
if (positionWithAffinity['affinity'] == canvasKit['Affinity']['Upstream']) {
return ui.TextPosition(
offset: positionWithAffinity['pos'],
affinity: ui.TextAffinity.upstream,
);
} else {
assert(positionWithAffinity['affinity'] ==
canvasKit['Affinity']['Downstream']);
return ui.TextPosition(
offset: positionWithAffinity['pos'],
affinity: ui.TextAffinity.downstream,
);
}
}
js.JsArray<double> makeSkPoint(ui.Offset point) {
final js.JsArray<double> skPoint = js.JsArray<double>();
skPoint.length = 2;
skPoint[0] = point.dx;
skPoint[1] = point.dy;
return skPoint;
}
// TODO(hterkelsen): https://github.com/flutter/flutter/issues/58824
/// Creates a point list using a 2D JS array.
js.JsArray<js.JsArray<double>>? encodePointList(List<ui.Offset>? points) {
if (points == null) {
return null;
}
final int pointCount = points.length;
final js.JsArray<js.JsArray<double>> result =
js.JsArray<js.JsArray<double>>();
result.length = pointCount;
for (int i = 0; i < pointCount; ++i) {
final ui.Offset point = points[i];
assert(_offsetIsValid(point));
final js.JsArray<double> skPoint = js.JsArray<double>();
skPoint.length = 2;
skPoint[0] = point.dx;
skPoint[1] = point.dy;
result[i] = skPoint;
}
return result;
}
// TODO(hterkelsen): https://github.com/flutter/flutter/issues/58824
/// Creates a point list using a 2D JS array.
List<List<double>>? encodeRawPointList(Float32List? points) {
if (points == null) {
return null;
}
assert(points.length % 2 == 0);
var pointLength = points.length ~/ 2;
final js.JsArray<js.JsArray<double>> result =
js.JsArray<js.JsArray<double>>();
result.length = pointLength;
for (var i = 0; i < pointLength; i++) {
var x = i * 2;
var y = x + 1;
final js.JsArray<double> skPoint = js.JsArray<double>();
skPoint.length = 2;
skPoint[0] = points[x];
skPoint[1] = points[y];
result[i] = skPoint;
}
return result;
}
js.JsObject? makeSkPointMode(ui.PointMode pointMode) {
switch (pointMode) {
case ui.PointMode.points:
return canvasKit['PointMode']['Points'];
case ui.PointMode.lines:
return canvasKit['PointMode']['Lines'];
case ui.PointMode.polygon:
return canvasKit['PointMode']['Polygon'];
default:
throw StateError('Unrecognized point mode $pointMode');
}
}
js.JsObject? makeSkBlendMode(ui.BlendMode? blendMode) {
switch (blendMode) {
case ui.BlendMode.clear:
return canvasKit['BlendMode']['Clear'];
case ui.BlendMode.src:
return canvasKit['BlendMode']['Src'];
case ui.BlendMode.dst:
return canvasKit['BlendMode']['Dst'];
case ui.BlendMode.srcOver:
return canvasKit['BlendMode']['SrcOver'];
case ui.BlendMode.dstOver:
return canvasKit['BlendMode']['DstOver'];
case ui.BlendMode.srcIn:
return canvasKit['BlendMode']['SrcIn'];
case ui.BlendMode.dstIn:
return canvasKit['BlendMode']['DstIn'];
case ui.BlendMode.srcOut:
return canvasKit['BlendMode']['SrcOut'];
case ui.BlendMode.dstOut:
return canvasKit['BlendMode']['DstOut'];
case ui.BlendMode.srcATop:
return canvasKit['BlendMode']['SrcATop'];
case ui.BlendMode.dstATop:
return canvasKit['BlendMode']['DstATop'];
case ui.BlendMode.xor:
return canvasKit['BlendMode']['Xor'];
case ui.BlendMode.plus:
return canvasKit['BlendMode']['Plus'];
case ui.BlendMode.modulate:
return canvasKit['BlendMode']['Modulate'];
case ui.BlendMode.screen:
return canvasKit['BlendMode']['Screen'];
case ui.BlendMode.overlay:
return canvasKit['BlendMode']['Overlay'];
case ui.BlendMode.darken:
return canvasKit['BlendMode']['Darken'];
case ui.BlendMode.lighten:
return canvasKit['BlendMode']['Lighten'];
case ui.BlendMode.colorDodge:
return canvasKit['BlendMode']['ColorDodge'];
case ui.BlendMode.colorBurn:
return canvasKit['BlendMode']['ColorBurn'];
case ui.BlendMode.hardLight:
return canvasKit['BlendMode']['HardLight'];
case ui.BlendMode.softLight:
return canvasKit['BlendMode']['SoftLight'];
case ui.BlendMode.difference:
return canvasKit['BlendMode']['Difference'];
case ui.BlendMode.exclusion:
return canvasKit['BlendMode']['Exclusion'];
case ui.BlendMode.multiply:
return canvasKit['BlendMode']['Multiply'];
case ui.BlendMode.hue:
return canvasKit['BlendMode']['Hue'];
case ui.BlendMode.saturation:
return canvasKit['BlendMode']['Saturation'];
case ui.BlendMode.color:
return canvasKit['BlendMode']['Color'];
case ui.BlendMode.luminosity:
return canvasKit['BlendMode']['Luminosity'];
default:
return null;
}
}
// Mappings from SkMatrix-index to input-index.
const List<int> _skMatrixIndexToMatrix4Index = <int>[
0, 4, 12, // Row 1
1, 5, 13, // Row 2
3, 7, 15, // Row 3
];
/// Converts a 4x4 Flutter matrix (represented as a [Float32List]) to an
/// SkMatrix, which is a 3x3 transform matrix.
js.JsArray<double> makeSkMatrixFromFloat32(Float32List? matrix4) {
final js.JsArray<double> skMatrix = js.JsArray<double>();
skMatrix.length = 9;
for (int i = 0; i < 9; ++i) {
final int matrix4Index = _skMatrixIndexToMatrix4Index[i];
if (matrix4Index < matrix4!.length)
skMatrix[i] = matrix4[matrix4Index];
else
skMatrix[i] = 0.0;
}
return skMatrix;
}
js.JsArray<double> makeSkMatrixFromFloat64(Float64List matrix4) {
final js.JsArray<double> skMatrix = js.JsArray<double>();
skMatrix.length = 9;
for (int i = 0; i < 9; ++i) {
final int matrix4Index = _skMatrixIndexToMatrix4Index[i];
if (matrix4Index < matrix4.length)
skMatrix[i] = matrix4[matrix4Index];
else
skMatrix[i] = 0.0;
}
return skMatrix;
}
/// Color stops used when the framework specifies `null`.
final js.JsArray<double> _kDefaultColorStops = () {
final js.JsArray<double> jsColorStops = js.JsArray<double>();
jsColorStops.length = 2;
jsColorStops[0] = 0;
jsColorStops[1] = 1;
return jsColorStops;
}();
/// 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.
js.JsArray<double> makeSkiaColorStops(List<double>? colorStops) {
if (colorStops == null) {
return _kDefaultColorStops;
}
final js.JsArray<double> jsColorStops = js.JsArray<double>.from(colorStops);
jsColorStops.length = colorStops.length;
return jsColorStops;
}
void drawSkShadow(
js.JsObject skCanvas,
CkPath path,
ui.Color color,
double elevation,
bool transparentOccluder,
double devicePixelRatio,
) {
const double ambientAlpha = 0.039;
const double spotAlpha = 0.25;
final int flags = transparentOccluder ? 0x01 : 0x00;
final ui.Rect bounds = path.getBounds();
final double shadowX = (bounds.left + bounds.right) / 2.0;
final double shadowY = bounds.top - 600.0;
ui.Color inAmbient = color.withAlpha((color.alpha * ambientAlpha).round());
ui.Color inSpot = color.withAlpha((color.alpha * spotAlpha).round());
final js.JsObject inTonalColors = js.JsObject.jsify(<String, Float32List>{
'ambient': makeFreshSkColor(inAmbient),
'spot': makeFreshSkColor(inSpot),
});
final js.JsObject tonalColors =
canvasKit.callMethod('computeTonalColors', <js.JsObject>[inTonalColors]);
skCanvas.callMethod('drawShadow', <dynamic>[
path._skPath,
js.JsArray<double>.from(<double>[0, 0, devicePixelRatio * elevation]),
js.JsArray<double>.from(
<double>[shadowX, shadowY, devicePixelRatio * kLightHeight]),
devicePixelRatio * kLightRadius,
tonalColors['ambient'],
tonalColors['spot'],
flags,
]);
}