diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index e224a1815422d..a9f7d9e31c548 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -325,14 +325,6 @@ abstract class Paint { factory Paint() => engine.renderer.createPaint(); factory Paint.from(Paint other) { - // This is less efficient than copying the underlying buffer or object but - // it's a reasonable default, as if a user wanted to implement a copy of a - // paint object themselves they are unable to do much better than this. - // - // TODO(matanlurey): Web team, if important to optimize, could: - // 1. Add a `engine.renderer.copyPaint` method. - // 2. Use the below code as the default implementation. - // 3. Have renderer-specific implementations override with optimized code. final Paint paint = Paint(); paint ..blendMode = other.blendMode diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart index 26c780c6ce0bb..3c00f793459b8 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart @@ -80,13 +80,16 @@ class CkCanvas { CkPaint paint, ) { const double toDegrees = 180 / math.pi; + + final skPaint = paint.toSkPaint(); skCanvas.drawArc( toSkRect(oval), startAngle * toDegrees, sweepAngle * toDegrees, useCenter, - paint.skiaObject, + skPaint, ); + skPaint.delete(); } // TODO(flar): CanvasKit does not expose sampling options available on SkCanvas.drawAtlas @@ -98,23 +101,27 @@ class CkCanvas { Uint32List? colors, ui.BlendMode blendMode, ) { + final skPaint = paint.toSkPaint(); skCanvas.drawAtlas( atlas.skImage, rects, rstTransforms, - paint.skiaObject, + skPaint, toSkBlendMode(blendMode), colors, ); + skPaint.delete(); } void drawCircle(ui.Offset c, double radius, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawCircle( c.dx, c.dy, radius, - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawColor(ui.Color color, ui.BlendMode blendMode) { @@ -125,15 +132,18 @@ class CkCanvas { } void drawDRRect(ui.RRect outer, ui.RRect inner, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawDRRect( toSkRRect(outer), toSkRRect(inner), - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawImage(CkImage image, ui.Offset offset, CkPaint paint) { final ui.FilterQuality filterQuality = paint.filterQuality; + final skPaint = paint.toSkPaint(); if (filterQuality == ui.FilterQuality.high) { skCanvas.drawImageCubic( image.skImage, @@ -141,7 +151,7 @@ class CkCanvas { offset.dy, _kMitchellNetravali_B, _kMitchellNetravali_C, - paint.skiaObject, + skPaint, ); } else { skCanvas.drawImageOptions( @@ -150,13 +160,15 @@ class CkCanvas { offset.dy, toSkFilterMode(filterQuality), toSkMipmapMode(filterQuality), - paint.skiaObject, + skPaint, ); } + skPaint.delete(); } void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) { final ui.FilterQuality filterQuality = paint.filterQuality; + final skPaint = paint.toSkPaint(); if (filterQuality == ui.FilterQuality.high) { skCanvas.drawImageRectCubic( image.skImage, @@ -164,7 +176,7 @@ class CkCanvas { toSkRect(dst), _kMitchellNetravali_B, _kMitchellNetravali_C, - paint.skiaObject, + skPaint, ); } else { skCanvas.drawImageRectOptions( @@ -173,41 +185,50 @@ class CkCanvas { toSkRect(dst), toSkFilterMode(filterQuality), toSkMipmapMode(filterQuality), - paint.skiaObject, + skPaint, ); } + skPaint.delete(); } void drawImageNine( CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawImageNine( image.skImage, toSkRect(center), toSkRect(dst), toSkFilterMode(paint.filterQuality), - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawLine(ui.Offset p1, ui.Offset p2, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawLine( p1.dx, p1.dy, p2.dx, p2.dy, - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawOval(ui.Rect rect, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawOval( toSkRect(rect), - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawPaint(CkPaint paint) { - skCanvas.drawPaint(paint.skiaObject); + final skPaint = paint.toSkPaint(); + skCanvas.drawPaint(skPaint); + skPaint.delete(); } void drawParagraph(CkParagraph paragraph, ui.Offset offset) { @@ -219,7 +240,9 @@ class CkCanvas { } void drawPath(CkPath path, CkPaint paint) { - skCanvas.drawPath(path.skiaObject, paint.skiaObject); + final skPaint = paint.toSkPaint(); + skCanvas.drawPath(path.skiaObject, skPaint); + skPaint.delete(); } void drawPicture(CkPicture picture) { @@ -228,22 +251,28 @@ class CkCanvas { } void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) { + final skPaint = paint.toSkPaint(); skCanvas.drawPoints( toSkPointMode(pointMode), points, - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawRRect(ui.RRect rrect, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawRRect( toSkRRect(rrect), - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void drawRect(ui.Rect rect, CkPaint paint) { - skCanvas.drawRect(toSkRect(rect), paint.skiaObject); + final skPaint = paint.toSkPaint(); + skCanvas.drawRect(toSkRect(rect), skPaint); + skPaint.delete(); } void drawShadow( @@ -254,11 +283,13 @@ class CkCanvas { void drawVertices( CkVertices vertices, ui.BlendMode blendMode, CkPaint paint) { + final skPaint = paint.toSkPaint(); skCanvas.drawVertices( vertices.skiaObject, toSkBlendMode(blendMode), - paint.skiaObject, + skPaint, ); + skPaint.delete(); } void restore() { @@ -278,16 +309,20 @@ class CkCanvas { } void saveLayer(ui.Rect bounds, CkPaint? paint) { + final skPaint = paint?.toSkPaint(); skCanvas.saveLayer( - paint?.skiaObject, + skPaint, toSkRect(bounds), null, null, ); + skPaint?.delete(); } void saveLayerWithoutBounds(CkPaint? paint) { - skCanvas.saveLayer(paint?.skiaObject, null, null, null); + final skPaint = paint?.toSkPaint(); + skCanvas.saveLayer(skPaint, null, null, null); + skPaint?.delete(); } void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter, @@ -298,13 +333,15 @@ class CkCanvas { } else { convertible = filter as CkManagedSkImageFilterConvertible; } - convertible.imageFilter((SkImageFilter filter) { + convertible.withSkImageFilter((SkImageFilter filter) { + final skPaint = paint?.toSkPaint(); skCanvas.saveLayer( - paint?.skiaObject, + skPaint, toSkRect(bounds), filter, 0, ); + skPaint?.delete(); }); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 99fd3671206e4..af0e484128f08 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -1516,6 +1516,9 @@ class SkImageFilter {} extension SkImageFilterExtension on SkImageFilter { external JSVoid delete(); + @JS('isDeleted') + external JSBoolean _isDeleted(); + bool isDeleted() => _isDeleted().toDart; @JS('getOutputBounds') external JSInt32Array _getOutputBounds(JSFloat32Array bounds); diff --git a/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart index 569a48edb9552..dea9b30130d8f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart @@ -71,7 +71,7 @@ abstract class CkColorFilter implements CkManagedSkImageFilterConvertible { SkColorFilter _initRawColorFilter(); @override - void imageFilter(SkImageFilterBorrow borrow) { + void withSkImageFilter(SkImageFilterBorrow borrow) { // Since ColorFilter has a const constructor it cannot store dynamically // created Skia objects. Therefore a new SkImageFilter is created every time // it's used. However, once used it's no longer needed, so it's deleted diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index 204b4972a1370..812c98feb2099 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -300,7 +300,6 @@ CkImage scaleImage(SkImage image, int? targetWidth, int? targetHeight) { ui.Rect.fromLTWH(0, 0, targetWidth!.toDouble(), targetHeight!.toDouble()), paint, ); - paint.dispose(); final CkPicture picture = recorder.endRecording(); final ui.Image finalImage = picture.toImageSync(targetWidth, targetHeight); diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart index 9ab40927656c3..6da70c1c6e881 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart @@ -10,7 +10,6 @@ import 'package:ui/ui.dart' as ui; import '../util.dart'; import 'canvaskit_api.dart'; import 'color_filter.dart'; -import 'native_memory.dart'; typedef SkImageFilterBorrow = void Function(SkImageFilter); @@ -22,7 +21,12 @@ typedef SkImageFilterBorrow = void Function(SkImageFilter); /// /// Currently implemented by [CkImageFilter] and [CkColorFilter]. abstract class CkManagedSkImageFilterConvertible implements ui.ImageFilter { - void imageFilter(SkImageFilterBorrow borrow); + /// Creates a temporary [SkImageFilter], passes it to [borrow], and then + /// immediately deletes it. + /// + /// [SkImageFilter] objects are not kept around so that their memory is + /// reclaimed immediately, rather than waiting for the GC cycle. + void withSkImageFilter(SkImageFilterBorrow borrow); Matrix4 get transform; } @@ -56,22 +60,15 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible { } class CkColorFilterImageFilter extends CkImageFilter { - CkColorFilterImageFilter({required this.colorFilter}) : super._() { - final SkImageFilter skImageFilter = colorFilter.initRawImageFilter(); - _ref = UniqueRef(this, skImageFilter, 'ImageFilter.color'); - } + CkColorFilterImageFilter({required this.colorFilter}) : super._(); final CkColorFilter colorFilter; - late final UniqueRef _ref; - @override - void imageFilter(SkImageFilterBorrow borrow) { - borrow(_ref.nativeObject); - } - - void dispose() { - _ref.dispose(); + void withSkImageFilter(SkImageFilterBorrow borrow) { + final skImageFilter = colorFilter.initRawImageFilter(); + borrow(skImageFilter); + skImageFilter.delete(); } @override @@ -93,7 +90,14 @@ class CkColorFilterImageFilter extends CkImageFilter { class _CkBlurImageFilter extends CkImageFilter { _CkBlurImageFilter( {required this.sigmaX, required this.sigmaY, required this.tileMode}) - : super._() { + : super._(); + + final double sigmaX; + final double sigmaY; + final ui.TileMode tileMode; + + @override + void withSkImageFilter(SkImageFilterBorrow borrow) { /// Return the identity matrix when both sigmaX and sigmaY are 0. Replicates /// effect of applying no filter final SkImageFilter skImageFilter; @@ -110,18 +114,9 @@ class _CkBlurImageFilter extends CkImageFilter { null, ); } - _ref = UniqueRef(this, skImageFilter, 'ImageFilter.blur'); - } - - final double sigmaX; - final double sigmaY; - final ui.TileMode tileMode; - late final UniqueRef _ref; - - @override - void imageFilter(SkImageFilterBorrow borrow) { - borrow(_ref.nativeObject); + borrow(skImageFilter); + skImageFilter.delete(); } @override @@ -149,25 +144,22 @@ class _CkMatrixImageFilter extends CkImageFilter { {required Float64List matrix, required this.filterQuality}) : matrix = Float64List.fromList(matrix), _transform = Matrix4.fromFloat32List(toMatrix32(matrix)), - super._() { - final SkImageFilter skImageFilter = - canvasKit.ImageFilter.MakeMatrixTransform( - toSkMatrixFromFloat64(matrix), - toSkFilterOptions(filterQuality), - null, - ); - _ref = UniqueRef(this, skImageFilter, 'ImageFilter.matrix'); - } + super._(); final Float64List matrix; final ui.FilterQuality filterQuality; final Matrix4 _transform; - late final UniqueRef _ref; - @override - void imageFilter(SkImageFilterBorrow borrow) { - borrow(_ref.nativeObject); + void withSkImageFilter(SkImageFilterBorrow borrow) { + final skImageFilter = + canvasKit.ImageFilter.MakeMatrixTransform( + toSkMatrixFromFloat64(matrix), + toSkFilterOptions(filterQuality), + null, + ); + borrow(skImageFilter); + skImageFilter.delete(); } @override @@ -192,23 +184,20 @@ class _CkMatrixImageFilter extends CkImageFilter { class _CkDilateImageFilter extends CkImageFilter { _CkDilateImageFilter({required this.radiusX, required this.radiusY}) - : super._() { - final SkImageFilter skImageFilter = canvasKit.ImageFilter.MakeDilate( - radiusX, - radiusY, - null, - ); - _ref = UniqueRef(this, skImageFilter, 'ImageFilter.dilate'); - } + : super._(); final double radiusX; final double radiusY; - late final UniqueRef _ref; - @override - void imageFilter(SkImageFilterBorrow borrow) { - borrow(_ref.nativeObject); + void withSkImageFilter(SkImageFilterBorrow borrow) { + final skImageFilter = canvasKit.ImageFilter.MakeDilate( + radiusX, + radiusY, + null, + ); + borrow(skImageFilter); + skImageFilter.delete(); } @override @@ -232,23 +221,20 @@ class _CkDilateImageFilter extends CkImageFilter { class _CkErodeImageFilter extends CkImageFilter { _CkErodeImageFilter({required this.radiusX, required this.radiusY}) - : super._() { - final SkImageFilter skImageFilter = canvasKit.ImageFilter.MakeErode( - radiusX, - radiusY, - null, - ); - _ref = UniqueRef(this, skImageFilter, 'ImageFilter.erode'); - } + : super._(); final double radiusX; final double radiusY; - late final UniqueRef _ref; - @override - void imageFilter(SkImageFilterBorrow borrow) { - borrow(_ref.nativeObject); + void withSkImageFilter(SkImageFilterBorrow borrow) { + final skImageFilter = canvasKit.ImageFilter.MakeErode( + radiusX, + radiusY, + null, + ); + borrow(skImageFilter); + skImageFilter.delete(); } @override @@ -272,27 +258,23 @@ class _CkErodeImageFilter extends CkImageFilter { class _CkComposeImageFilter extends CkImageFilter { _CkComposeImageFilter({required this.outer, required this.inner}) - : super._() { - outer.imageFilter((SkImageFilter outerFilter) { - inner.imageFilter((SkImageFilter innerFilter) { - final SkImageFilter skImageFilter = canvasKit.ImageFilter.MakeCompose( - outerFilter, - innerFilter, - ); - _ref = UniqueRef( - this, skImageFilter, 'ImageFilter.compose'); - }); - }); - } + : super._(); final CkImageFilter outer; final CkImageFilter inner; - late final UniqueRef _ref; - @override - void imageFilter(SkImageFilterBorrow borrow) { - borrow(_ref.nativeObject); + void withSkImageFilter(SkImageFilterBorrow borrow) { + outer.withSkImageFilter((skOuter) { + inner.withSkImageFilter((skInner) { + final skImageFilter = canvasKit.ImageFilter.MakeCompose( + skOuter, + skInner, + ); + borrow(skImageFilter); + skImageFilter.delete(); + }); + }); } @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 325322d799ab4..b4d419a926548 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -187,7 +187,6 @@ class BackdropFilterEngineLayer extends ContainerLayer // single canvas, the backdrop filter will be applied multiple times. final CkCanvas currentCanvas = paintContext.leafNodesCanvas!; currentCanvas.saveLayerWithFilter(paintBounds, _filter, paint); - paint.dispose(); paintChildren(paintContext); currentCanvas.restore(); } @@ -349,7 +348,6 @@ class OpacityEngineLayer extends ContainerLayer final ui.Rect saveLayerBounds = paintBounds.shift(-_offset); paintContext.internalNodesCanvas.saveLayer(saveLayerBounds, paint); - paint.dispose(); paintChildren(paintContext); // Restore twice: once for the translate and once for the saveLayer. paintContext.internalNodesCanvas.restore(); @@ -419,16 +417,17 @@ class ImageFilterEngineLayer extends ContainerLayer } final ui.Rect childPaintBounds = prerollChildren(prerollContext, childMatrix); - convertible.imageFilter((SkImageFilter filter) { - // If the filter is a ColorFilter, the extended paint bounds will be the - // entire screen, which is not what we want. if (_filter is ui.ColorFilter) { + // If the filter is a ColorFilter, the extended paint bounds will be the + // entire screen, which is not what we want. paintBounds = childPaintBounds; } else { - paintBounds = - rectFromSkIRect(filter.getOutputBounds(toSkRect(childPaintBounds))); + convertible.withSkImageFilter((skFilter) { + paintBounds = rectFromSkIRect( + skFilter.getOutputBounds(toSkRect(childPaintBounds)), + ); + }); } - }); prerollContext.mutatorsStack.pop(); } @@ -442,7 +441,6 @@ class ImageFilterEngineLayer extends ContainerLayer final CkPaint paint = CkPaint(); paint.imageFilter = _filter; paintContext.internalNodesCanvas.saveLayer(paintBounds, paint); - paint.dispose(); paintChildren(paintContext); paintContext.internalNodesCanvas.restore(); paintContext.internalNodesCanvas.restore(); @@ -479,7 +477,6 @@ class ShaderMaskEngineLayer extends ContainerLayer paintContext.leafNodesCanvas!.drawRect( ui.Rect.fromLTWH(0, 0, maskRect.width, maskRect.height), paint); - paint.dispose(); paintContext.leafNodesCanvas!.restore(); paintContext.internalNodesCanvas.restore(); @@ -547,7 +544,6 @@ class ColorFilterEngineLayer extends ContainerLayer paintChildren(paintContext); paintContext.internalNodesCanvas.restore(); paintContext.internalNodesCanvas.restore(); - paint.dispose(); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart index 331960ba48411..55f35541dc6a2 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart @@ -5,25 +5,14 @@ import 'package:ui/ui.dart' as ui; import 'canvaskit_api.dart'; -import 'native_memory.dart'; -/// The CanvasKit implementation of [ui.MaskFilter]. -class CkMaskFilter { - CkMaskFilter.blur(ui.BlurStyle blurStyle, double sigma) - : _blurStyle = blurStyle, - _sigma = sigma { - final SkMaskFilter skMaskFilter = canvasKit.MaskFilter.MakeBlur( - toSkBlurStyle(_blurStyle), - _sigma, - true, - )!; - _ref = UniqueRef(this, skMaskFilter, 'MaskFilter'); - } - - final ui.BlurStyle _blurStyle; - final double _sigma; - - late final UniqueRef _ref; - - SkMaskFilter get skiaObject => _ref.nativeObject; +/// Creates and returns a [SkMaskFilter] that applies a blur effect. +/// +/// It is the responsibility of the caller to delete the returned Skia object. +SkMaskFilter createBlurSkMaskFilter(ui.BlurStyle blurStyle, double sigma) { + return canvasKit.MaskFilter.MakeBlur( + toSkBlurStyle(blurStyle), + sigma, + true, + )!; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/painting.dart b/lib/web_ui/lib/src/engine/canvaskit/painting.dart index 80a811540fd42..8d791e94e59ec 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/painting.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/painting.dart @@ -21,116 +21,83 @@ import 'shader.dart'; /// /// This class is backed by a Skia object that must be explicitly /// deleted to avoid a memory leak. This is done by extending [SkiaObject]. +// TODO(154281): try to unify with SkwasmPaint class CkPaint implements ui.Paint { - CkPaint() : skiaObject = SkPaint() { - skiaObject.setAntiAlias(_isAntiAlias); - skiaObject.setColorInt(_defaultPaintColor); - _ref = UniqueRef(this, skiaObject, 'Paint'); - } - - final SkPaint skiaObject; - late final UniqueRef _ref; - CkManagedSkImageFilterConvertible? _imageFilter; + CkPaint(); - static const int _defaultPaintColor = 0xFF000000; - - /// Returns the native reference to the underlying [SkPaint] object. + /// Creates a new [SkPaint] object and returns it. /// - /// This should only be used in tests. - @visibleForTesting - UniqueRef get debugRef => _ref; - - @override - ui.BlendMode get blendMode => _blendMode; - @override - set blendMode(ui.BlendMode value) { - if (_blendMode == value) { - return; + /// The caller is responsible for deleting the returned object when it's no + /// longer needed. + SkPaint toSkPaint() { + final skPaint = SkPaint(); + skPaint.setAntiAlias(isAntiAlias); + skPaint.setBlendMode(toSkBlendMode(blendMode)); + skPaint.setStyle(toSkPaintStyle(style)); + skPaint.setStrokeWidth(strokeWidth); + skPaint.setStrokeCap(toSkStrokeCap(strokeCap)); + skPaint.setStrokeJoin(toSkStrokeJoin(strokeJoin)); + skPaint.setColorInt(_colorValue); + skPaint.setStrokeMiter(strokeMiterLimit); + + final effectiveColorFilter = _effectiveColorFilter; + if (effectiveColorFilter != null) { + skPaint.setColorFilter(effectiveColorFilter.skiaObject); } - _blendMode = value; - skiaObject.setBlendMode(toSkBlendMode(value)); - } - ui.BlendMode _blendMode = ui.BlendMode.srcOver; - - @override - ui.PaintingStyle get style => _style; - - @override - set style(ui.PaintingStyle value) { - if (_style == value) { - return; + final shader = _shader; + if (shader != null) { + skPaint.setShader(shader.getSkShader(filterQuality)); } - _style = value; - skiaObject.setStyle(toSkPaintStyle(value)); - } - - ui.PaintingStyle _style = ui.PaintingStyle.fill; - @override - double get strokeWidth => _strokeWidth; - @override - set strokeWidth(double value) { - if (_strokeWidth == value) { - return; + final localMaskFilter = maskFilter; + if (localMaskFilter != null) { + // CanvasKit returns `null` if the sigma is `0` or infinite. + if (localMaskFilter.webOnlySigma.isFinite && localMaskFilter.webOnlySigma > 0) { + skPaint.setMaskFilter(createBlurSkMaskFilter( + localMaskFilter.webOnlyBlurStyle, + localMaskFilter.webOnlySigma, + )); + } } - _strokeWidth = value; - skiaObject.setStrokeWidth(value); - } - double _strokeWidth = 0.0; - - @override - ui.StrokeCap get strokeCap => _strokeCap; - @override - set strokeCap(ui.StrokeCap value) { - if (_strokeCap == value) { - return; + final localImageFilter = _imageFilter; + if (localImageFilter != null) { + localImageFilter.withSkImageFilter((skImageFilter) { + skPaint.setImageFilter(skImageFilter); + }); } - _strokeCap = value; - skiaObject.setStrokeCap(toSkStrokeCap(value)); - } - ui.StrokeCap _strokeCap = ui.StrokeCap.butt; + return skPaint; + } @override - ui.StrokeJoin get strokeJoin => _strokeJoin; + ui.BlendMode blendMode = ui.BlendMode.srcOver; + @override - set strokeJoin(ui.StrokeJoin value) { - if (_strokeJoin == value) { - return; - } - _strokeJoin = value; - skiaObject.setStrokeJoin(toSkStrokeJoin(value)); - } + ui.PaintingStyle style = ui.PaintingStyle.fill; - ui.StrokeJoin _strokeJoin = ui.StrokeJoin.miter; + @override + double strokeWidth = 0.0; @override - bool get isAntiAlias => _isAntiAlias; + ui.StrokeCap strokeCap = ui.StrokeCap.butt; + @override - set isAntiAlias(bool value) { - if (_isAntiAlias == value) { - return; - } - _isAntiAlias = value; - skiaObject.setAntiAlias(value); - } + ui.StrokeJoin strokeJoin = ui.StrokeJoin.miter; - bool _isAntiAlias = true; + @override + bool isAntiAlias = true; @override - ui.Color get color => ui.Color(_color); + ui.Color get color => ui.Color(_colorValue); @override set color(ui.Color value) { - if (_color == value.value) { - return; - } - _color = value.value; - skiaObject.setColorInt(value.value); + _colorValue = value.value; } - int _color = _defaultPaintColor; + static const int _defaultPaintColorValue = 0xFF000000; + int _colorValue = _defaultPaintColorValue; @override bool get invertColors => _invertColors; @@ -152,7 +119,6 @@ class CkPaint implements ui.Paint { ); } } - skiaObject.setColorFilter(_effectiveColorFilter?.skiaObject); _invertColors = value; } @@ -170,52 +136,15 @@ class CkPaint implements ui.Paint { return; } _shader = value as CkShader?; - skiaObject.setShader(_shader?.getSkShader(_filterQuality)); } CkShader? _shader; @override - ui.MaskFilter? get maskFilter => _maskFilter; - @override - set maskFilter(ui.MaskFilter? value) { - if (value == _maskFilter) { - return; - } - _maskFilter = value; - if (value != null) { - // CanvasKit returns `null` if the sigma is `0` or infinite. - if (!(value.webOnlySigma.isFinite && value.webOnlySigma > 0)) { - // Don't create a [CkMaskFilter]. - _ckMaskFilter = null; - } else { - _ckMaskFilter = CkMaskFilter.blur( - value.webOnlyBlurStyle, - value.webOnlySigma, - ); - } - } else { - _ckMaskFilter = null; - } - skiaObject.setMaskFilter(_ckMaskFilter?.skiaObject); - } + ui.MaskFilter? maskFilter; - ui.MaskFilter? _maskFilter; - CkMaskFilter? _ckMaskFilter; - - @override - ui.FilterQuality get filterQuality => _filterQuality; @override - set filterQuality(ui.FilterQuality value) { - if (_filterQuality == value) { - return; - } - _filterQuality = value; - skiaObject.setShader(_shader?.getSkShader(value)); - } - - ui.FilterQuality _filterQuality = ui.FilterQuality.none; - EngineColorFilter? _engineColorFilter; + ui.FilterQuality filterQuality = ui.FilterQuality.none; @override ui.ColorFilter? get colorFilter => _engineColorFilter; @@ -244,27 +173,18 @@ class CkPaint implements ui.Paint { ); } } - - skiaObject.setColorFilter(_effectiveColorFilter?.skiaObject); } + /// The original color filter objects passed by the framework. + EngineColorFilter? _engineColorFilter; + /// The effective color filter. /// /// This is a combination of the `colorFilter` and `invertColors` properties. ManagedSkColorFilter? _effectiveColorFilter; @override - double get strokeMiterLimit => _strokeMiterLimit; - @override - set strokeMiterLimit(double value) { - if (_strokeMiterLimit == value) { - return; - } - _strokeMiterLimit = value; - skiaObject.setStrokeMiter(value); - } - - double _strokeMiterLimit = 0.0; + double strokeMiterLimit = 4.0; @override ui.ImageFilter? get imageFilter => _imageFilter; @@ -273,29 +193,15 @@ class CkPaint implements ui.Paint { if (_imageFilter == value) { return; } - final CkManagedSkImageFilterConvertible? filter; - if (value is ui.ColorFilter) { - filter = createCkColorFilter(value as EngineColorFilter); - } - else { - filter = value as CkManagedSkImageFilterConvertible?; - } - if (filter != null) { - filter.imageFilter((SkImageFilter skImageFilter) { - skiaObject.setImageFilter(skImageFilter); - }); + if (value is ui.ColorFilter) { + _imageFilter = createCkColorFilter(value as EngineColorFilter); + } else { + _imageFilter = value as CkManagedSkImageFilterConvertible?; } - - _imageFilter = filter; } - /// Disposes of this paint object. - /// - /// This object cannot be used again after calling this method. - void dispose() { - _ref.dispose(); - } + CkManagedSkImageFilterConvertible? _imageFilter; // Must be kept in sync with the default in paint.cc. static const double _kStrokeMiterLimitDefault = 4.0; diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 3fc550fb28f8a..e012f6eebe5ef 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -1169,38 +1169,47 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { return _styleStack.last; } - // Used as the paint for background or foreground in the text style when - // the other one is not specified. CanvasKit either both background and - // foreground paints specified, or neither, but Flutter allows one of them - // to go unspecified. - // - // This object is never deleted. It is effectively a static global constant. - // Therefore it doesn't need to be wrapped in CkPaint. - static final SkPaint _defaultTextForeground = SkPaint(); - static final SkPaint _defaultTextBackground = SkPaint() - ..setColorInt(0x00000000); + static SkPaint createForegroundPaint(CkTextStyle style) { + final SkPaint foreground; + if (style.foreground != null) { + foreground = style.foreground!.toSkPaint(); + } else { + foreground = SkPaint(); + foreground.setColorInt( + style.color?.value ?? 0xFF000000, + ); + } + return foreground; + } + + static SkPaint createBackgroundPaint(CkTextStyle style) { + final SkPaint background; + if (style.background != null) { + background = style.background!.toSkPaint(); + } else { + background = SkPaint() + ..setColorInt(0x00000000); + } + return background; + } @override - void pushStyle(ui.TextStyle style) { + void pushStyle(ui.TextStyle leafStyle) { + leafStyle as CkTextStyle; + final CkTextStyle baseStyle = _peekStyle(); - final CkTextStyle ckStyle = style as CkTextStyle; - final CkTextStyle skStyle = baseStyle.mergeWith(ckStyle); - _styleStack.add(skStyle); - if (skStyle.foreground != null || skStyle.background != null) { - SkPaint? foreground = skStyle.foreground?.skiaObject; - if (foreground == null) { - _defaultTextForeground.setColorInt( - skStyle.color?.value ?? 0xFF000000, - ); - foreground = _defaultTextForeground; - } + final CkTextStyle mergedStyle = baseStyle.mergeWith(leafStyle); + _styleStack.add(mergedStyle); - final SkPaint background = - skStyle.background?.skiaObject ?? _defaultTextBackground; + if (mergedStyle.foreground != null || mergedStyle.background != null) { + final foreground = createForegroundPaint(mergedStyle); + final background = createBackgroundPaint(mergedStyle); _paragraphBuilder.pushPaintStyle( - skStyle.skTextStyle, foreground, background); + mergedStyle.skTextStyle, foreground, background); + foreground.delete(); + background.delete(); } else { - _paragraphBuilder.pushStyle(skStyle.skTextStyle); + _paragraphBuilder.pushStyle(mergedStyle.skTextStyle); } } } diff --git a/lib/web_ui/lib/src/engine/html/painting.dart b/lib/web_ui/lib/src/engine/html/painting.dart index dcf14e7d39600..3f442506e9ad3 100644 --- a/lib/web_ui/lib/src/engine/html/painting.dart +++ b/lib/web_ui/lib/src/engine/html/painting.dart @@ -148,7 +148,7 @@ class SurfacePaint implements ui.Paint { // TODO(ferhat): see https://github.com/flutter/flutter/issues/33605 @override - double strokeMiterLimit = 0; + double strokeMiterLimit = 4.0; // TODO(ferhat): Implement ImageFilter, flutter/flutter#35156. @override diff --git a/lib/web_ui/test/canvaskit/filter_test.dart b/lib/web_ui/test/canvaskit/filter_test.dart index e397134cc96c4..f02230fbd7f65 100644 --- a/lib/web_ui/test/canvaskit/filter_test.dart +++ b/lib/web_ui/test/canvaskit/filter_test.dart @@ -54,16 +54,25 @@ void testMain() { setUpCanvasKitTest(withImplicitView: true); group('ImageFilters', () { - test('can be constructed', () { - final CkImageFilter imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10, tileMode: ui.TileMode.clamp); - expect(imageFilter, isA()); - SkImageFilter? skFilter; - imageFilter.imageFilter((SkImageFilter value) { - skFilter = value; - }); - expect(skFilter, isNotNull); - }); - + { + final testFilters = createImageFilters(); + for (final imageFilter in testFilters) { + test('${imageFilter.runtimeType}.withSkImageFilter creates temp SkImageFilter', () { + expect(imageFilter, isA()); + SkImageFilter? skFilter; + imageFilter.withSkImageFilter((value) { + expect(value.isDeleted(), isFalse); + skFilter = value; + }); + expect(skFilter, isNotNull); + expect( + reason: 'Because the SkImageFilter instance is temporary', + skFilter!.isDeleted(), + isTrue, + ); + }); + } + } test('== operator', () { final List filters1 = [ diff --git a/lib/web_ui/test/canvaskit/painting_test.dart b/lib/web_ui/test/canvaskit/painting_test.dart index c2ab64cab621e..c27aa99ed4e60 100644 --- a/lib/web_ui/test/canvaskit/painting_test.dart +++ b/lib/web_ui/test/canvaskit/painting_test.dart @@ -7,7 +7,6 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import '../common/matchers.dart'; import 'common.dart'; void main() { @@ -18,17 +17,11 @@ void testMain() { group('CkPaint', () { setUpCanvasKitTest(); - test('lifecycle', () { - final CkPaint paint = CkPaint(); - expect(paint.skiaObject, isNotNull); - expect(paint.debugRef.isDisposed, isFalse); - paint.dispose(); - expect(paint.debugRef.isDisposed, isTrue); - expect( - reason: 'Cannot dispose more than once', - () => paint.dispose(), - throwsA(isAssertionError), - ); + test('toSkPaint', () { + final paint = CkPaint(); + final skPaint = paint.toSkPaint(); + expect(skPaint, isNotNull); + skPaint.delete(); }); }); } diff --git a/lib/web_ui/test/ui/paint_test.dart b/lib/web_ui/test/ui/paint_test.dart index a0264d7a98494..759aa636321d7 100644 --- a/lib/web_ui/test/ui/paint_test.dart +++ b/lib/web_ui/test/ui/paint_test.dart @@ -18,6 +18,24 @@ Future testMain() async { setUpTestViewDimensions: false, ); + test('default field values are as documented on api.flutter.dev', () { + final paint = ui.Paint(); + expect(paint.blendMode, ui.BlendMode.srcOver); + expect(paint.color, const ui.Color(0xFF000000)); + expect(paint.colorFilter, null); + expect(paint.filterQuality, ui.FilterQuality.none); + expect(paint.imageFilter, null); + expect(paint.invertColors, false); + expect(paint.isAntiAlias, true); + expect(paint.maskFilter, null); + expect(paint.shader, null); + expect(paint.strokeCap, ui.StrokeCap.butt); + expect(paint.strokeJoin, ui.StrokeJoin.miter); + expect(paint.strokeMiterLimit, 4.0); + expect(paint.strokeWidth, 0.0); + expect(paint.style, ui.PaintingStyle.fill); + }); + test('toString()', () { final ui.Paint paint = ui.Paint(); paint.blendMode = ui.BlendMode.darken;