From 93f64f9e22e69a52f82534a5f515c534b7902153 Mon Sep 17 00:00:00 2001 From: Scott Kidder Date: Thu, 7 Nov 2024 10:50:55 -0800 Subject: [PATCH] fix: improve GIF loop count detection and add test coverage - Enhance loop count detection to handle multiple extension blocks - Add proper cleanup and error handling in GIF parsing - Add test cases for duplicate loop counts and multiple extensions --- giflib.cpp | 61 +++++++++++++++++-------- testdata/dispose_bgnd.gif | Bin 0 -> 1450 bytes testdata/duplicate_number_of_loops.gif | Bin 0 -> 1622 bytes webp_test.go | 20 ++++++++ 4 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 testdata/dispose_bgnd.gif create mode 100644 testdata/duplicate_number_of_loops.gif diff --git a/giflib.cpp b/giflib.cpp index b10efda0..64504af9 100644 --- a/giflib.cpp +++ b/giflib.cpp @@ -1134,32 +1134,55 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) { return loop_count; } - // Read extension blocks + // Read all blocks until we find NETSCAPE2.0 or hit end GifRecordType recordType; - if (DGifGetRecordType(gif, &recordType) == GIF_OK && - recordType == EXTENSION_RECORD_TYPE) { - - GifByteType* ExtData; - int ExtFunction; - - if (DGifGetExtension(gif, &ExtFunction, &ExtData) == GIF_OK && ExtData != NULL) { - // Look for NETSCAPE2.0 extension - if (ExtFunction == APPLICATION_EXT_FUNC_CODE && ExtData[0] >= 11 && - memcmp(ExtData + 1, "NETSCAPE2.0", 11) == 0) { - // Get the next block with loop count - if (DGifGetExtensionNext(gif, &ExtData) == GIF_OK && - ExtData != NULL && - ExtData[0] >= 3 && - ExtData[1] == 1) { - - loop_count = ExtData[2] | (ExtData[3] << 8); + while (DGifGetRecordType(gif, &recordType) == GIF_OK) { + switch (recordType) { + case EXTENSION_RECORD_TYPE: { + GifByteType* ExtData; + int ExtFunction; + + if (DGifGetExtension(gif, &ExtFunction, &ExtData) == GIF_OK && ExtData != NULL) { + // Look for NETSCAPE2.0 extension + if (ExtFunction == APPLICATION_EXT_FUNC_CODE && ExtData[0] >= 11 && + memcmp(ExtData + 1, "NETSCAPE2.0", 11) == 0) { + // Get the next block with loop count + if (DGifGetExtensionNext(gif, &ExtData) == GIF_OK && + ExtData != NULL && + ExtData[0] >= 3 && + ExtData[1] == 1) { + loop_count = ExtData[2] | (ExtData[3] << 8); + goto cleanup; // Found what we need + } + } + + // Skip any remaining extension blocks + while (ExtData != NULL) { + if (DGifGetExtensionNext(gif, &ExtData) != GIF_OK) { + goto cleanup; + } + } } + break; } + + case IMAGE_DESC_RECORD_TYPE: + // Skip image data + if (DGifGetImageDesc(gif) != GIF_OK) { + goto cleanup; + } + break; + + case TERMINATE_RECORD_TYPE: + goto cleanup; + + default: + break; } } +cleanup: DGifCloseFile(gif, &error); delete loopReader; - return loop_count; } diff --git a/testdata/dispose_bgnd.gif b/testdata/dispose_bgnd.gif new file mode 100644 index 0000000000000000000000000000000000000000..d68504010fe1248b656d15ee84172da4ab402219 GIT binary patch literal 1450 zcmYk*dpOg390%~Drc~ z%-Iz$W>&3$+SY`PXz7U!Q~KtU``ig>i5p6lQFeuJ`lA3cRA6#wv@Ry}Ug$M`1ATit zD~#I=yMKnAT-&ZJ$YspM9QezeH({0VrMhWTk~4r(;`mLbmn?bo;qmVwo`~9zpG-_h zFKf7ius2&!c?XDt*JoVTN`+qabKVPR;>H7qxS>JZ zS{xAPF&dTCfl@IHhs)_=)g4RW!5?+iVYnG2kwPLedNL7qxD@Eh;S16V~VNT4n;G|)uo{&rKD(KfUe z&5lTEg%17+3-@h)ns#0%`3}8W((p5={-9c=B`3GFV=(@*3fXf&&VFT~(QzRP6)fET zh)dCtcQbSLk2NDJxuJ{_A_8rcY$Fq>#ZItzyh3mh9uXgN#UkFYFv5lvinWI+U{j2+ zF&bCT<|k+0tKfF%N-4J%mGwAd^Go`BesQHG5wj**d2fCnwEO!tB zJr0)uTdQAxJrI7$w%fWBHuHR}J~Nhxc%}y#B8v6l%w?%9R=S!9ZPa>3p_kRvNz3wn zwEV@UOA6j7*2yCkcG&?~RT+0GAVo?|Rgk-Fg*hQkjw4Vk&3?X$29tB+@HBLEmWONZ z%`lo4BBC&fUP7hBA_$3DH!JhXC}@WC9d>Cvvw%TpF)WE~z&A0ud94rH^Q$;r{_%IZ z%T*XO-f(?C;Xb9eu5(0_@oxD&v{WMYa+wbIzJ$Uu`8e$iC$@64D(%#%wcMK4$7WC zk>@27kloi$d%v1mH0D#a1;?;ytgSS6m5%8Br*obLI0_2*m|$G+?-w8)>slY^TG-%c zIuH$CW2DK8vFeQL2WtXt@k&!g1S ag@plK4KtFO_WvU~_(nZH(nA26N2~$y{0yQ1 literal 0 HcmV?d00001 diff --git a/webp_test.go b/webp_test.go index 2240eb9f..55588f95 100644 --- a/webp_test.go +++ b/webp_test.go @@ -413,6 +413,26 @@ func testNewWebpEncoderWithAnimatedGIFSource(t *testing.T) { resizeMethod: ImageOpsResize, wantLoops: 1, }, + { + name: "Animated GIF with duplicate number of loop count, use the first loop count", + inputPath: "testdata/duplicate_number_of_loops.gif", + outputPath: "testdata/out/duplicate_number_of_loops.webp", + width: 200, + height: 200, + quality: 80, + resizeMethod: ImageOpsResize, + wantLoops: 2, + }, + { + name: "Animated GIF with multiple extension blocks", + inputPath: "testdata/dispose_bgnd.gif", + outputPath: "testdata/out/dispose_bgnd.webp", + width: 200, + height: 200, + quality: 80, + resizeMethod: ImageOpsResize, + wantLoops: 0, + }, } for _, tc := range testCases {