[Bug 59887] New: windowscodecs GIF decoder (CommonDecoder_Initialize) fails to load a valid multi-frame animated GIF; gdiplus then returns GenericError
http://bugs.winehq.org/show_bug.cgi?id=59887 Bug ID: 59887 Summary: windowscodecs GIF decoder (CommonDecoder_Initialize) fails to load a valid multi-frame animated GIF; gdiplus then returns GenericError Product: Wine Version: 11.0 Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: windowscodecs Assignee: wine-bugs@list.winehq.org Reporter: mcmarius@gmx.net Distribution: --- ## Description Loading a particular valid animated GIF through GDI+ (`GdipLoadImageFromFile` / `GdipLoadImageFromStream`) fails. GDI+ delegates to the builtin WIC GIF decoder (`CLSID_WICGifDecoder`, `{1f8a5601-7d4d-4cbd-9c82-1bc8d4eeb9a5}`) and the decoder's `CommonDecoder_Initialize` fails, so GDI+ reports `ExternalException: A generic error occurred in GDI+`. When the same image is consumed by a WinForms application via designer resources (`ComponentResourceManager.GetObject` → `Bitmap` → `PictureBox.Image`), the failure surfaces later as `System.ArgumentException: Parameter is not valid` at `System.Drawing.Image.get_FrameDimensionsList()` (called from `ImageAnimator.CanAnimate` when the PictureBox is parented), which crashes the app. On native Windows the same GIF loads and animates fine. This affects any .NET WinForms / GDI+ application that displays such GIFs. It was originally found because it crashes the "SpellForce 3 Universal Reforced Content Patch" installer at startup (its animated intro logo). ## Steps to reproduce 1. Take the attached `a_0_and_38.gif` (146 KB, 2 frames — a minimal cut of the original 249-frame animation that still reproduces). 2. Load it with builtin gdiplus, e.g. the attached `Repro.exe`: `WINEDLLOVERRIDES="gdiplus=b;windowscodecs=b" wine Repro.exe a_0_and_38.gif` `Repro.cs` (compiled with the in-prefix csc): ```csharp using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; class Repro { static void Main(string[] a){ using (Image img = Image.FromStream(new MemoryStream(File.ReadAllBytes(a[0])))) Console.WriteLine("frames=" + img.GetFrameCount(FrameDimension.Time)); } } ``` ## Current behavior `System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.` (builtin gdiplus + builtin windowscodecs, verified via `WINEDEBUG=+loaddll`). The `+wincodecs` trace ends at: ``` trace:wincodecs:CommonDecoder_Initialize (...,...,1) trace:wincodecs:CommonDecoder_Release (...) refcount=0 ``` i.e. `CommonDecoder_Initialize` returns failure. ## Expected behavior The GIF loads; `GetFrameCount(FrameDimension.Time)` returns the number of frames and the image animates (matches native Windows). ## Analysis / isolation - It is **not** the native gdiplus/windowscodecs path (this report is strictly about the builtin path; native DLLs are not expected to work under Wine). - It is **not** size, total memory, or frame count: synthetic GIFs of 800x450 with up to 249 frames (full or partial frames, global palette, disposal=1, min-code-size 8) all load fine under builtin gdiplus. - By losslessly truncating the original 249-frame GIF (keeping the original frame bytes), the failure was isolated to a **single frame, index 38 (0-based)**: - first 38 frames → loads OK (`trunc_38.gif`, 3.68 MB) - first 39 frames → fails (`trunc_39.gif`, 3.80 MB) - a 39-frame file where frame 38 is replaced by an earlier frame → loads OK - `frame 0 + frame 38` alone (146 KB, `a_0_and_38.gif`) → **fails** - Frame 38 descriptor: position (0,27), size 800x423, no local color table (uses the global 256-color table), not interlaced, LZW min code size 8, disposal=1. Structurally similar to its neighbours (which load), so the trigger is in that frame's image data / decode path, not an obviously malformed descriptor. The exact decode divergence (likely in `dlls/windowscodecs/gifformat.c` / the bundled GIF reader used by `CommonDecoder_Initialize`) needs source-level debugging. ## Attachments - `a_0_and_38.gif` — minimal reproducer (146 KB, 2 frames) - `trunc_38.gif` (works) / `trunc_39.gif` (fails) — off-by-one-frame pair - `pb3_exact.gif` — the full original 249-frame image (25 MB) - `Repro.exe` / `Repro.cs` — loader ## Environment - Wine 11.0 (Staging), Fedora 44 (x86_64) - .NET Framework 4.8 installed in the prefix (only used to surface the original crash; the bug reproduces with the plain `Repro.exe` loader above) -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 --- Comment #1 from mcmarius@gmx.net --- Created attachment 81210 --> http://bugs.winehq.org/attachment.cgi?id=81210 All needed Attachements to reproduce it -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 --- Comment #2 from Dmitry Timoshkov <dmitry@baikal.ru> --- This problem seems to be fixed in wine-11.11. Could you please retest with recent development version of Wine? -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 --- Comment #3 from mcmarius@gmx.net --- i tested it with wine 11.11 and the issue is still the same. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 mcmarius@gmx.net changed: What |Removed |Added ---------------------------------------------------------------------------- Version|11.0 |11.11 -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 --- Comment #4 from mcmarius@gmx.net --- ## ROOT CAUSE: full LZW table / deferred-clear boundary in DGifDecompressLine Instrumenting the LZW stream of every frame shows that **frame 38 is the only frame whose code table fills to all 4096 entries** (then continues with 12-bit codes before a clear). Every other frame emits a clear code before the table fills, so they never exercise this path. This is the GIF "deferred clear code" region of the LZW stream. The error comes from Wine's bundled GIF LZW decompressor, `DGifDecompressLine` in `dlls/windowscodecs/ungif.c`, reached via `CommonDecoder_Initialize` → `DGifSlurp`. The relevant code (wine master): ```c #define LZ_MAX_CODE 4095 ... if (Prefix[CrntCode] == NO_SUCH_CODE) { if (CrntCode == Private->RunningCode - 2) { CrntPrefix = LastCode; /* KwKwK / just-defined-code case */ Suffix[Private->RunningCode - 2] = ... DGifGetPrefixChar(Prefix, LastCode, ClearCode); } else { return GIF_ERROR; /* <-- fails here */ } } else CrntPrefix = CrntCode; ``` and the growth guard in `DGifDecompressInput`: ```c if (Private->RunningCode < LZ_MAX_CODE + 2 && ++Private->RunningCode > Private->MaxCode1 && Private->RunningBits < LZ_BITS) { Private->MaxCode1 <<= 1; Private->RunningBits++; } ``` When the table fills, the decoder stops defining new codes. If its `RunningCode` state is off by one entry from the encoder's at that fill boundary, a following code references an entry the decoder has not defined → `Prefix[CrntCode] == NO_SUCH_CODE`, not the KwKwK case → `return GIF_ERROR`, which fails the whole image. Windows' decoder accepts the same stream. → Fix direction: correct the entry-add / RunningCode bookkeeping at the full-table / deferred-clear boundary so a valid code at LZ_MAX_CODE is not treated as undefined. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 --- Comment #5 from mcmarius@gmx.net --- PR is open here: https://gitlab.winehq.org/wine/wine/-/merge_requests/11209 -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59887 Dmitry Timoshkov <dmitry@baikal.ru> changed: What |Removed |Added ---------------------------------------------------------------------------- Ever confirmed|0 |1 Status|UNCONFIRMED |NEW --- Comment #6 from Dmitry Timoshkov <dmitry@baikal.ru> --- (In reply to mcmarius from comment #3)
i tested it with wine 11.11 and the issue is still the same.
Thanks for testing. Looks like that the problem is not visible due to deferred GIF frames loading which was recently added in c2f570827e5c3241223b4d9dccc5cfbf81ffc76d. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
participants (1)
-
WineHQ Bugzilla