From 142b40d515cf1ba309ce01d3c1b12719a4a2a85d Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 19 May 2021 20:53:26 -0700 Subject: [PATCH] recall the last frame in the animated image after resurrection --- .../lib/src/engine/canvaskit/image.dart | 22 +++++++++++++-- lib/web_ui/test/canvaskit/image_test.dart | 28 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index 0306ce8d7bafd..94a9034228bf3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -101,6 +101,11 @@ class CkAnimatedImage extends ManagedSkiaObject final String src; final Uint8List _bytes; + int _frameCount = 0; + int _repetitionCount = -1; + + /// The index to the next frame to be decoded. + int _nextFrameIndex = 0; @override SkAnimatedImage createDefault() { @@ -112,12 +117,24 @@ class CkAnimatedImage extends ManagedSkiaObject 'Image source: $src', ); } + + _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++) { + animatedImage.decodeNextFrame(); + } return animatedImage; } @override SkAnimatedImage resurrect() => createDefault(); + @override + bool get isResurrectionExpensive => true; + @override void delete() { rawSkiaObject?.delete(); @@ -144,13 +161,13 @@ class CkAnimatedImage extends ManagedSkiaObject @override int get frameCount { assert(_debugCheckIsNotDisposed()); - return skiaObject.getFrameCount(); + return _frameCount; } @override int get repetitionCount { assert(_debugCheckIsNotDisposed()); - return skiaObject.getRepetitionCount(); + return _repetitionCount; } @override @@ -159,6 +176,7 @@ class CkAnimatedImage extends ManagedSkiaObject final int durationMillis = skiaObject.decodeNextFrame(); final Duration duration = Duration(milliseconds: durationMillis); final CkImage image = CkImage(skiaObject.getCurrentFrame()); + _nextFrameIndex = (_nextFrameIndex + 1) % _frameCount; return Future.value(AnimatedImageFrameInfo(duration, image)); } } diff --git a/lib/web_ui/test/canvaskit/image_test.dart b/lib/web_ui/test/canvaskit/image_test.dart index 0d1e255079403..4b99a8deeb41b 100644 --- a/lib/web_ui/test/canvaskit/image_test.dart +++ b/lib/web_ui/test/canvaskit/image_test.dart @@ -43,6 +43,34 @@ void testMain() { testCollector.collectNow(); }); + test('CkAnimatedImage remembers last animation position after resurrection', () async { + browserSupportsFinalizationRegistry = false; + + Future expectFrameData(ui.FrameInfo frame, List data) async { + final ByteData frameData = await frame.image.toByteData(); + expect(frameData.buffer.asUint8List(), Uint8List.fromList(data)); + } + + final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test'); + expect(image.frameCount, 3); + expect(image.repetitionCount, -1); + + final ui.FrameInfo frame1 = await image.getNextFrame(); + expectFrameData(frame1, [0, 255, 0, 255]); + final ui.FrameInfo frame2 = await image.getNextFrame(); + expectFrameData(frame2, [0, 0, 255, 255]); + + // Pretend that the image is temporarily deleted. + image.delete(); + image.didDelete(); + + // Check that we got the 3rd frame after resurrection. + final ui.FrameInfo frame3 = await image.getNextFrame(); + expectFrameData(frame3, [255, 0, 0, 255]); + + testCollector.collectNow(); + }); + test('CkImage toString', () { final SkImage skImage = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage)