)]}'
{
  "commit": "9c3ea4e6e77d0472f1d964b2411643a6d1853b40",
  "tree": "b2ab666688b68ba1388071f2031cc2f2e9fd441a",
  "parents": [
    "4d8bbcef96513d7e318d2b617995c7b5925d3fcf"
  ],
  "author": {
    "name": "Kyrylo K.",
    "email": "63228361+krll-kov@users.noreply.github.com",
    "time": "Mon Sep 28 23:54:13 2026 +0000"
  },
  "committer": {
    "name": "GitHub",
    "email": "noreply@github.com",
    "time": "Mon Sep 28 23:54:13 2026 +0000"
  },
  "message": "[engine] Decode animation frames with alpha after an opaque first frame (#193113)\n\nFixes https://github.com/flutter/flutter/issues/85831\n\nWhile working on webp encoder for `image` package i noticed that flutter\ndoes not render some animated webp and gifs with following exception:\n```\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d Exception caught by image resource service \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nThe following _Exception was thrown resolving an image frame:\nException: Could not getPixels for frame 1\n\nWhen the exception was thrown, this was the stack: \nPath: \\\\Mac\\Downloads\\gif\\200w.webp\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n```\n\u003cdetails\u003e\n\u003csummary\u003eCode sample and visual demo with the files used, since the\nissue has no screenshots and author did not provide his code\nsample:\u003c/summary\u003e\n\n\u003cimg width\u003d\"1491\" height\u003d\"819\" alt\u003d\"image\"\nsrc\u003d\"https://github.com/user-attachments/assets/04e7dc17-bc7f-453f-ba28-5621d47c3f23\"\n/\u003e\n\n```dart\nimport \u0027dart:io\u0027;\n\nimport \u0027package:flutter/material.dart\u0027;\n\nvoid main() \u003d\u003e runApp(MyApp());\n\nclass MyApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: \u0027Material App\u0027,\n      home: Scaffold(\n        appBar: AppBar(title: Text(\u0027Material App Bar\u0027)),\n        body: Center(child: Column(children: \u003cWidget\u003e[\n          Image.file(File(r\u0027\\\\Mac\\Downloads\\gif\\200w.gif\u0027), height: 100,),\n          Image.file(File(r\u0027\\\\Mac\\Downloads\\gif\\200w.webp\u0027), height: 100,),\n\n          Image.file(File(r\u0027\\\\Mac\\Downloads\\gif\\giphy.gif\u0027), height: 100,),\n          Image.file(File(r\u0027\\\\Mac\\Downloads\\gif\\giphy.webp\u0027), height: 100,),\n\n          Image.file(File(r\u0027\\\\Mac\\Downloads\\gif\\opaque_first_frame_bug.gif\u0027), height: 100,),\n        ])),\n      ),\n    );\n  }\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eWebp and gif Images used for this sample:\u003c/summary\u003e\n\n\u003cimg width\u003d\"200\" height\u003d\"356\" alt\u003d\"200w\"\nsrc\u003d\"https://github.com/user-attachments/assets/abcbdd6e-d4d0-4e23-b88d-04c0e3d4a79e\"\n/\u003e\n\u003cimg width\u003d\"56\" height\u003d\"100\" alt\u003d\"200w\"\nsrc\u003d\"https://github.com/user-attachments/assets/8df4d356-1297-4aad-af85-a2b4bd23b606\"\n/\u003e\n\n\u003cimg width\u003d\"480\" height\u003d\"480\" alt\u003d\"giphy\"\nsrc\u003d\"https://github.com/user-attachments/assets/13b6952e-1bf9-49ec-b58f-52c5caf6fa04\"\n/\u003e\n\u003cimg width\u003d\"100\" height\u003d\"100\" alt\u003d\"giphy\"\nsrc\u003d\"https://github.com/user-attachments/assets/4d3bb3dc-0f8c-4384-9b1d-86e3a4474206\"\n/\u003e\n\u003cimg width\u003d\"200\" height\u003d\"200\" alt\u003d\"opaque_first_frame_bug\"\nsrc\u003d\"https://github.com/user-attachments/assets/5e41e321-04f4-4ad5-a9cd-90c0164e4c6a\"\n/\u003e\n\u003c/details\u003e\n\n\u003c/details\u003e\n\nIt appears that this is an old issue in how Flutter uses Skia, because\nfor animated WebP and GIF, Skia\u0027s SkCodec::getInfo() reports the alpha\ntype of the first frame only, and its documentation doesn\u0027t mention that\n(or at least i did not find it). Flutter allocates every frame\u0027s bitmap\nfrom that info without checking getFrameInfo(i).fAlphaType, so Skia\nrejects any later frame with alpha after an opaque first frame with\nkInvalidConversion.\n\nTested on an engine built from master (e619960004d, Windows x64 release)\nwith an app that decodes every frame through `ui.instantiateImageCodec`:\n\n| file | without the change | with the change |\n|---|---|---|\n| WebP, 59 frames, frames 46 and 53 have alpha | frames 46, 53 fail |\nall decode |\n| 3 WebP files, 15 frames each, frames 1-14 have alpha (lossy and\nlossless) | frames 1-14 fail | all decode |\n| GIF, opaque frame 0, frame 1 with transparency, disposal to background\n| frame 1 fails | all decode |\n| the same WebP frames with one alpha pixel in frame 0, and a GIF with\ndisposal none | all decode | all decode |\n\nFix: when `SkCodec::getInfo()` reports `kOpaque_SkAlphaType` for an\nanimation, `BuiltinSkiaCodecImageGenerator` now reports\n`kPremul_SkAlphaType` if any later frame reports a non-opaque\n`SkCodec::FrameInfo::fAlphaType`. Animations whose later frames all\nreport `kOpaque_SkAlphaType` stay opaque. APNG is decoded by\nAPNGImageGenerator, which this change doesn\u0027t touch.\n\nThe new tests in `testing/dart/codec_test.dart` use a 140-byte WebP and\na 70-byte GIF fixture. I ran `codec_test.dart` with `flutter_tester`\nfrom a `host_debug_unopt_x64` build (Windows x64) with the Skia and\nImpeller Vulkan backends, single and multithreaded: all 16 tests pass.\nWithout the change the two new tests fail with `Could not getPixels for\nframe 1`. The Impeller OpenGL ES tests were not tested locally.\n\n\u003cdetails\u003e\n\u003csummary\u003eScreenshot after the fix, both with impeller enabled and\ndisabled:\u003c/summary\u003e\n\u003cimg width\u003d\"1329\" height\u003d\"760\" alt\u003d\"image\"\nsrc\u003d\"https://github.com/user-attachments/assets/a01da7d8-3c40-49a1-a312-4941d23128a2\"\n/\u003e\n\u003c/details\u003e\n\n\n## Pre-launch Checklist\n\n- [x] I read the [Contributor Guide] and followed the process outlined\nthere for submitting PRs.\n- [x] I read the [AI contribution guidelines] and understand my\nresponsibilities, or I am not using AI tools.\n- [x] I read the [Tree Hygiene] wiki page, which explains my\nresponsibilities.\n- [x] I read and followed the [Flutter Style Guide], including [Features\nwe expect every widget to implement].\n- [x] I signed the [CLA].\n- [x] I listed at least one issue that this PR fixes in the description\nabove.\n- [ ] I updated/added relevant in-code documentation (doc comments with\n`///`).\n- [ ] If this PR introduces a new feature or capability, I created and\nlinked a website documentation issue or PR in [flutter/website] (or\nverified none is needed).\n- [x] I added new tests to check the change I am making, or this PR is\n[test-exempt].\n- [ ] I followed the [breaking change policy] and added [Data Driven\nFixes] where supported.\n- [ ] All existing and new tests are passing.\n\n\u003c!-- Links --\u003e\n[Contributor Guide]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview\n[AI contribution guidelines]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines\n[Tree Hygiene]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md\n[test-exempt]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests\n[Flutter Style Guide]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md\n[Features we expect every widget to implement]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement\n[CLA]: https://cla.developers.google.com/\n[flutter/website]: https://github.com/flutter/website\n[breaking change policy]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes\n[Data Driven Fixes]:\nhttps://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md",
  "tree_diff": [
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "52803e975da471ce85848acd42057382b9c2af78",
      "new_mode": 33188,
      "new_path": "engine/src/flutter/lib/ui/fixtures/opaque_first_frame_then_alpha.gif"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "3aa43d09919a62f8f9dd96aaecd519fcb0ec4205",
      "new_mode": 33188,
      "new_path": "engine/src/flutter/lib/ui/fixtures/opaque_first_frame_then_alpha.webp"
    },
    {
      "type": "modify",
      "old_id": "1ba1c4f4785bb2d0ce75bcf2d98c924f15a3cece",
      "old_mode": 33188,
      "old_path": "engine/src/flutter/lib/ui/painting/image_generator.cc",
      "new_id": "8574a94417fd17867e70ae07c6be9f706fb0d6d0",
      "new_mode": 33188,
      "new_path": "engine/src/flutter/lib/ui/painting/image_generator.cc"
    },
    {
      "type": "modify",
      "old_id": "c75145b58cd3adaf9919e204b31e9ee983e09165",
      "old_mode": 33188,
      "old_path": "engine/src/flutter/testing/dart/codec_test.dart",
      "new_id": "3ec59a76b45e35eb4627ef789426b5cb3c8c262e",
      "new_mode": 33188,
      "new_path": "engine/src/flutter/testing/dart/codec_test.dart"
    }
  ]
}
