| // 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. |
| |
| // @dart = 2.6 |
| part of engine; |
| |
| bool _offsetIsValid(ui.Offset offset) { |
| assert(offset != null, 'Offset argument was null.'); |
| assert(!offset.dx.isNaN && !offset.dy.isNaN, |
| 'Offset argument contained a NaN value.'); |
| return true; |
| } |
| |
| bool _matrix4IsValid(Float32List matrix4) { |
| assert(matrix4 != null, 'Matrix4 argument was null.'); |
| assert(matrix4.length == 16, 'Matrix4 must have 16 entries.'); |
| return true; |
| } |
| |
| abstract class EngineShader { |
| /// Create a shader for use in the Skia backend. |
| js.JsObject createSkiaShader(); |
| } |
| |
| abstract class EngineGradient implements ui.Gradient, EngineShader { |
| /// Hidden constructor to prevent subclassing. |
| EngineGradient._(); |
| |
| /// Creates a fill style to be used in painting. |
| Object createPaintStyle(html.CanvasRenderingContext2D ctx); |
| |
| List<dynamic> webOnlySerializeToCssPaint() { |
| throw UnsupportedError('CSS paint not implemented for this shader type'); |
| } |
| } |
| |
| class GradientSweep extends EngineGradient { |
| GradientSweep(this.center, this.colors, this.colorStops, this.tileMode, |
| this.startAngle, this.endAngle, this.matrix4) |
| : assert(_offsetIsValid(center)), |
| assert(colors != null), |
| assert(tileMode != null), |
| assert(startAngle != null), |
| assert(endAngle != null), |
| assert(startAngle < endAngle), |
| assert(matrix4 == null || _matrix4IsValid(matrix4)), |
| super._() { |
| _validateColorStops(colors, colorStops); |
| } |
| |
| @override |
| Object createPaintStyle(html.CanvasRenderingContext2D ctx) { |
| throw UnimplementedError(); |
| } |
| |
| final ui.Offset center; |
| final List<ui.Color> colors; |
| final List<double> colorStops; |
| final ui.TileMode tileMode; |
| final double startAngle; |
| final double endAngle; |
| final Float32List matrix4; |
| |
| @override |
| js.JsObject createSkiaShader() { |
| throw UnimplementedError(); |
| } |
| } |
| |
| void _validateColorStops(List<ui.Color> colors, List<double> colorStops) { |
| if (colorStops == null) { |
| if (colors.length != 2) |
| throw ArgumentError( |
| '"colors" must have length 2 if "colorStops" is omitted.'); |
| } else { |
| if (colors.length != colorStops.length) |
| throw ArgumentError( |
| '"colors" and "colorStops" arguments must have equal length.'); |
| } |
| } |
| |
| class GradientLinear extends EngineGradient { |
| GradientLinear( |
| this.from, |
| this.to, |
| this.colors, |
| this.colorStops, |
| this.tileMode, |
| Float64List matrix, |
| ) : assert(_offsetIsValid(from)), |
| assert(_offsetIsValid(to)), |
| assert(colors != null), |
| assert(tileMode != null), |
| this.matrix4 = matrix == null ? null : _FastMatrix64(matrix), |
| super._() { |
| if (assertionsEnabled) { |
| _validateColorStops(colors, colorStops); |
| } |
| } |
| |
| final ui.Offset from; |
| final ui.Offset to; |
| final List<ui.Color> colors; |
| final List<double> colorStops; |
| final ui.TileMode tileMode; |
| final _FastMatrix64 matrix4; |
| |
| @override |
| html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D ctx) { |
| final bool hasMatrix = matrix4 != null; |
| html.CanvasGradient gradient; |
| if (hasMatrix) { |
| final centerX = (from.dx + to.dx) / 2.0; |
| final centerY = (from.dy + to.dy) / 2.0; |
| matrix4.transform(from.dx - centerX, from.dy - centerY); |
| final double fromX = matrix4.transformedX + centerX; |
| final double fromY = matrix4.transformedY + centerY; |
| matrix4.transform(to.dx - centerX, to.dy - centerY); |
| gradient = ctx.createLinearGradient(fromX, fromY, |
| matrix4.transformedX + centerX, matrix4.transformedY + centerY); |
| } else { |
| gradient = ctx.createLinearGradient(from.dx, from.dy, to.dx, to.dy); |
| } |
| |
| if (colorStops == null) { |
| assert(colors.length == 2); |
| gradient.addColorStop(0, colorToCssString(colors[0])); |
| gradient.addColorStop(1, colorToCssString(colors[1])); |
| return gradient; |
| } |
| for (int i = 0; i < colors.length; i++) { |
| gradient.addColorStop(colorStops[i], colorToCssString(colors[i])); |
| } |
| return gradient; |
| } |
| |
| @override |
| List<dynamic> webOnlySerializeToCssPaint() { |
| final List<dynamic> serializedColors = <dynamic>[]; |
| for (int i = 0; i < colors.length; i++) { |
| serializedColors.add(colorToCssString(colors[i])); |
| } |
| return <dynamic>[ |
| 1, |
| from.dx, |
| from.dy, |
| to.dx, |
| to.dy, |
| serializedColors, |
| colorStops, |
| tileMode.index |
| ]; |
| } |
| |
| @override |
| js.JsObject createSkiaShader() { |
| assert(experimentalUseSkia); |
| |
| final js.JsArray<num> jsColors = js.JsArray<num>(); |
| jsColors.length = colors.length; |
| for (int i = 0; i < colors.length; i++) { |
| jsColors[i] = colors[i].value; |
| } |
| |
| return canvasKit.callMethod('MakeLinearGradientShader', <dynamic>[ |
| makeSkPoint(from), |
| makeSkPoint(to), |
| jsColors, |
| makeSkiaColorStops(colorStops), |
| tileMode.index, |
| ]); |
| } |
| } |
| |
| // TODO(flutter_web): For transforms and tile modes implement as webgl |
| // For now only GradientRotation is supported in flutter which is implemented |
| // for linear gradient. |
| // See https://github.com/flutter/flutter/issues/32819 |
| class GradientRadial extends EngineGradient { |
| GradientRadial(this.center, this.radius, this.colors, this.colorStops, |
| this.tileMode, this.matrix4) |
| : super._(); |
| |
| final ui.Offset center; |
| final double radius; |
| final List<ui.Color> colors; |
| final List<double> colorStops; |
| final ui.TileMode tileMode; |
| final Float32List matrix4; |
| |
| @override |
| Object createPaintStyle(html.CanvasRenderingContext2D ctx) { |
| if (!experimentalUseSkia) { |
| if (tileMode != ui.TileMode.clamp) { |
| throw UnimplementedError( |
| 'TileMode not supported in GradientRadial shader'); |
| } |
| } |
| final html.CanvasGradient gradient = ctx.createRadialGradient( |
| center.dx, center.dy, 0, center.dx, center.dy, radius); |
| if (colorStops == null) { |
| assert(colors.length == 2); |
| gradient.addColorStop(0, colorToCssString(colors[0])); |
| gradient.addColorStop(1, colorToCssString(colors[1])); |
| return gradient; |
| } else { |
| for (int i = 0; i < colors.length; i++) { |
| gradient.addColorStop(colorStops[i], colorToCssString(colors[i])); |
| } |
| } |
| return gradient; |
| } |
| |
| @override |
| js.JsObject createSkiaShader() { |
| assert(experimentalUseSkia); |
| |
| final js.JsArray<num> jsColors = js.JsArray<num>(); |
| jsColors.length = colors.length; |
| for (int i = 0; i < colors.length; i++) { |
| jsColors[i] = colors[i].value; |
| } |
| |
| return canvasKit.callMethod('MakeRadialGradientShader', <dynamic>[ |
| makeSkPoint(center), |
| radius, |
| jsColors, |
| makeSkiaColorStops(colorStops), |
| tileMode.index, |
| matrix4 != null ? makeSkMatrixFromFloat32(matrix4) : null, |
| 0, |
| ]); |
| } |
| } |
| |
| class GradientConical extends EngineGradient { |
| GradientConical(this.focal, this.focalRadius, this.center, this.radius, |
| this.colors, this.colorStops, this.tileMode, this.matrix4) |
| : super._(); |
| |
| final ui.Offset focal; |
| final double focalRadius; |
| final ui.Offset center; |
| final double radius; |
| final List<ui.Color> colors; |
| final List<double> colorStops; |
| final ui.TileMode tileMode; |
| final Float32List matrix4; |
| |
| @override |
| Object createPaintStyle(html.CanvasRenderingContext2D ctx) { |
| throw UnimplementedError(); |
| } |
| |
| @override |
| js.JsObject createSkiaShader() { |
| assert(experimentalUseSkia); |
| |
| final js.JsArray<num> jsColors = js.JsArray<num>(); |
| jsColors.length = colors.length; |
| for (int i = 0; i < colors.length; i++) { |
| jsColors[i] = colors[i].value; |
| } |
| |
| return canvasKit.callMethod('MakeTwoPointConicalGradient', <dynamic>[ |
| makeSkPoint(focal), |
| focalRadius, |
| makeSkPoint(center), |
| radius, |
| jsColors, |
| makeSkiaColorStops(colorStops), |
| tileMode.index, |
| matrix4 != null ? makeSkMatrixFromFloat32(matrix4) : null, |
| 0, |
| ]); |
| } |
| } |
| |
| /// Backend implementation of [ui.ImageFilter]. |
| /// |
| /// Currently only `blur` is supported. |
| class EngineImageFilter implements ui.ImageFilter { |
| EngineImageFilter.blur({this.sigmaX = 0.0, this.sigmaY = 0.0}); |
| |
| final double sigmaX; |
| final double sigmaY; |
| |
| @override |
| bool operator ==(dynamic other) { |
| if (other is! EngineImageFilter) { |
| return false; |
| } |
| final EngineImageFilter typedOther = other; |
| return sigmaX == typedOther.sigmaX && sigmaY == typedOther.sigmaY; |
| } |
| |
| @override |
| int get hashCode => ui.hashValues(sigmaX, sigmaY); |
| |
| @override |
| String toString() { |
| return 'ImageFilter.blur($sigmaX, $sigmaY)'; |
| } |
| } |
| |
| 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(ui.Image image, this.tileModeX, this.tileModeY, |
| this.matrix4) : _skImage = image as SkImage; |
| |
| final ui.TileMode tileModeX; |
| final ui.TileMode tileModeY; |
| final Float64List matrix4; |
| final SkImage _skImage; |
| |
| js.JsObject createSkiaShader() => _skImage.skImage.callMethod('makeShader', |
| <dynamic>[_skTileMode(tileModeX), _skTileMode(tileModeY)]); |
| } |