diff --git a/DEPS b/DEPS index c816e538b4e6d..4871047f796b6 100644 --- a/DEPS +++ b/DEPS @@ -31,7 +31,7 @@ vars = { # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. - 'canvaskit_cipd_instance': 'NcwvqeeKK7urddCbEdDvHytdaCiCA_8-4oS_T_ouGfgC', + 'canvaskit_cipd_instance': 'CQJGaIvKwSuYCIi4hxn3jdY-Pcrdkcnnu65ZVt18oW8C', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/lib/web_ui/dev/canvaskit_lock.yaml b/lib/web_ui/dev/canvaskit_lock.yaml index f66b919c09cfc..24c765fbe29bd 100644 --- a/lib/web_ui/dev/canvaskit_lock.yaml +++ b/lib/web_ui/dev/canvaskit_lock.yaml @@ -1,4 +1,4 @@ # Specifies the version of CanvasKit to use for Flutter Web apps. # # See `lib/web_ui/README.md` for how to update this file. -canvaskit_version: "0.31.0" +canvaskit_version: "0.32.0" 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 3eb49efe23e03..d5fcf7c2b660a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -758,9 +758,14 @@ class SkColorType { class SkAnimatedImage { external int getFrameCount(); - /// Returns duration in milliseconds. external int getRepetitionCount(); + + /// Returns duration in milliseconds. + external int currentFrameDuration(); + + /// Advances to the next frame and returns its duration in milliseconds. external int decodeNextFrame(); + external SkImage makeImageAtCurrentFrame(); external int width(); external int height(); 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 2467e2a32a89b..15a6a93834ee2 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 @@ -31,8 +31,8 @@ class CkAnimatedImage extends ManagedSkiaObject int _frameCount = 0; int _repetitionCount = -1; - /// The index to the next frame to be decoded. - int _nextFrameIndex = 0; + /// Current frame index. + int _currentFrameIndex = 0; @override SkAnimatedImage createDefault() { @@ -48,11 +48,16 @@ class CkAnimatedImage extends ManagedSkiaObject _frameCount = animatedImage.getFrameCount(); _repetitionCount = animatedImage.getRepetitionCount(); - // If the object has been deleted then resurrected, it may already have - // iterated over some frames. We need to skip over them. - for (int i = 0; i < _nextFrameIndex; i++) { + // Normally CanvasKit initializes `SkAnimatedImage` to point to the first + // frame in the animation. However, if the Skia object has been deleted then + // resurrected, the framework/app may already have advanced to one of the + // subsequent frames. When that happens the value of _currentFrameIndex will + // be something other than zero, and we need to tell the decoder to skip + // over the previous frames to point to the current one. + for (int i = 0; i < _currentFrameIndex; i++) { animatedImage.decodeNextFrame(); } + return animatedImage; } @@ -100,10 +105,23 @@ class CkAnimatedImage extends ManagedSkiaObject @override Future getNextFrame() { assert(_debugCheckIsNotDisposed()); - final int durationMillis = skiaObject.decodeNextFrame(); - final Duration duration = Duration(milliseconds: durationMillis); - final CkImage image = CkImage(skiaObject.makeImageAtCurrentFrame()); - _nextFrameIndex = (_nextFrameIndex + 1) % _frameCount; - return Future.value(AnimatedImageFrameInfo(duration, image)); + final SkAnimatedImage animatedImage = skiaObject; + + // SkAnimatedImage comes pre-initialized to point to the current frame (by + // default the first frame, and, with some special resurrection logic in + // `createDefault`, to a subsequent frame if resurrection happens in the + // middle of animation). Flutter's `Codec` semantics is to initialize to + // point to "just before the first frame", i.e. the first invocation of + // `getNextFrame` returns the first frame. Therefore, we have to read the + // current Skia frame, then advance SkAnimatedImage to the next frame, and + // return the current frame. + final ui.FrameInfo currentFrame = AnimatedImageFrameInfo( + Duration(milliseconds: animatedImage.currentFrameDuration()), + CkImage(animatedImage.makeImageAtCurrentFrame()), + ); + + animatedImage.decodeNextFrame(); + _currentFrameIndex = (_currentFrameIndex + 1) % _frameCount; + return Future.value(currentFrame); } } diff --git a/lib/web_ui/lib/src/engine/configuration.dart b/lib/web_ui/lib/src/engine/configuration.dart index c5f373557f18f..88360d8519485 100644 --- a/lib/web_ui/lib/src/engine/configuration.dart +++ b/lib/web_ui/lib/src/engine/configuration.dart @@ -32,7 +32,7 @@ import 'package:js/js.dart'; /// The version of CanvasKit used by the web engine by default. // DO NOT EDIT THE NEXT LINE OF CODE MANUALLY // See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. -const String _canvaskitVersion = '0.31.0'; +const String _canvaskitVersion = '0.32.0'; /// The Web Engine configuration for the current application. FlutterConfiguration get configuration => _configuration ??= FlutterConfiguration(_jsConfiguration); diff --git a/lib/web_ui/test/canvaskit/image_golden_test.dart b/lib/web_ui/test/canvaskit/image_golden_test.dart index abfacd4bb5919..fb140b3f79f36 100644 --- a/lib/web_ui/test/canvaskit/image_golden_test.dart +++ b/lib/web_ui/test/canvaskit/image_golden_test.dart @@ -94,9 +94,9 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { expect(image.repetitionCount, -1); final ui.FrameInfo frame1 = await image.getNextFrame(); - await expectFrameData(frame1, [0, 255, 0, 255]); + await expectFrameData(frame1, [255, 0, 0, 255]); final ui.FrameInfo frame2 = await image.getNextFrame(); - await expectFrameData(frame2, [0, 0, 255, 255]); + await expectFrameData(frame2, [0, 255, 0, 255]); // Pretend that the image is temporarily deleted. image.delete(); @@ -104,7 +104,7 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { // Check that we got the 3rd frame after resurrection. final ui.FrameInfo frame3 = await image.getNextFrame(); - await expectFrameData(frame3, [255, 0, 0, 255]); + await expectFrameData(frame3, [0, 0, 255, 255]); testCollector.collectNow(); }); @@ -548,11 +548,10 @@ void _testCkAnimatedImage() { test('CkAnimatedImage toByteData(RGBA)', () async { final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); - // TODO(yjbanov): frame sequence is wrong (https://github.com/flutter/flutter/issues/95281) const List> expectedColors = >[ + [255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], - [255, 0, 0, 255], ]; for (int i = 0; i < image.frameCount; i++) { final ui.FrameInfo frame = await image.getNextFrame();