diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c441a606189ba..5bb1a51273993 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -451,6 +451,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/picture_recorder.dar FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/platform_message.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/raster_cache.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/rasterizer.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/shader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/surface.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/text.dart @@ -489,7 +490,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/services/buffers.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/services/message_codec.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/services/message_codecs.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/services/serialization.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/shader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/shadow.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/canvas.dart @@ -514,6 +514,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/render_vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene_builder.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/shader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/surface.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/surface_stats.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/transform.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 165a672cebb57..0f2aa4e6c6485 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -52,6 +52,7 @@ part 'engine/compositor/picture_recorder.dart'; part 'engine/compositor/platform_message.dart'; part 'engine/compositor/raster_cache.dart'; part 'engine/compositor/rasterizer.dart'; +part 'engine/compositor/shader.dart'; part 'engine/compositor/skia_object_cache.dart'; part 'engine/compositor/surface.dart'; part 'engine/compositor/text.dart'; @@ -90,7 +91,6 @@ part 'engine/services/buffers.dart'; part 'engine/services/message_codec.dart'; part 'engine/services/message_codecs.dart'; part 'engine/services/serialization.dart'; -part 'engine/shader.dart'; part 'engine/shadow.dart'; part 'engine/surface/backdrop_filter.dart'; part 'engine/surface/canvas.dart'; @@ -115,6 +115,7 @@ part 'engine/surface/recording_canvas.dart'; part 'engine/surface/render_vertices.dart'; part 'engine/surface/scene.dart'; part 'engine/surface/scene_builder.dart'; +part 'engine/surface/shader.dart'; part 'engine/surface/surface.dart'; part 'engine/surface/surface_stats.dart'; part 'engine/surface/transform.dart'; 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 baed64f6962e2..98a67b252ecd5 100644 --- a/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart @@ -643,7 +643,11 @@ class SkImage { external void delete(); external int width(); external int height(); - external SkShader makeShader(SkTileMode tileModeX, SkTileMode tileModeY); + external SkShader makeShader( + SkTileMode tileModeX, + SkTileMode tileModeY, + Float32List? matrix, // 3x3 matrix + ); } @JS() @@ -677,10 +681,24 @@ class SkShaderNamespace { Float32List? matrix, // 3x3 matrix int flags, ); + + external SkShader MakeSweepGradient( + double cx, + double cy, + List colors, + Float32List colorStops, + SkTileMode tileMode, + Float32List? matrix, // 3x3 matrix + int flags, + double startAngle, + double endAngle, + ); } @JS() -class SkShader {} +class SkShader { + external void delete(); +} // This needs to be bound to top-level because SkPaint is initialized // with `new`. Also in Dart you can't write this: @@ -1556,7 +1574,7 @@ class TypefaceFontProviderNamespace { Timer? _skObjectCollector; List _skObjectDeleteQueue = []; -final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) { +final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) { _skObjectDeleteQueue.add(deletable); _skObjectCollector ??= _scheduleSkObjectCollection(); })); @@ -1589,7 +1607,7 @@ Timer _scheduleSkObjectCollection() => Timer(Duration.zero, () { html.window.performance.measure('SkObject collection', 'SkObject collection-start', 'SkObject collection-end'); }); -typedef SkObjectFinalizer = void Function(SkDeletable deletable); +typedef SkObjectFinalizer = void Function(T key); /// Any Skia object that has a `delete` method. @JS() @@ -1613,8 +1631,8 @@ class SkDeletable { /// 5. The finalizer function is called with the SkPaint as the sole argument. /// 6. We call `delete` on SkPaint. @JS('window.FinalizationRegistry') -class SkObjectFinalizationRegistry { - external SkObjectFinalizationRegistry(SkObjectFinalizer finalizer); +class SkObjectFinalizationRegistry { + external SkObjectFinalizationRegistry(SkObjectFinalizer finalizer); external void register(Object ckObject, Object skObject); } diff --git a/lib/web_ui/lib/src/engine/compositor/image.dart b/lib/web_ui/lib/src/engine/compositor/image.dart index 85ac7971e8b41..26006585dd647 100644 --- a/lib/web_ui/lib/src/engine/compositor/image.dart +++ b/lib/web_ui/lib/src/engine/compositor/image.dart @@ -39,11 +39,17 @@ void skiaInstantiateWebImageCodec(String src, Callback callback, class CkAnimatedImage implements ui.Image { final SkAnimatedImage _skAnimatedImage; - CkAnimatedImage(this._skAnimatedImage); + // Use a box because `SkImage` may be deleted either due to this object + // being garbage-collected, or by an explicit call to [delete]. + late final SkiaObjectBox box; + + CkAnimatedImage(this._skAnimatedImage) { + box = SkiaObjectBox(this, _skAnimatedImage as SkDeletable); + } @override void dispose() { - _skAnimatedImage.delete(); + box.delete(); } int get frameCount => _skAnimatedImage.getFrameCount(); @@ -77,11 +83,17 @@ class CkAnimatedImage implements ui.Image { class CkImage implements ui.Image { final SkImage skImage; - CkImage(this.skImage); + // Use a box because `SkImage` may be deleted either due to this object + // being garbage-collected, or by an explicit call to [delete]. + late final SkiaObjectBox box; + + CkImage(this.skImage) { + box = SkiaObjectBox(this, skImage as SkDeletable); + } @override void dispose() { - skImage.delete(); + box.delete(); } @override @@ -99,26 +111,25 @@ class CkImage implements ui.Image { /// A [Codec] that wraps an `SkAnimatedImage`. class CkAnimatedImageCodec implements ui.Codec { - CkAnimatedImage? animatedImage; + CkAnimatedImage animatedImage; CkAnimatedImageCodec(this.animatedImage); @override void dispose() { - animatedImage!.dispose(); - animatedImage = null; + animatedImage.dispose(); } @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() { - final Duration duration = animatedImage!.decodeNextFrame(); - final CkImage image = animatedImage!.currentFrameAsImage; + final Duration duration = animatedImage.decodeNextFrame(); + final CkImage image = animatedImage.currentFrameAsImage; return Future.value(AnimatedImageFrameInfo(duration, image)); } } diff --git a/lib/web_ui/lib/src/engine/compositor/painting.dart b/lib/web_ui/lib/src/engine/compositor/painting.dart index 25b59f9e7d781..550ff12351e40 100644 --- a/lib/web_ui/lib/src/engine/compositor/painting.dart +++ b/lib/web_ui/lib/src/engine/compositor/painting.dart @@ -117,17 +117,17 @@ class CkPaint extends ManagedSkiaObject implements ui.Paint { bool _invertColors = false; @override - ui.Shader? get shader => _shader as ui.Shader?; + ui.Shader? get shader => _shader; @override set shader(ui.Shader? value) { if (_shader == value) { return; } - _shader = value as EngineShader?; - skiaObject.setShader(_shader?.createSkiaShader()); + _shader = value as CkShader?; + skiaObject.setShader(_shader?.skiaObject); } - EngineShader? _shader; + CkShader? _shader; @override ui.MaskFilter? get maskFilter => _maskFilter; @@ -222,7 +222,7 @@ class CkPaint extends ManagedSkiaObject implements ui.Paint { paint.setStrokeWidth(_strokeWidth); paint.setAntiAlias(_isAntiAlias); paint.setColorInt(_color.value); - paint.setShader(_shader?.createSkiaShader()); + paint.setShader(_shader?.skiaObject); paint.setMaskFilter(_ckMaskFilter?.skiaObject); paint.setColorFilter(_ckColorFilter?.skiaObject); paint.setImageFilter(_imageFilter?.skiaObject); diff --git a/lib/web_ui/lib/src/engine/compositor/shader.dart b/lib/web_ui/lib/src/engine/compositor/shader.dart new file mode 100644 index 0000000000000..db3759297617c --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/shader.dart @@ -0,0 +1,186 @@ +// 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.10 +part of engine; + +abstract class CkShader extends ManagedSkiaObject implements ui.Shader { + @override + void delete() { + rawSkiaObject?.delete(); + } +} + +class CkGradientSweep extends CkShader implements ui.Gradient { + CkGradientSweep(this.center, this.colors, this.colorStops, this.tileMode, + this.startAngle, this.endAngle, this.matrix4) + : assert(_offsetIsValid(center)), + assert(colors != null), // ignore: unnecessary_null_comparison + assert(tileMode != null), // ignore: unnecessary_null_comparison + assert(startAngle != null), // ignore: unnecessary_null_comparison + assert(endAngle != null), // ignore: unnecessary_null_comparison + assert(startAngle < endAngle), + assert(matrix4 == null || _matrix4IsValid(matrix4)) { + _validateColorStops(colors, colorStops); + } + + final ui.Offset center; + final List colors; + final List? colorStops; + final ui.TileMode tileMode; + final double startAngle; + final double endAngle; + final Float32List? matrix4; + + @override + SkShader createDefault() { + return canvasKit.SkShader.MakeSweepGradient( + center.dx, + center.dy, + toSkFloatColorList(colors), + toSkColorStops(colorStops), + toSkTileMode(tileMode), + matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, + 0, + startAngle, + endAngle, + ); + } + + @override + SkShader resurrect() { + return createDefault(); + } +} + +class CkGradientLinear extends CkShader implements ui.Gradient { + CkGradientLinear( + this.from, + this.to, + this.colors, + this.colorStops, + this.tileMode, + Float64List? matrix, + ) : assert(_offsetIsValid(from)), + assert(_offsetIsValid(to)), + assert(colors != null), // ignore: unnecessary_null_comparison + assert(tileMode != null), // ignore: unnecessary_null_comparison + this.matrix4 = matrix == null ? null : _FastMatrix64(matrix) { + if (assertionsEnabled) { + _validateColorStops(colors, colorStops); + } + } + + final ui.Offset from; + final ui.Offset to; + final List colors; + final List? colorStops; + final ui.TileMode tileMode; + final _FastMatrix64? matrix4; + + @override + SkShader createDefault() { + assert(experimentalUseSkia); + + return canvasKit.SkShader.MakeLinearGradient( + toSkPoint(from), + toSkPoint(to), + toSkFloatColorList(colors), + toSkColorStops(colorStops), + toSkTileMode(tileMode), + ); + } + + @override + SkShader resurrect() => createDefault(); +} + +class CkGradientRadial extends CkShader implements ui.Gradient { + CkGradientRadial(this.center, this.radius, this.colors, this.colorStops, + this.tileMode, this.matrix4); + + final ui.Offset center; + final double radius; + final List colors; + final List? colorStops; + final ui.TileMode tileMode; + final Float32List? matrix4; + + @override + SkShader createDefault() { + assert(experimentalUseSkia); + + return canvasKit.SkShader.MakeRadialGradient( + toSkPoint(center), + radius, + toSkFloatColorList(colors), + toSkColorStops(colorStops), + toSkTileMode(tileMode), + matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, + 0, + ); + } + + @override + SkShader resurrect() => createDefault(); +} + +class CkGradientConical extends CkShader implements ui.Gradient { + CkGradientConical(this.focal, this.focalRadius, this.center, this.radius, + this.colors, this.colorStops, this.tileMode, this.matrix4); + + final ui.Offset focal; + final double focalRadius; + final ui.Offset center; + final double radius; + final List colors; + final List? colorStops; + final ui.TileMode tileMode; + final Float32List? matrix4; + + @override + SkShader createDefault() { + assert(experimentalUseSkia); + return canvasKit.SkShader.MakeTwoPointConicalGradient( + toSkPoint(focal), + focalRadius, + toSkPoint(center), + radius, + toSkFloatColorList(colors), + toSkColorStops(colorStops), + toSkTileMode(tileMode), + matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, + 0, + ); + } + + @override + SkShader resurrect() => createDefault(); +} + +class CkImageShader extends CkShader implements ui.ImageShader { + CkImageShader( + ui.Image image, this.tileModeX, this.tileModeY, this.matrix4) + : _skImage = image as CkImage; + + final ui.TileMode tileModeX; + final ui.TileMode tileModeY; + final Float64List matrix4; + final CkImage _skImage; + + @override + SkShader createDefault() => _skImage.skImage.makeShader( + toSkTileMode(tileModeX), + toSkTileMode(tileModeY), + matrix4 != null ? toSkMatrixFromFloat64(matrix4) : null, + ); + + @override + SkShader resurrect() => createDefault(); + + @override + void delete() { + rawSkiaObject?.delete(); + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart b/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart index b964c27015753..160ee172f2f7d 100644 --- a/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart +++ b/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart @@ -239,6 +239,50 @@ abstract class OneShotSkiaObject extends SkiaObject { } } +/// Manages the lifecycle of a Skia object owned by a wrapper object. +/// +/// When the wrapper is garbage collected, deletes the corresponding +/// [skObject] (only in browsers that support weak references). +/// +/// The [delete] method can be used to eagerly delete the [skObject] +/// before the wrapper is garbage collected. +/// +/// The [delete] method may be called any number of times. The box +/// will only delete the object once. +class SkiaObjectBox { + SkiaObjectBox(Object wrapper, this.skObject) { + if (browserSupportsFinalizationRegistry) { + boxRegistry.register(wrapper, this); + } + } + + /// The Skia object whose lifecycle is being managed. + final SkDeletable skObject; + + /// Whether this object has been deleted. + bool get isDeleted => _isDeleted; + bool _isDeleted = false; + + /// Deletes Skia objects when their wrappers are garbage collected. + static final SkObjectFinalizationRegistry boxRegistry = + SkObjectFinalizationRegistry( + js.allowInterop((SkiaObjectBox box) { + box.delete(); + })); + + /// Deletes the [skObject]. + /// + /// Does nothing if the object has already been deleted. + void delete() { + if (_isDeleted) { + return; + } + _isDeleted = true; + _skObjectDeleteQueue.add(skObject); + _skObjectCollector ??= _scheduleSkObjectCollection(); + } +} + /// Singleton that manages the lifecycles of [SkiaObject] instances. class SkiaObjects { @visibleForTesting diff --git a/lib/web_ui/lib/src/engine/shader.dart b/lib/web_ui/lib/src/engine/surface/shader.dart similarity index 70% rename from lib/web_ui/lib/src/engine/shader.dart rename to lib/web_ui/lib/src/engine/surface/shader.dart index 46d7153f9b4ec..6fb21b6f70b67 100644 --- a/lib/web_ui/lib/src/engine/shader.dart +++ b/lib/web_ui/lib/src/engine/surface/shader.dart @@ -5,25 +5,7 @@ // @dart = 2.10 part of engine; -bool _offsetIsValid(ui.Offset offset) { - assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison - 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.'); // ignore: unnecessary_null_comparison - assert(matrix4.length == 16, 'Matrix4 must have 16 entries.'); - return true; -} - -abstract class EngineShader { - /// Create a shader for use in the Skia backend. - SkShader createSkiaShader(); -} - -abstract class EngineGradient implements ui.Gradient, EngineShader { +abstract class EngineGradient implements ui.Gradient { /// Hidden constructor to prevent subclassing. EngineGradient._(); @@ -61,23 +43,6 @@ class GradientSweep extends EngineGradient { final double startAngle; final double endAngle; final Float32List? matrix4; - - @override - SkShader createSkiaShader() { - throw UnimplementedError(); - } -} - -void _validateColorStops(List colors, List? 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 { @@ -153,19 +118,6 @@ class GradientLinear extends EngineGradient { tileMode.index ]; } - - @override - SkShader createSkiaShader() { - assert(experimentalUseSkia); - - return canvasKit.SkShader.MakeLinearGradient( - toSkPoint(from), - toSkPoint(to), - toSkFloatColorList(colors), - toSkColorStops(colorStops), - toSkTileMode(tileMode), - ); - } } // TODO(flutter_web): For transforms and tile modes implement as webgl @@ -207,21 +159,6 @@ class GradientRadial extends EngineGradient { } return gradient; } - - @override - SkShader createSkiaShader() { - assert(experimentalUseSkia); - - return canvasKit.SkShader.MakeRadialGradient( - toSkPoint(center), - radius, - toSkFloatColorList(colors), - toSkColorStops(colorStops), - toSkTileMode(tileMode), - matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, - 0, - ); - } } class GradientConical extends EngineGradient { @@ -242,22 +179,6 @@ class GradientConical extends EngineGradient { Object createPaintStyle(html.CanvasRenderingContext2D? ctx) { throw UnimplementedError(); } - - @override - SkShader createSkiaShader() { - assert(experimentalUseSkia); - return canvasKit.SkShader.MakeTwoPointConicalGradient( - toSkPoint(focal), - focalRadius, - toSkPoint(center), - radius, - toSkFloatColorList(colors), - toSkColorStops(colorStops), - toSkTileMode(tileMode), - matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, - 0, - ); - } } /// Backend implementation of [ui.ImageFilter]. @@ -284,20 +205,3 @@ class EngineImageFilter implements ui.ImageFilter { return 'ImageFilter.blur($sigmaX, $sigmaY)'; } } - -/// Backend implementation of [ui.ImageShader]. -class EngineImageShader implements ui.ImageShader, EngineShader { - EngineImageShader( - ui.Image image, this.tileModeX, this.tileModeY, this.matrix4) - : _skImage = image as CkImage; - - final ui.TileMode tileModeX; - final ui.TileMode tileModeY; - final Float64List matrix4; - final CkImage _skImage; - - SkShader createSkiaShader() => _skImage.skImage.makeShader( - toSkTileMode(tileModeX), - toSkTileMode(tileModeY), - ); -} diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index ba8061fc061a6..b747583c6d3fd 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -527,3 +527,28 @@ double convertSigmaToRadius(double sigma) { bool isUnsoundNull(dynamic object) { return object == null; } + +bool _offsetIsValid(ui.Offset offset) { + assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison + 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.'); // ignore: unnecessary_null_comparison + assert(matrix4.length == 16, 'Matrix4 must have 16 entries.'); + return true; +} + +void _validateColorStops(List colors, List? 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.'); + } +} diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 44fd581c38d36..0760a1a6d2e47 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -1097,8 +1097,9 @@ abstract class Gradient extends Shader { List? colorStops, TileMode tileMode = TileMode.clamp, Float64List? matrix4, - ]) => - engine.GradientLinear(from, to, colors, colorStops, tileMode, matrix4); + ]) => engine.experimentalUseSkia + ? engine.CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4) + : engine.GradientLinear(from, to, colors, colorStops, tileMode, matrix4); /// Creates a radial gradient centered at `center` that ends at `radius` /// distance from the center. @@ -1145,12 +1146,18 @@ abstract class Gradient extends Shader { final Float32List? matrix32 = matrix4 != null ? engine.toMatrix32(matrix4) : null; if (focal == null || (focal == center && focalRadius == 0.0)) { - return engine.GradientRadial( + return engine.experimentalUseSkia + ? engine.CkGradientRadial( + center, radius, colors, colorStops, tileMode, matrix32) + : engine.GradientRadial( center, radius, colors, colorStops, tileMode, matrix32); } else { assert(center != Offset.zero || focal != Offset.zero); // will result in exception(s) in Skia side - return engine.GradientConical(focal, focalRadius, center, radius, colors, + return engine.experimentalUseSkia + ? engine.CkGradientConical(focal, focalRadius, center, radius, colors, + colorStops, tileMode, matrix32) + : engine.GradientConical(focal, focalRadius, center, radius, colors, colorStops, tileMode, matrix32); } } @@ -1189,8 +1196,10 @@ abstract class Gradient extends Shader { double startAngle = 0.0, double endAngle = math.pi * 2, Float64List? matrix4, - ]) => - engine.GradientSweep(center, colors, colorStops, tileMode, startAngle, + ]) => engine.experimentalUseSkia + ? engine.CkGradientSweep(center, colors, colorStops, tileMode, startAngle, + endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null) + : engine.GradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null); } @@ -1850,7 +1859,7 @@ class ImageShader extends Shader { factory ImageShader( Image image, TileMode tmx, TileMode tmy, Float64List matrix4) { if (engine.experimentalUseSkia) { - return engine.EngineImageShader(image, tmx, tmy, matrix4); + return engine.CkImageShader(image, tmx, tmy, matrix4); } throw UnsupportedError('ImageShader not implemented for web platform.'); } diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 8dc48df5d44eb..23da3c3d28f56 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -11,6 +11,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'common.dart'; +import 'test_data.dart'; void main() { group('CanvasKit API', () { @@ -266,7 +267,14 @@ void _imageTests() { expect(frame.height(), 1); expect(nonAnimated.decodeNextFrame(), -1); - expect(frame.makeShader(canvasKit.TileMode.Repeat, canvasKit.TileMode.Mirror), isNotNull); + expect( + frame.makeShader( + canvasKit.TileMode.Repeat, + canvasKit.TileMode.Mirror, + toSkMatrixFromFloat32(Matrix4.identity().storage), + ), + isNotNull, + ); }); test('MakeAnimatedImageFromEncoded makes an animated image', () { @@ -1176,25 +1184,3 @@ void _canvasTests() { ); }); } - -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, - 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, - 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, - 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, -]); - -/// An animated GIF image with 3 1x1 pixel frames (a red, green, and blue -/// frames). The GIF animates forever, and each frame has a 100ms delay. -final Uint8List kAnimatedGif = Uint8List.fromList( [ - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0xa1, 0x03, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x21, - 0xff, 0x0b, 0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30, - 0x03, 0x01, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x4c, - 0x01, 0x00, 0x21, 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x54, 0x01, 0x00, 0x21, - 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b, -]); diff --git a/lib/web_ui/test/canvaskit/image_test.dart b/lib/web_ui/test/canvaskit/image_test.dart new file mode 100644 index 0000000000000..8ba442c7d495d --- /dev/null +++ b/lib/web_ui/test/canvaskit/image_test.dart @@ -0,0 +1,40 @@ +// 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 +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import 'common.dart'; +import 'test_data.dart'; + +void main() { + group('CanvasKit image', () { + setUpAll(() async { + await ui.webOnlyInitializePlatform(); + }); + + test('CkAnimatedImage can be explicitly disposed of', () { + final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); + final CkAnimatedImage image = CkAnimatedImage(skAnimatedImage); + expect(image.box.isDeleted, false); + image.dispose(); + expect(image.box.isDeleted, true); + image.dispose(); + expect(image.box.isDeleted, true); + }); + + test('CkImage can be explicitly disposed of', () { + final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame(); + final CkImage image = CkImage(skImage); + expect(image.box.isDeleted, false); + image.dispose(); + expect(image.box.isDeleted, true); + image.dispose(); + expect(image.box.isDeleted, true); + }); + // TODO: https://github.com/flutter/flutter/issues/60040 + }, skip: isIosSafari); +} diff --git a/lib/web_ui/test/canvaskit/shader_test.dart b/lib/web_ui/test/canvaskit/shader_test.dart new file mode 100644 index 0000000000000..8e4ec00a98b1f --- /dev/null +++ b/lib/web_ui/test/canvaskit/shader_test.dart @@ -0,0 +1,76 @@ +// 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 +import 'dart:typed_data'; + +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import 'common.dart'; +import 'test_data.dart'; + +void main() { + group('CanvasKit shaders', () { + setUpAll(() async { + await ui.webOnlyInitializePlatform(); + }); + + test('Sweep gradient', () { + final CkGradientSweep gradient = ui.Gradient.sweep( + ui.Offset.zero, + testColors, + ); + expect(gradient.createDefault(), isNotNull); + }); + + test('Linear gradient', () { + final CkGradientLinear gradient = ui.Gradient.linear( + ui.Offset.zero, + const ui.Offset(0, 1), + testColors, + ); + expect(gradient.createDefault(), isNotNull); + }); + + test('Radial gradient', () { + final CkGradientRadial gradient = ui.Gradient.radial( + ui.Offset.zero, + 10, + testColors, + ); + expect(gradient.createDefault(), isNotNull); + }); + + test('Conical gradient', () { + final CkGradientConical gradient = ui.Gradient.radial( + ui.Offset.zero, + 10, + testColors, + null, + ui.TileMode.clamp, + null, + const ui.Offset(10, 10), + 40, + ); + expect(gradient.createDefault(), isNotNull); + }); + + test('Image shader', () { + final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage).getCurrentFrame(); + final CkImage image = CkImage(skImage); + final CkImageShader imageShader = ui.ImageShader( + image, + ui.TileMode.clamp, + ui.TileMode.repeated, + Float64List.fromList(Matrix4.diagonal3Values(1, 2, 3).storage), + ); + expect(imageShader, isA()); + }); + // TODO: https://github.com/flutter/flutter/issues/60040 + }, skip: isIosSafari); +} + +const List testColors = [ui.Color(0xFFFFFF00), ui.Color(0xFFFFFFFF)]; diff --git a/lib/web_ui/test/canvaskit/test_data.dart b/lib/web_ui/test/canvaskit/test_data.dart new file mode 100644 index 0000000000000..9ce6313d714ae --- /dev/null +++ b/lib/web_ui/test/canvaskit/test_data.dart @@ -0,0 +1,29 @@ +// 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.10 + +import 'dart:typed_data'; + +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, + 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, + 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, + 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, +]); + +/// An animated GIF image with 3 1x1 pixel frames (a red, green, and blue +/// frames). The GIF animates forever, and each frame has a 100ms delay. +final Uint8List kAnimatedGif = Uint8List.fromList( [ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0xa1, 0x03, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x21, + 0xff, 0x0b, 0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30, + 0x03, 0x01, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x4c, + 0x01, 0x00, 0x21, 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x54, 0x01, 0x00, 0x21, + 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b, +]);