Skip to content

Commit

Permalink
fix: Report the frame-count and dispose method for animated gif inputs.
Browse files Browse the repository at this point in the history
  • Loading branch information
skidder committed Nov 10, 2024
1 parent 2a8aa7e commit d1510db
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 24 deletions.
46 changes: 36 additions & 10 deletions giflib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ int giflib_decoder_get_prev_frame_delay(const giflib_decoder d)
return d->prev_frame_delay_time;
}

int giflib_decoder_get_prev_frame_disposal(const giflib_decoder d)
{
switch (d->prev_frame_disposal) {
case DISPOSE_DO_NOT:
return GIF_DISPOSE_NONE;
default:
return GIF_DISPOSE_BACKGROUND;
}
}

void giflib_decoder_release(giflib_decoder d)
{
if (d->pixels) {
Expand Down Expand Up @@ -1113,15 +1123,15 @@ int giflib_encoder_get_output_length(giflib_encoder e)
return e->dst_offset;
}

int giflib_decoder_get_loop_count(const giflib_decoder d) {
struct GifAnimationInfo giflib_decoder_get_animation_info(const giflib_decoder d) {
// Default to 1 loop (play once) if no NETSCAPE2.0 extension is found
int loop_count = 1;
GifAnimationInfo info = {1, 0}; // Initialize with defaults

// 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
return info; // Return default on allocation failure
}

memset(loopReader, 0, sizeof(struct giflib_decoder_struct));
Expand All @@ -1131,10 +1141,11 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) {
GifFileType* gif = DGifOpen(loopReader, decode_func, &error);
if (error) {
delete loopReader;
return loop_count;
return info;
}

// Read all blocks until we find NETSCAPE2.0 or hit end
bool found_loop_count = false;
// Read all blocks until we hit end
GifRecordType recordType;
while (DGifGetRecordType(gif, &recordType) == GIF_OK) {
switch (recordType) {
Expand All @@ -1144,15 +1155,17 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) {

if (DGifGetExtension(gif, &ExtFunction, &ExtData) == GIF_OK && ExtData != NULL) {
// Look for NETSCAPE2.0 extension
if (ExtFunction == APPLICATION_EXT_FUNC_CODE && ExtData[0] >= 11 &&
if (!found_loop_count &&
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
info.loop_count = ExtData[2] | (ExtData[3] << 8);
found_loop_count = true;
}
}

Expand All @@ -1167,10 +1180,23 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) {
}

case IMAGE_DESC_RECORD_TYPE:
// Skip image data
// Count frame and skip image data
info.frame_count++;
if (DGifGetImageDesc(gif) != GIF_OK) {
goto cleanup;
}
// Skip the image data
{
GifByteType* CodeBlock;
if (DGifGetCode(gif, &error, &CodeBlock) == GIF_ERROR) {
goto cleanup;
}
while (CodeBlock != NULL) {
if (DGifGetCodeNext(gif, &CodeBlock) == GIF_ERROR) {
goto cleanup;
}
}
}
break;

case TERMINATE_RECORD_TYPE:
Expand All @@ -1184,5 +1210,5 @@ int giflib_decoder_get_loop_count(const giflib_decoder d) {
cleanup:
DGifCloseFile(gif, &error);
delete loopReader;
return loop_count;
return info;
}
39 changes: 27 additions & 12 deletions giflib.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import (
)

type gifDecoder struct {
decoder C.giflib_decoder
mat C.opencv_mat
buf []byte
frameIndex int
loopCount int
loopCountRead bool
decoder C.giflib_decoder
mat C.opencv_mat
buf []byte
frameIndex int
loopCount int
frameCount int
animationInfoRead bool
}

type gifEncoder struct {
Expand Down Expand Up @@ -69,7 +70,7 @@ func (d *gifDecoder) Header() (*ImageHeader, error) {
height: int(C.giflib_decoder_get_height(d.decoder)),
pixelType: PixelType(C.CV_8UC4),
orientation: OrientationTopLeft,
numFrames: int(C.giflib_decoder_get_num_frames(d.decoder)),
numFrames: d.FrameCount(),
contentLength: len(d.buf),
}, nil
}
Expand Down Expand Up @@ -115,14 +116,28 @@ func (d *gifDecoder) BackgroundColor() uint32 {
return 0x00FFFFFF // use a non-opaque alpha value so we can see the background if needed
}

func (d *gifDecoder) LoopCount() int {
if !d.loopCountRead {
d.loopCount = int(C.giflib_decoder_get_loop_count(d.decoder))
d.loopCountRead = true
// readAnimationInfo reads the GIF info from the decoder and caches it
// this involves reading extension blocks and is relatively expensive
// so we only do it once when we need it
func (d *gifDecoder) readAnimationInfo() {
if !d.animationInfoRead {
info := C.giflib_decoder_get_animation_info(d.decoder)
d.loopCount = int(info.loop_count)
d.frameCount = int(info.frame_count)
d.animationInfoRead = true
}
}

func (d *gifDecoder) LoopCount() int {
d.readAnimationInfo()
return d.loopCount
}

func (d *gifDecoder) FrameCount() int {
d.readAnimationInfo()
return d.frameCount
}

func (d *gifDecoder) DecodeTo(f *Framebuffer) error {
h, err := d.Header()
if err != nil {
Expand Down Expand Up @@ -157,7 +172,7 @@ func (d *gifDecoder) DecodeTo(f *Framebuffer) error {
}
f.duration = time.Duration(C.giflib_decoder_get_prev_frame_delay(d.decoder)) * 10 * time.Millisecond
f.blend = NoBlend
f.dispose = DisposeToBackgroundColor
f.dispose = DisposeMethod(C.giflib_decoder_get_prev_frame_disposal(d.decoder))
f.xOffset = 0
f.yOffset = 0
d.frameIndex++
Expand Down
12 changes: 10 additions & 2 deletions giflib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
extern "C" {
#endif

struct GifAnimationInfo {
int loop_count;
int frame_count;
};

#define GIF_DISPOSE_NONE 1
#define GIF_DISPOSE_BACKGROUND 2

typedef struct giflib_decoder_struct* giflib_decoder;
typedef struct giflib_encoder_struct* giflib_encoder;

Expand Down Expand Up @@ -34,8 +42,8 @@ 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);

struct GifAnimationInfo giflib_decoder_get_animation_info(const giflib_decoder d);
int giflib_decoder_get_prev_frame_disposal(const giflib_decoder d);
#ifdef __cplusplus
}
#endif
Expand Down

0 comments on commit d1510db

Please sign in to comment.