diff --git a/giflib.cpp b/giflib.cpp index d6bf6c84..b10efda0 100644 --- a/giflib.cpp +++ b/giflib.cpp @@ -1112,3 +1112,54 @@ int giflib_encoder_get_output_length(giflib_encoder e) { return e->dst_offset; } + +int giflib_decoder_get_loop_count(const giflib_decoder d) { + // Default to 1 loop (play once) if no NETSCAPE2.0 extension is found + int loop_count = 1; + + // Create a temporary decoder to read extension blocks + // We need a separate decoder because reading extension blocks modifies decoder state + giflib_decoder loopReader = new struct giflib_decoder_struct(); + if (!loopReader) { + return loop_count; // Return default on allocation failure + } + + memset(loopReader, 0, sizeof(struct giflib_decoder_struct)); + loopReader->mat = d->mat; // Share the source data + + int error = 0; + GifFileType* gif = DGifOpen(loopReader, decode_func, &error); + if (error) { + delete loopReader; + return loop_count; + } + + // Read extension blocks + 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); + } + } + } + } + + DGifCloseFile(gif, &error); + delete loopReader; + + return loop_count; +} diff --git a/giflib.go b/giflib.go index d0043e6e..655fbcdc 100644 --- a/giflib.go +++ b/giflib.go @@ -12,10 +12,12 @@ import ( ) type gifDecoder struct { - decoder C.giflib_decoder - mat C.opencv_mat - buf []byte - frameIndex int + decoder C.giflib_decoder + mat C.opencv_mat + buf []byte + frameIndex int + loopCount int + loopCountRead bool } type gifEncoder struct { @@ -114,7 +116,11 @@ func (d *gifDecoder) BackgroundColor() uint32 { } func (d *gifDecoder) LoopCount() int { - return 0 // loop indefinitely + if !d.loopCountRead { + d.loopCount = int(C.giflib_decoder_get_loop_count(d.decoder)) + d.loopCountRead = true + } + return d.loopCount } func (d *gifDecoder) DecodeTo(f *Framebuffer) error { diff --git a/giflib.hpp b/giflib.hpp index da8680f1..30e12bbc 100644 --- a/giflib.hpp +++ b/giflib.hpp @@ -34,6 +34,7 @@ bool giflib_encoder_encode_frame(giflib_encoder e, const giflib_decoder d, const bool giflib_encoder_flush(giflib_encoder e, const giflib_decoder d); void giflib_encoder_release(giflib_encoder e); int giflib_encoder_get_output_length(giflib_encoder e); +int giflib_decoder_get_loop_count(const giflib_decoder d); #ifdef __cplusplus } diff --git a/testdata/no-loop.gif b/testdata/no-loop.gif new file mode 100644 index 00000000..92f1b1ed Binary files /dev/null and b/testdata/no-loop.gif differ diff --git a/webp_test.go b/webp_test.go index 931e1134..2240eb9f 100644 --- a/webp_test.go +++ b/webp_test.go @@ -391,6 +391,7 @@ func testNewWebpEncoderWithAnimatedGIFSource(t *testing.T) { height int quality int resizeMethod ImageOpsSizeMethod + wantLoops int }{ { name: "Animated GIF with alpha channel", @@ -400,6 +401,17 @@ func testNewWebpEncoderWithAnimatedGIFSource(t *testing.T) { height: 17, quality: 80, resizeMethod: ImageOpsResize, + wantLoops: 0, + }, + { + name: "Animated GIF with specific loop count", + inputPath: "testdata/no-loop.gif", + outputPath: "testdata/out/no-loop_out.webp", + width: 200, + height: 200, + quality: 80, + resizeMethod: ImageOpsResize, + wantLoops: 1, }, } @@ -422,6 +434,11 @@ func testNewWebpEncoderWithAnimatedGIFSource(t *testing.T) { return } + // Verify loop count + if decoder.LoopCount() != tc.wantLoops { + t.Errorf("Loop count = %d, want %d", decoder.LoopCount(), tc.wantLoops) + } + dstBuf := make([]byte, destinationBufferSize) options := &ImageOptions{