diff --git a/lib/web_ui/lib/src/engine/browser_detection.dart b/lib/web_ui/lib/src/engine/browser_detection.dart index b167480f80547..f7fbf455d3065 100644 --- a/lib/web_ui/lib/src/engine/browser_detection.dart +++ b/lib/web_ui/lib/src/engine/browser_detection.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'dom.dart'; +import 'safe_browser_api.dart'; // iOS 15 launched WebGL 2.0, but there's something broken about it, which // leads to apps failing to load. For now, we're forcing WebGL 1 on iOS. @@ -269,4 +270,4 @@ int _detectWebGLVersion() { /// Whether the current browser supports the Chromium variant of CanvasKit. bool get browserSupportsCanvaskitChromium => - domIntl.v8BreakIterator != null && domIntl.Segmenter != null; + domIntl.v8BreakIterator != null && domIntl.Segmenter != null && browserSupportsImageDecoder; 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 c6c0d2ebe5c1a..d9059a7ef4a0d 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -27,6 +27,9 @@ import 'renderer.dart'; /// Entrypoint into the CanvasKit API. late CanvasKit canvasKit; +/// Whether the [canvasKit] being used is a Chromium variant. +final bool isChromiumVariant = canvasKit.ParagraphBuilder.RequiresClientICU(); + bool get _enableCanvasKitChromiumInAutoMode => browserSupportsCanvaskitChromium; /// Sets the [CanvasKit] object on `window` so we can use `@JS()` to bind to @@ -76,8 +79,14 @@ extension CanvasKitExtension on CanvasKit { @JS('MakeAnimatedImageFromEncoded') external SkAnimatedImage? _MakeAnimatedImageFromEncoded( JSUint8Array imageData); - SkAnimatedImage? MakeAnimatedImageFromEncoded(Uint8List imageData) => - _MakeAnimatedImageFromEncoded(imageData.toJS); + SkAnimatedImage? MakeAnimatedImageFromEncoded(Uint8List imageData) { + assert( + !isChromiumVariant, + 'CanvasKit.MakeAnimatedImageFromEncoded cannot be used with the Chromium ' + 'build of CanvasKit.', + ); + return _MakeAnimatedImageFromEncoded(imageData.toJS); + } external SkShaderNamespace get Shader; external SkMaskFilterNamespace get MaskFilter; @@ -1153,10 +1162,10 @@ extension SkImageExtension on SkImage { matrix?.toJS); @JS('readPixels') - external JSUint8Array _readPixels( + external JSUint8Array? _readPixels( JSNumber srcX, JSNumber srcY, SkImageInfo imageInfo); - Uint8List readPixels(double srcX, double srcY, SkImageInfo imageInfo) => - _readPixels(srcX.toJS, srcY.toJS, imageInfo).toDart; + Uint8List? readPixels(double srcX, double srcY, SkImageInfo imageInfo) => + _readPixels(srcX.toJS, srcY.toJS, imageInfo)?.toDart; @JS('encodeToBytes') external JSUint8Array? _encodeToBytes(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index 2fcfa6b6f3791..4d3b69528b3cf 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -10,17 +10,34 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; /// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia. -FutureOr skiaInstantiateImageCodec(Uint8List list, - [int? targetWidth, int? targetHeight]) { - // If we have either a target width or target height, use canvaskit to decode. - if (browserSupportsImageDecoder && (targetWidth == null && targetHeight == null)) { - return CkBrowserImageDecoder.create( - data: list, - debugSource: 'encoded image bytes', +FutureOr skiaInstantiateImageCodec( + Uint8List list, [ + int? targetWidth, + int? targetHeight, +]) async { + if (!browserSupportsImageDecoder) { + return CkAnimatedImage.decodeFromBytes( + list, + 'encoded image bytes', + targetWidth: targetWidth, + targetHeight: targetHeight, ); - } else { - return CkAnimatedImage.decodeFromBytes(list, 'encoded image bytes', targetWidth: targetWidth, targetHeight: targetHeight); } + + final CkBrowserImageDecoder baseDecoder = await CkBrowserImageDecoder.create( + data: list, + debugSource: 'encoded image bytes', + ); + + if (targetWidth == null && targetHeight == null) { + return baseDecoder; + } + + return ResizingCodec( + baseDecoder, + targetWidth: targetWidth, + targetHeight: targetHeight, + ); } void skiaDecodeImageFromPixels( diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart b/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart index 337eac8679fdf..1a779e5821fa8 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart @@ -20,7 +20,16 @@ import 'package:ui/ui.dart' as ui; /// Wraps `SkAnimatedImage`. class CkAnimatedImage implements ui.Codec { /// Decodes an image from a list of encoded bytes. - CkAnimatedImage.decodeFromBytes(this._bytes, this.src, {this.targetWidth, this.targetHeight}) { + CkAnimatedImage.decodeFromBytes( + this._bytes, + this.src, { + this.targetWidth, + this.targetHeight, + }) : assert( + !isChromiumVariant, + 'CkAnimatedImage.decodeFromBytes cannot be used with the Chromium ' + 'build of CanvasKit.', + ) { final SkAnimatedImage skAnimatedImage = createSkAnimatedImage(); _ref = UniqueRef(this, skAnimatedImage, 'Codec'); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture.dart b/lib/web_ui/lib/src/engine/canvaskit/picture.dart index 374c24655d721..4165cf453703e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/picture.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/picture.dart @@ -109,7 +109,10 @@ class CkPicture implements ui.Picture { width: width.toDouble(), height: height.toDouble(), ); - final Uint8List pixels = skImage.readPixels(0, 0, imageInfo); + final Uint8List? pixels = skImage.readPixels(0, 0, imageInfo); + if (pixels == null) { + throw StateError('Unable to read pixels from SkImage.'); + } final SkImage? rasterImage = canvasKit.MakeImage(imageInfo, pixels, (4 * width).toDouble()); if (rasterImage == null) { throw StateError('Unable to convert image pixels into SkImage.'); diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 0948916b5d799..6564d84df781b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -9,8 +9,6 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; -final bool _ckRequiresClientICU = canvasKit.ParagraphBuilder.RequiresClientICU(); - final List _testFonts = ['FlutterTest', 'Ahem']; String? _effectiveFontFamily(String? fontFamily) { return ui_web.debugEmulateFlutterTesterEnvironment && !_testFonts.contains(fontFamily) @@ -882,7 +880,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { /// Builds the CkParagraph with the builder and deletes the builder. SkParagraph _buildSkParagraph() { - if (_ckRequiresClientICU) { + if (isChromiumVariant) { injectClientICU(_paragraphBuilder); } final SkParagraph result = _paragraphBuilder.build(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/text_fragmenter.dart b/lib/web_ui/lib/src/engine/canvaskit/text_fragmenter.dart index a762d769cc61b..5a8b92afaab08 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text_fragmenter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text_fragmenter.dart @@ -84,7 +84,7 @@ extension SegmentationCacheExtensions on SegmentationCache { /// without ICU data. void injectClientICU(SkParagraphBuilder builder) { assert( - canvasKit.ParagraphBuilder.RequiresClientICU(), + isChromiumVariant, 'This method should only be used with the CanvasKit Chromium variant.', ); diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 220f78dc4bd19..1029b0c02b687 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -296,7 +296,7 @@ void _imageTests() { ), isNotNull, ); - }); + }, skip: !canvasKitContainsCodecs); test('MakeAnimatedImageFromEncoded makes an animated image', () { final SkAnimatedImage animated = @@ -311,6 +311,13 @@ void _imageTests() { expect(frame.height(), 1); expect(animated.decodeNextFrame(), 100); } + }, skip: !canvasKitContainsCodecs); + + test('MakeAnimatedImageFromEncoded throws with Chromium variant', () { + expect( + () => canvasKit.MakeAnimatedImageFromEncoded(kAnimatedGif), + canvasKitContainsCodecs ? returnsNormally : throwsAssertionError, + ); }); } @@ -1153,7 +1160,7 @@ void _canvasTests() { canvasKit.BlendMode.SrcOver, Uint32List.fromList([0xff000000, 0xffffffff]), ); - }); + }, skip: !canvasKitContainsCodecs); test('drawCircle', () { canvas.drawCircle(1, 2, 3, SkPaint()); @@ -1171,69 +1178,71 @@ void _canvasTests() { ); }); - test('drawImageOptions', () { - final SkAnimatedImage image = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; - canvas.drawImageOptions( - image.makeImageAtCurrentFrame(), - 10, - 20, - canvasKit.FilterMode.Linear, - canvasKit.MipmapMode.None, - SkPaint(), - ); - }); + group('[wasm codecs]', () { + test('drawImageOptions', () { + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; + canvas.drawImageOptions( + image.makeImageAtCurrentFrame(), + 10, + 20, + canvasKit.FilterMode.Linear, + canvasKit.MipmapMode.None, + SkPaint(), + ); + }); - test('drawImageCubic', () { - final SkAnimatedImage image = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; - canvas.drawImageCubic( - image.makeImageAtCurrentFrame(), - 10, - 20, - 0.3, - 0.3, - SkPaint(), - ); - }); + test('drawImageCubic', () { + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; + canvas.drawImageCubic( + image.makeImageAtCurrentFrame(), + 10, + 20, + 0.3, + 0.3, + SkPaint(), + ); + }); - test('drawImageRectOptions', () { - final SkAnimatedImage image = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; - canvas.drawImageRectOptions( - image.makeImageAtCurrentFrame(), - Float32List.fromList([0, 0, 1, 1]), - Float32List.fromList([0, 0, 1, 1]), - canvasKit.FilterMode.Linear, - canvasKit.MipmapMode.None, - SkPaint(), - ); - }); + test('drawImageRectOptions', () { + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; + canvas.drawImageRectOptions( + image.makeImageAtCurrentFrame(), + Float32List.fromList([0, 0, 1, 1]), + Float32List.fromList([0, 0, 1, 1]), + canvasKit.FilterMode.Linear, + canvasKit.MipmapMode.None, + SkPaint(), + ); + }); - test('drawImageRectCubic', () { - final SkAnimatedImage image = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; - canvas.drawImageRectCubic( - image.makeImageAtCurrentFrame(), - Float32List.fromList([0, 0, 1, 1]), - Float32List.fromList([0, 0, 1, 1]), - 0.3, - 0.3, - SkPaint(), - ); - }); + test('drawImageRectCubic', () { + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; + canvas.drawImageRectCubic( + image.makeImageAtCurrentFrame(), + Float32List.fromList([0, 0, 1, 1]), + Float32List.fromList([0, 0, 1, 1]), + 0.3, + 0.3, + SkPaint(), + ); + }); - test('drawImageNine', () { - final SkAnimatedImage image = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; - canvas.drawImageNine( - image.makeImageAtCurrentFrame(), - Float32List.fromList([0, 0, 1, 1]), - Float32List.fromList([0, 0, 1, 1]), - canvasKit.FilterMode.Linear, - SkPaint(), - ); - }); + test('drawImageNine', () { + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!; + canvas.drawImageNine( + image.makeImageAtCurrentFrame(), + Float32List.fromList([0, 0, 1, 1]), + Float32List.fromList([0, 0, 1, 1]), + canvasKit.FilterMode.Linear, + SkPaint(), + ); + }); + }, skip: !canvasKitContainsCodecs); test('drawLine', () { canvas.drawLine(0, 1, 2, 3, SkPaint()); @@ -1615,7 +1624,7 @@ void _paragraphTests() { builder.pushStyle( canvasKit.TextStyle(SkTextStyleProperties()..halfLeading = true)); builder.pop(); - if (canvasKit.ParagraphBuilder.RequiresClientICU()) { + if (isChromiumVariant) { injectClientICU(builder); } final SkParagraph paragraph = builder.build(); @@ -1733,7 +1742,7 @@ void _paragraphTests() { ); builder.addText('hello'); - if (canvasKit.ParagraphBuilder.RequiresClientICU()) { + if (isChromiumVariant) { injectClientICU(builder); } @@ -1873,11 +1882,9 @@ void _paragraphTests() { v8BreakIterator = Object(); browserSupportsImageDecoder = false; - // TODO(mdebbar): we don't check image codecs for now. - // https://github.com/flutter/flutter/issues/122331 expect(getCanvasKitJsFileNames(CanvasKitVariant.full), ['canvaskit.js']); expect(getCanvasKitJsFileNames(CanvasKitVariant.chromium), ['chromium/canvaskit.js']); - expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), ['chromium/canvaskit.js', 'canvaskit.js']); + expect(getCanvasKitJsFileNames(CanvasKitVariant.auto), ['canvaskit.js']); v8BreakIterator = null; browserSupportsImageDecoder = false; diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index a8a2b04ce9469..505bf83cadd17 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -15,6 +15,9 @@ import '../common/test_initialization.dart'; const MethodCodec codec = StandardMethodCodec(); +bool get canvasKitContainsCodecs => + configuration.canvasKitVariant == CanvasKitVariant.full; + /// Common test setup for all CanvasKit unit-tests. void setUpCanvasKitTest() { setUpUnitTests( diff --git a/lib/web_ui/test/canvaskit/image_golden_test.dart b/lib/web_ui/test/canvaskit/image_golden_test.dart index 6e8fd738d9137..a46bf5650299a 100644 --- a/lib/web_ui/test/canvaskit/image_golden_test.dart +++ b/lib/web_ui/test/canvaskit/image_golden_test.dart @@ -30,9 +30,9 @@ void testMain() { _testCkAnimatedImage(); _testForImageCodecs(useBrowserImageDecoder: false); + _testForImageCodecs(useBrowserImageDecoder: true); if (browserSupportsImageDecoder) { - _testForImageCodecs(useBrowserImageDecoder: true); _testCkBrowserImageDecoder(); } @@ -62,6 +62,19 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { final List warnings = []; late void Function(String) oldPrintWarning; + final bool runGroup; + if (useBrowserImageDecoder) { + // We can only use browser codecs if the browser supports them. + runGroup = browserSupportsImageDecoder; + } else { + // We can only use wasm codecs if the CanvasKit build contains them. + runGroup = canvasKitContainsCodecs; + } + + if (!runGroup) { + return; + } + group('($mode)', () { setUp(() { browserSupportsImageDecoder = useBrowserImageDecoder; @@ -83,96 +96,98 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { printWarning = oldPrintWarning; }); - test('CkAnimatedImage can be explicitly disposed of', () { - final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kTransparentImage, 'test'); - expect(image.debugDisposed, isFalse); - image.dispose(); - expect(image.debugDisposed, isTrue); + group('[wasm codecs]', () { + test('CkAnimatedImage can be explicitly disposed of', () { + final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kTransparentImage, 'test'); + expect(image.debugDisposed, isFalse); + image.dispose(); + expect(image.debugDisposed, isTrue); - // Disallow usage after disposal - expect(() => image.frameCount, throwsAssertionError); - expect(() => image.repetitionCount, throwsAssertionError); - expect(() => image.getNextFrame(), throwsAssertionError); + // Disallow usage after disposal + expect(() => image.frameCount, throwsAssertionError); + expect(() => image.repetitionCount, throwsAssertionError); + expect(() => image.getNextFrame(), throwsAssertionError); - // Disallow double-dispose. - expect(() => image.dispose(), throwsAssertionError); - }); + // Disallow double-dispose. + expect(() => image.dispose(), throwsAssertionError); + }); - test('CkAnimatedImage iterates frames correctly', () async { - final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); - expect(image.frameCount, 3); - expect(image.repetitionCount, -1); - - final ui.FrameInfo frame1 = await image.getNextFrame(); - await expectFrameData(frame1, [255, 0, 0, 255]); - final ui.FrameInfo frame2 = await image.getNextFrame(); - await expectFrameData(frame2, [0, 255, 0, 255]); - final ui.FrameInfo frame3 = await image.getNextFrame(); - await expectFrameData(frame3, [0, 0, 255, 255]); - }); + test('CkAnimatedImage iterates frames correctly', () async { + final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); + expect(image.frameCount, 3); + expect(image.repetitionCount, -1); + + final ui.FrameInfo frame1 = await image.getNextFrame(); + await expectFrameData(frame1, [255, 0, 0, 255]); + final ui.FrameInfo frame2 = await image.getNextFrame(); + await expectFrameData(frame2, [0, 255, 0, 255]); + final ui.FrameInfo frame3 = await image.getNextFrame(); + await expectFrameData(frame3, [0, 0, 255, 255]); + }); - test('CkImage toString', () { - final SkImage skImage = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! - .makeImageAtCurrentFrame(); - final CkImage image = CkImage(skImage); - expect(image.toString(), '[1×1]'); - image.dispose(); - }); + test('CkImage toString', () { + final SkImage skImage = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! + .makeImageAtCurrentFrame(); + final CkImage image = CkImage(skImage); + expect(image.toString(), '[1×1]'); + image.dispose(); + }); - test('CkImage can be explicitly disposed of', () { - final SkImage skImage = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! - .makeImageAtCurrentFrame(); - final CkImage image = CkImage(skImage); - expect(image.debugDisposed, isFalse); - expect(image.box.isDisposed, isFalse); - image.dispose(); - expect(image.debugDisposed, isTrue); - expect(image.box.isDisposed, isTrue); + test('CkImage can be explicitly disposed of', () { + final SkImage skImage = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! + .makeImageAtCurrentFrame(); + final CkImage image = CkImage(skImage); + expect(image.debugDisposed, isFalse); + expect(image.box.isDisposed, isFalse); + image.dispose(); + expect(image.debugDisposed, isTrue); + expect(image.box.isDisposed, isTrue); - // Disallow double-dispose. - expect(() => image.dispose(), throwsAssertionError); - }); + // Disallow double-dispose. + expect(() => image.dispose(), throwsAssertionError); + }); - test('CkImage can be explicitly disposed of when cloned', () async { - final SkImage skImage = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! - .makeImageAtCurrentFrame(); - final CkImage image = CkImage(skImage); - final CountedRef box = image.box; - expect(box.refCount, 1); - expect(box.debugGetStackTraces().length, 1); + test('CkImage can be explicitly disposed of when cloned', () async { + final SkImage skImage = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! + .makeImageAtCurrentFrame(); + final CkImage image = CkImage(skImage); + final CountedRef box = image.box; + expect(box.refCount, 1); + expect(box.debugGetStackTraces().length, 1); - final CkImage clone = image.clone(); - expect(box.refCount, 2); - expect(box.debugGetStackTraces().length, 2); + final CkImage clone = image.clone(); + expect(box.refCount, 2); + expect(box.debugGetStackTraces().length, 2); - expect(image.isCloneOf(clone), isTrue); - expect(box.isDisposed, isFalse); + expect(image.isCloneOf(clone), isTrue); + expect(box.isDisposed, isFalse); - expect(skImage.isDeleted(), isFalse); - image.dispose(); - expect(box.refCount, 1); - expect(box.isDisposed, isFalse); + expect(skImage.isDeleted(), isFalse); + image.dispose(); + expect(box.refCount, 1); + expect(box.isDisposed, isFalse); - expect(skImage.isDeleted(), isFalse); - clone.dispose(); - expect(box.refCount, 0); - expect(box.isDisposed, isTrue); + expect(skImage.isDeleted(), isFalse); + clone.dispose(); + expect(box.refCount, 0); + expect(box.isDisposed, isTrue); - expect(skImage.isDeleted(), isTrue); - expect(box.debugGetStackTraces().length, 0); - }); + expect(skImage.isDeleted(), isTrue); + expect(box.debugGetStackTraces().length, 0); + }); - test('CkImage toByteData', () async { - final SkImage skImage = - canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! - .makeImageAtCurrentFrame(); - final CkImage image = CkImage(skImage); - expect((await image.toByteData()).lengthInBytes, greaterThan(0)); - expect((await image.toByteData(format: ui.ImageByteFormat.png)).lengthInBytes, greaterThan(0)); - }); + test('CkImage toByteData', () async { + final SkImage skImage = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)! + .makeImageAtCurrentFrame(); + final CkImage image = CkImage(skImage); + expect((await image.toByteData()).lengthInBytes, greaterThan(0)); + expect((await image.toByteData(format: ui.ImageByteFormat.png)).lengthInBytes, greaterThan(0)); + }); + }, skip: !canvasKitContainsCodecs); test('toByteData with decodeImageFromPixels on videoFrame formats', () async { // This test ensures that toByteData() returns pixels that can be used by decodeImageFromPixels @@ -297,27 +312,36 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { test('instantiateImageCodec with multi-frame image does not support targetWidth/targetHeight', () async { - final ui.Codec codec = await ui.instantiateImageCodec( - kAnimatedGif, - targetWidth: 2, - targetHeight: 3, - ); - final ui.Image image = (await codec.getNextFrame()).image; + final ui.Codec codec = await ui.instantiateImageCodec( + kAnimatedGif, + targetWidth: 2, + targetHeight: 3, + ); + final ui.Image image = (await codec.getNextFrame()).image; + if (browserSupportsImageDecoder) { + expect(warnings, isEmpty); + + expect(image.width, 2); + expect(image.height, 3); + } else { + // This limitation is only applicable for wasm codecs. expect( - warnings, - containsAllInOrder( - [ - 'targetWidth and targetHeight for multi-frame images not supported', - ], - ), - ); + warnings, + containsAllInOrder( + [ + 'targetWidth and targetHeight for multi-frame images not supported', + ], + ), + ); // expect the re-size did not happen, kAnimatedGif is [1x1] expect(image.width, 1); expect(image.height, 1); - image.dispose(); - codec.dispose(); + } + + image.dispose(); + codec.dispose(); }); test('skiaInstantiateWebImageCodec throws exception on request error', @@ -796,31 +820,33 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { /// Tests specific to WASM codecs bundled with CanvasKit. void _testCkAnimatedImage() { - test('ImageDecoder toByteData(PNG)', () async { - final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); - final ui.FrameInfo frame = await image.getNextFrame(); - final ByteData? png = await frame.image.toByteData(format: ui.ImageByteFormat.png); - expect(png, isNotNull); + group('CkAnimatedImage', () { + test('toByteData(PNG)', () async { + final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); + final ui.FrameInfo frame = await image.getNextFrame(); + final ByteData? png = await frame.image.toByteData(format: ui.ImageByteFormat.png); + expect(png, isNotNull); - // The precise PNG encoding is browser-specific, but we can check the file - // signature. - expect(detectContentType(png!.buffer.asUint8List()), 'image/png'); - }); + // The precise PNG encoding is browser-specific, but we can check the file + // signature. + expect(detectContentType(png!.buffer.asUint8List()), 'image/png'); + }); - test('CkAnimatedImage toByteData(RGBA)', () async { - final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); - const List> expectedColors = >[ - [255, 0, 0, 255], - [0, 255, 0, 255], - [0, 0, 255, 255], - ]; - for (int i = 0; i < image.frameCount; i++) { - final ui.FrameInfo frame = await image.getNextFrame(); - final ByteData? rgba = await frame.image.toByteData(); - expect(rgba, isNotNull); - expect(rgba!.buffer.asUint8List(), expectedColors[i]); - } - }); + test('toByteData(RGBA)', () async { + final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); + const List> expectedColors = >[ + [255, 0, 0, 255], + [0, 255, 0, 255], + [0, 0, 255, 255], + ]; + for (int i = 0; i < image.frameCount; i++) { + final ui.FrameInfo frame = await image.getNextFrame(); + final ByteData? rgba = await frame.image.toByteData(); + expect(rgba, isNotNull); + expect(rgba!.buffer.asUint8List(), expectedColors[i]); + } + }); + }, skip: !canvasKitContainsCodecs); } /// Tests specific to browser image codecs based functionality. diff --git a/lib/web_ui/test/canvaskit/shader_test.dart b/lib/web_ui/test/canvaskit/shader_test.dart index 6be40459c7295..71438ebb29d5f 100644 --- a/lib/web_ui/test/canvaskit/shader_test.dart +++ b/lib/web_ui/test/canvaskit/shader_test.dart @@ -60,73 +60,75 @@ void testMain() { expect(gradient.getSkShader(ui.FilterQuality.none), isNotNull); }); - test('Image shader initialize/dispose cycle', () { - final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!.makeImageAtCurrentFrame(); - 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), - ) as CkImageShader; - expect(imageShader, isA()); - - final UniqueRef ref = imageShader.ref!; - expect(imageShader.debugDisposed, false); - expect(imageShader.getSkShader(ui.FilterQuality.none), same(ref.nativeObject)); - expect(ref.isDisposed, false); - expect(image.debugDisposed, false); - imageShader.dispose(); - expect(imageShader.debugDisposed, true); - expect(ref.isDisposed, true); - expect(imageShader.ref, isNull); - expect(image.debugDisposed, true); - }); - - test('Image shader withQuality', () { - final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!.makeImageAtCurrentFrame(); - 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), - ) as CkImageShader; - expect(imageShader, isA()); - - final UniqueRef ref1 = imageShader.ref!; - expect(imageShader.getSkShader(ui.FilterQuality.none), same(ref1.nativeObject)); - - // Request the same quality as the default quality (none). - expect(imageShader.getSkShader(ui.FilterQuality.none), isNotNull); - final UniqueRef ref2 = imageShader.ref!; - expect(ref1, same(ref2)); - expect(ref1.isDisposed, false); - expect(image.debugDisposed, false); - - // Change quality to medium. - expect(imageShader.getSkShader(ui.FilterQuality.medium), isNotNull); - final UniqueRef ref3 = imageShader.ref!; - expect(ref1, isNot(same(ref3))); - expect(ref1.isDisposed, true, reason: 'The previous reference must be released to avoid a memory leak'); - expect(image.debugDisposed, false); - expect(imageShader.ref!.nativeObject, same(ref3.nativeObject)); - - // Ask for medium again. - expect(imageShader.getSkShader(ui.FilterQuality.medium), isNotNull); - final UniqueRef ref4 = imageShader.ref!; - expect(ref4, same(ref3)); - expect(ref3.isDisposed, false); - expect(image.debugDisposed, false); - expect(imageShader.ref!.nativeObject, same(ref4.nativeObject)); - - // Done with the shader. - imageShader.dispose(); - expect(imageShader.debugDisposed, true); - expect(ref4.isDisposed, true); - expect(imageShader.ref, isNull); - expect(image.debugDisposed, true); - }); + group('[wasm codecs]', () { + test('Image shader initialize/dispose cycle', () { + final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!.makeImageAtCurrentFrame(); + 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), + ) as CkImageShader; + expect(imageShader, isA()); + + final UniqueRef ref = imageShader.ref!; + expect(imageShader.debugDisposed, false); + expect(imageShader.getSkShader(ui.FilterQuality.none), same(ref.nativeObject)); + expect(ref.isDisposed, false); + expect(image.debugDisposed, false); + imageShader.dispose(); + expect(imageShader.debugDisposed, true); + expect(ref.isDisposed, true); + expect(imageShader.ref, isNull); + expect(image.debugDisposed, true); + }); + + test('Image shader withQuality', () { + final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)!.makeImageAtCurrentFrame(); + 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), + ) as CkImageShader; + expect(imageShader, isA()); + + final UniqueRef ref1 = imageShader.ref!; + expect(imageShader.getSkShader(ui.FilterQuality.none), same(ref1.nativeObject)); + + // Request the same quality as the default quality (none). + expect(imageShader.getSkShader(ui.FilterQuality.none), isNotNull); + final UniqueRef ref2 = imageShader.ref!; + expect(ref1, same(ref2)); + expect(ref1.isDisposed, false); + expect(image.debugDisposed, false); + + // Change quality to medium. + expect(imageShader.getSkShader(ui.FilterQuality.medium), isNotNull); + final UniqueRef ref3 = imageShader.ref!; + expect(ref1, isNot(same(ref3))); + expect(ref1.isDisposed, true, reason: 'The previous reference must be released to avoid a memory leak'); + expect(image.debugDisposed, false); + expect(imageShader.ref!.nativeObject, same(ref3.nativeObject)); + + // Ask for medium again. + expect(imageShader.getSkShader(ui.FilterQuality.medium), isNotNull); + final UniqueRef ref4 = imageShader.ref!; + expect(ref4, same(ref3)); + expect(ref3.isDisposed, false); + expect(image.debugDisposed, false); + expect(imageShader.ref!.nativeObject, same(ref4.nativeObject)); + + // Done with the shader. + imageShader.dispose(); + expect(imageShader.debugDisposed, true); + expect(ref4.isDisposed, true); + expect(imageShader.ref, isNull); + expect(image.debugDisposed, true); + }); + }, skip: !canvasKitContainsCodecs); }); } diff --git a/lib/web_ui/test/engine/browser_detect_test.dart b/lib/web_ui/test/engine/browser_detect_test.dart index 26b742e00d3a6..b871fc28781e7 100644 --- a/lib/web_ui/test/engine/browser_detect_test.dart +++ b/lib/web_ui/test/engine/browser_detect_test.dart @@ -183,9 +183,7 @@ void testMain() { intlSegmenter = Object(); // Any non-null value. browserSupportsImageDecoder = false; - // TODO(mdebbar): we don't check image codecs for now. - // https://github.com/flutter/flutter/issues/122331 - expect(browserSupportsCanvaskitChromium, isTrue); + expect(browserSupportsCanvaskitChromium, isFalse); }); test('Detect browsers that do not support v8BreakIterator', () { diff --git a/third_party/canvaskit/BUILD.gn b/third_party/canvaskit/BUILD.gn index fd2836e349b19..082448be99cc6 100644 --- a/third_party/canvaskit/BUILD.gn +++ b/third_party/canvaskit/BUILD.gn @@ -41,14 +41,10 @@ wasm_toolchain("canvaskit_chromium") { skia_use_client_icu = true skia_icu_bidi_third_party_dir = "//flutter/third_party/canvaskit/icu_bidi" - # TODO(mdebbar): Set these to false once all image decoding can be done - # using the browser's built-in codecs. - # https://github.com/flutter/flutter/issues/122331 - # In Chromium browsers, we can use the browser's built-in codecs. - skia_use_libjpeg_turbo_decode = true - skia_use_libpng_decode = true - skia_use_libwebp_decode = true + skia_use_libjpeg_turbo_decode = false + skia_use_libpng_decode = false + skia_use_libwebp_decode = false # Disable LTO. enable_lto = false