Repository navigation
[engine] Decode animation frames with alpha after an opaque first frame - #193113
Conversation
There was a problem hiding this comment.
Code Review
This pull request addresses an issue where decoding animated images with an opaque first frame followed by frames with alpha fails due to an invalid conversion. The fix updates image_generator.cc to inspect all frames and set the alpha type to premultiplied if any subsequent frame contains alpha, and adds corresponding regression tests. The review feedback recommends explicitly disposing of the instantiated ui.Image objects in the new tests to prevent potential native memory leaks.
| ui.Image image = (await codec.getNextFrame()).image; | ||
| ByteData imageData = (await image.toByteData())!; | ||
| expect(imageData.getUint32(0), 0xFF0000FF); | ||
| image = (await codec.getNextFrame()).image; | ||
| imageData = (await image.toByteData())!; | ||
| expect(imageData.getUint32(0), 0x00008080); | ||
| codec.dispose(); |
There was a problem hiding this comment.
The instantiated ui.Image objects are not disposed, which can lead to native memory leaks during test execution. It is a best practice to explicitly call dispose() on ui.Image instances once they are no longer needed.
| ui.Image image = (await codec.getNextFrame()).image; | |
| ByteData imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0xFF0000FF); | |
| image = (await codec.getNextFrame()).image; | |
| imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0x00008080); | |
| codec.dispose(); | |
| ui.Image image = (await codec.getNextFrame()).image; | |
| ByteData imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0xFF0000FF); | |
| image.dispose(); | |
| image = (await codec.getNextFrame()).image; | |
| imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0x00008080); | |
| image.dispose(); | |
| codec.dispose(); |
There was a problem hiding this comment.
The other codec tests in this file only dispose the codec, so I kept the same pattern
| ui.Image image = (await codec.getNextFrame()).image; | ||
| ByteData imageData = (await image.toByteData())!; | ||
| expect(imageData.getUint32(0), 0xFF0000FF); | ||
| image = (await codec.getNextFrame()).image; | ||
| imageData = (await image.toByteData())!; | ||
| expect(imageData.getUint32(0), 0x0000FFFF); | ||
| expect(imageData.getUint32(4), 0x00000000); | ||
| codec.dispose(); |
There was a problem hiding this comment.
The instantiated ui.Image objects are not disposed, which can lead to native memory leaks during test execution. It is a best practice to explicitly call dispose() on ui.Image instances once they are no longer needed.
| ui.Image image = (await codec.getNextFrame()).image; | |
| ByteData imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0xFF0000FF); | |
| image = (await codec.getNextFrame()).image; | |
| imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0x0000FFFF); | |
| expect(imageData.getUint32(4), 0x00000000); | |
| codec.dispose(); | |
| ui.Image image = (await codec.getNextFrame()).image; | |
| ByteData imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0xFF0000FF); | |
| image.dispose(); | |
| image = (await codec.getNextFrame()).image; | |
| imageData = (await image.toByteData())!; | |
| expect(imageData.getUint32(0), 0x0000FFFF); | |
| expect(imageData.getUint32(4), 0x00000000); | |
| image.dispose(); | |
| codec.dispose(); |
There was a problem hiding this comment.
The other codec tests in this file only dispose the codec, so I kept the same pattern
|
@gaaclarke The previous run had one infra failure: Linux mac_android_aot_engine hit the 60m timeout on the Mac after all build steps had passed (it was cancelled while uploading logs, no failed tests): dashboard I've merged the latest master just in case, so the CICD label was removed. Could you please add it back? |
Thanks for approval! |
|
autosubmit label was removed for flutter/flutter/193113, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR. |
|
@gaaclarke bot asked to merge main, can you add label again? |
flutter/flutter@c5a061b...55b8f88 2026-09-29 [email protected] [Android 17] Added Android 17 device to Firebase tests (flutter/flutter#193251) 2026-09-29 [email protected] [engine] Fix remaining `unawaited_futures` lints (flutter/flutter#193508) 2026-09-29 [email protected] shell: Rename first-frame tests AddFirstFrameCallback* (flutter/flutter#193418) 2026-09-28 [email protected] Enable `unawaited_futures` lint across the Flutter repository (flutter/flutter#193125) 2026-09-28 [email protected] Upgrade flutter/flutter to Roblectric 4.17 (flutter/flutter#193369) 2026-09-28 [email protected] [engine] Decode animation frames with alpha after an opaque first frame (flutter/flutter#193113) 2026-09-28 [email protected] Allow macOS tests to use macOS 15 or 26 (flutter/flutter#193317) 2026-09-28 [email protected] Cleanup DDC SDK build targets (flutter/flutter#193193) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Fixes #85831
While working on webp encoder for
imagepackage i noticed that flutter does not render some animated webp and gifs with following exception:Code sample and visual demo with the files used, since the issue has no screenshots and author did not provide his code sample:
Webp and gif Images used for this sample:
It appears that this is an old issue in how Flutter uses Skia, because for animated WebP and GIF, Skia's SkCodec::getInfo() reports the alpha type of the first frame only, and its documentation doesn't mention that (or at least i did not find it). Flutter allocates every frame's bitmap from that info without checking getFrameInfo(i).fAlphaType, so Skia rejects any later frame with alpha after an opaque first frame with kInvalidConversion.
Tested on an engine built from master (e619960, Windows x64 release) with an app that decodes every frame through
ui.instantiateImageCodec:Fix: when
SkCodec::getInfo()reportskOpaque_SkAlphaTypefor an animation,BuiltinSkiaCodecImageGeneratornow reportskPremul_SkAlphaTypeif any later frame reports a non-opaqueSkCodec::FrameInfo::fAlphaType. Animations whose later frames all reportkOpaque_SkAlphaTypestay opaque. APNG is decoded by APNGImageGenerator, which this change doesn't touch.The new tests in
testing/dart/codec_test.dartuse a 140-byte WebP and a 70-byte GIF fixture. I rancodec_test.dartwithflutter_testerfrom ahost_debug_unopt_x64build (Windows x64) with the Skia and Impeller Vulkan backends, single and multithreaded: all 16 tests pass. Without the change the two new tests fail withCould not getPixels for frame 1. The Impeller OpenGL ES tests were not tested locally.Screenshot after the fix, both with impeller enabled and disabled:
Pre-launch Checklist
///).